mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-16 17:36:07 +00:00
feat(agent-platform): add scenario-based route creation
This commit is contained in:
@@ -20,6 +20,10 @@ await loadEnvFiles();
|
||||
const paths = evidencePaths(caseId);
|
||||
await ensureEvidence(paths);
|
||||
const mobileScreenshot = paths.screenshot.replace(/\.png$/, "-mobile.png");
|
||||
const scenarioMenuScreenshot = paths.screenshot.replace(
|
||||
/\.png$/,
|
||||
"-scenario-menu.png",
|
||||
);
|
||||
|
||||
const startedAt = new Date();
|
||||
const frontendUrl = process.env.LANGBOT_FRONTEND_URL || "";
|
||||
@@ -50,6 +54,7 @@ const result = {
|
||||
network_log: paths.networkLog,
|
||||
screenshot: paths.screenshot,
|
||||
mobile_screenshot: mobileScreenshot,
|
||||
scenario_menu_screenshot: scenarioMenuScreenshot,
|
||||
automation_result_json: paths.automationResultJson,
|
||||
result_json: paths.resultJson,
|
||||
},
|
||||
@@ -93,10 +98,23 @@ try {
|
||||
.getByText(/Event Routing|事件路由|イベントルーティング/)
|
||||
.first()
|
||||
.waitFor();
|
||||
const addBehavior = page.getByRole("button", {
|
||||
name: /Add behavior|添加行为|動作を追加/,
|
||||
});
|
||||
await addBehavior.waitFor();
|
||||
await addBehavior.click();
|
||||
const messageBehavior = page.getByRole("menuitem", {
|
||||
name: /Reply to messages|回复收到的消息|受信メッセージに返信/,
|
||||
});
|
||||
await messageBehavior.waitFor();
|
||||
await page.waitForTimeout(250);
|
||||
await safeScreenshot(page, scenarioMenuScreenshot);
|
||||
await messageBehavior.click();
|
||||
await page
|
||||
.getByRole("button", { name: /Add Route|添加路由|ルートを追加/ })
|
||||
.getByText(/Message received|收到消息|メッセージを受信/)
|
||||
.first()
|
||||
.waitFor();
|
||||
result.visible_signals.push("create-mode-routing");
|
||||
result.visible_signals.push("create-mode-routing", "scenario-route-added");
|
||||
|
||||
const create = await apiJson(backendUrl, "/api/v1/platform/bots", {
|
||||
method: "POST",
|
||||
|
||||
@@ -27,7 +27,7 @@ automation_env:
|
||||
preconditions:
|
||||
- "The target is a local test instance where a temporary HTTP Bot may be created and deleted."
|
||||
steps:
|
||||
- "Open Create Bot, choose HTTP Bot, and confirm event routing can be configured before the first save."
|
||||
- "Open Create Bot, choose HTTP Bot, and add a message-reply behavior before the first save."
|
||||
- "Create a temporary HTTP Bot with a saved message.received route to the discard processor."
|
||||
- "Open the Bot configuration in the WebUI."
|
||||
- "Confirm the adapter capability summary, friendly event name, target, and route status are visible."
|
||||
@@ -36,7 +36,7 @@ steps:
|
||||
- "Run the saved runtime route with a synthetic event."
|
||||
- "Close the dialog and confirm the route card shows the latest discarded status."
|
||||
checks:
|
||||
- "UI: A user can choose a channel and add event routes during initial Bot creation."
|
||||
- "UI: A user can choose a channel and add a scenario-labeled behavior during initial Bot creation."
|
||||
- "UI: Event routing uses user-facing labels and does not require the raw event name in the primary route card."
|
||||
- "UI: Definite route shadowing and unmatched-event fallback behavior are visible without opening raw logs."
|
||||
- "UI: Dry-run visibly reports that the route matched the discard processor."
|
||||
|
||||
@@ -18,10 +18,15 @@ import {
|
||||
GripVertical,
|
||||
Info,
|
||||
ListChecks,
|
||||
MessageSquare,
|
||||
Plus,
|
||||
Play,
|
||||
RefreshCw,
|
||||
Shield,
|
||||
Trash2,
|
||||
UserCheck,
|
||||
UserMinus,
|
||||
UserPlus,
|
||||
Workflow,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
@@ -56,6 +61,13 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
@@ -145,6 +157,39 @@ const ELEMENTS = [
|
||||
// only emit message.received, so that's the sole fallback option.
|
||||
const DEFAULT_EVENTS = ['message.received'];
|
||||
|
||||
const BEHAVIOR_PRESETS = [
|
||||
{
|
||||
eventType: 'message.received',
|
||||
labelKey: 'bots.behaviorReplyMessages',
|
||||
descriptionKey: 'bots.behaviorReplyMessagesDescription',
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
eventType: 'group.member_joined',
|
||||
labelKey: 'bots.behaviorWelcomeMembers',
|
||||
descriptionKey: 'bots.behaviorWelcomeMembersDescription',
|
||||
icon: UserPlus,
|
||||
},
|
||||
{
|
||||
eventType: 'group.member_left',
|
||||
labelKey: 'bots.behaviorHandleDepartures',
|
||||
descriptionKey: 'bots.behaviorHandleDeparturesDescription',
|
||||
icon: UserMinus,
|
||||
},
|
||||
{
|
||||
eventType: 'friend.request_received',
|
||||
labelKey: 'bots.behaviorReviewFriendRequests',
|
||||
descriptionKey: 'bots.behaviorReviewFriendRequestsDescription',
|
||||
icon: UserCheck,
|
||||
},
|
||||
{
|
||||
eventType: 'group.member_banned',
|
||||
labelKey: 'bots.behaviorHandleModeration',
|
||||
descriptionKey: 'bots.behaviorHandleModerationDescription',
|
||||
icon: Shield,
|
||||
},
|
||||
];
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function isMessageEventPattern(p: string) {
|
||||
@@ -1451,6 +1496,13 @@ export default function EventBindingsEditor({
|
||||
() => findCatchAllRouteIndex(bindings),
|
||||
[bindings],
|
||||
);
|
||||
const behaviorPresets = useMemo(
|
||||
() =>
|
||||
BEHAVIOR_PRESETS.filter((preset) =>
|
||||
dryRunEventOptions.includes(preset.eventType),
|
||||
),
|
||||
[dryRunEventOptions],
|
||||
);
|
||||
|
||||
const refreshRouteStatuses = useCallback(async () => {
|
||||
if (!botId) {
|
||||
@@ -1479,11 +1531,11 @@ export default function EventBindingsEditor({
|
||||
form.setValue('event_bindings', next, { shouldDirty: true });
|
||||
}
|
||||
|
||||
function addBinding() {
|
||||
function addBinding(eventPattern = 'message.received') {
|
||||
updateBindings([
|
||||
...bindings,
|
||||
{
|
||||
event_pattern: 'message.received',
|
||||
event_pattern: eventPattern,
|
||||
target_type: 'agent',
|
||||
target_uuid: '',
|
||||
priority: 0,
|
||||
@@ -1673,10 +1725,48 @@ export default function EventBindingsEditor({
|
||||
</DndContext>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="outline" size="sm" onClick={addBinding}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
{t('bots.addEventBinding')}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="button" variant="outline" size="sm">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
{t('bots.addBehavior')}
|
||||
<ChevronDown className="ml-1 h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[300px] max-w-[90vw]">
|
||||
{behaviorPresets.map((preset) => {
|
||||
const Icon = preset.icon;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={preset.eventType}
|
||||
className="items-start gap-2 py-2"
|
||||
onClick={() => addBinding(preset.eventType)}
|
||||
>
|
||||
<Icon className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<span className="flex min-w-0 flex-col gap-0.5">
|
||||
<span>{t(preset.labelKey)}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t(preset.descriptionKey)}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="items-start gap-2 py-2"
|
||||
onClick={() => addBinding(dryRunEventOptions[0])}
|
||||
>
|
||||
<Workflow className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<span className="flex min-w-0 flex-col gap-0.5">
|
||||
<span>{t('bots.behaviorCustom')}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('bots.behaviorCustomDescription')}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<RouteDryRunDialog
|
||||
botId={botId}
|
||||
bindings={bindings}
|
||||
|
||||
@@ -367,6 +367,25 @@ const enUS = {
|
||||
'Choose which processor handles each event received by this bot. Edit the processing logic on the Agent page. Pipelines only support message events.',
|
||||
eventBindings: 'Event Routes',
|
||||
addEventBinding: 'Add Route',
|
||||
addBehavior: 'Add behavior',
|
||||
behaviorReplyMessages: 'Reply to messages',
|
||||
behaviorReplyMessagesDescription:
|
||||
'Send incoming messages to an Agent or Pipeline.',
|
||||
behaviorWelcomeMembers: 'Welcome new members',
|
||||
behaviorWelcomeMembersDescription:
|
||||
'Run an Agent when someone joins a group.',
|
||||
behaviorHandleDepartures: 'Handle member departures',
|
||||
behaviorHandleDeparturesDescription:
|
||||
'Run an Agent when someone leaves or is removed.',
|
||||
behaviorReviewFriendRequests: 'Review friend requests',
|
||||
behaviorReviewFriendRequestsDescription:
|
||||
'Let an Agent decide how to handle a new request.',
|
||||
behaviorHandleModeration: 'Handle moderation events',
|
||||
behaviorHandleModerationDescription:
|
||||
'Run an Agent when a group member is restricted.',
|
||||
behaviorCustom: 'Configure another event',
|
||||
behaviorCustomDescription:
|
||||
'Add a route and choose from every event supported by this adapter.',
|
||||
eventPattern: 'Event',
|
||||
eventPatternPlaceholder: 'Select event',
|
||||
targetType: 'Target Type',
|
||||
|
||||
@@ -373,6 +373,25 @@ const jaJP = {
|
||||
'このボットが受信した各イベントをどのプロセッサーに渡すかを選択します。処理ロジックは Agent ページで編集します。Pipeline はメッセージイベントのみ対応します。',
|
||||
eventBindings: 'イベントルート',
|
||||
addEventBinding: 'ルートを追加',
|
||||
addBehavior: '動作を追加',
|
||||
behaviorReplyMessages: '受信メッセージに返信',
|
||||
behaviorReplyMessagesDescription:
|
||||
'受信メッセージを Agent または Pipeline で処理します。',
|
||||
behaviorWelcomeMembers: '新しいメンバーを歓迎',
|
||||
behaviorWelcomeMembersDescription:
|
||||
'メンバーがグループに参加したときに Agent を実行します。',
|
||||
behaviorHandleDepartures: 'メンバーの退出を処理',
|
||||
behaviorHandleDeparturesDescription:
|
||||
'メンバーが退出または削除されたときに Agent を実行します。',
|
||||
behaviorReviewFriendRequests: '友だち申請を確認',
|
||||
behaviorReviewFriendRequestsDescription:
|
||||
'新しい申請の処理方法を Agent に判断させます。',
|
||||
behaviorHandleModeration: 'モデレーションイベントを処理',
|
||||
behaviorHandleModerationDescription:
|
||||
'グループメンバーが制限されたときに Agent を実行します。',
|
||||
behaviorCustom: '別のイベントを設定',
|
||||
behaviorCustomDescription:
|
||||
'ルートを追加し、このアダプターが対応する全イベントから選択します。',
|
||||
eventPattern: 'イベント',
|
||||
eventPatternPlaceholder: 'イベントを選択',
|
||||
targetType: 'ターゲットタイプ',
|
||||
|
||||
@@ -352,6 +352,21 @@ const zhHans = {
|
||||
'选择此机器人收到不同事件时交给哪个处理器。具体处理逻辑在 Agent 页面编辑,Pipeline 仅支持消息事件。',
|
||||
eventBindings: '事件路由',
|
||||
addEventBinding: '添加路由',
|
||||
addBehavior: '添加行为',
|
||||
behaviorReplyMessages: '回复收到的消息',
|
||||
behaviorReplyMessagesDescription:
|
||||
'把收到的消息交给 Agent 或 Pipeline 处理。',
|
||||
behaviorWelcomeMembers: '欢迎新成员',
|
||||
behaviorWelcomeMembersDescription: '有人加入群组时运行 Agent。',
|
||||
behaviorHandleDepartures: '处理成员离群',
|
||||
behaviorHandleDeparturesDescription: '有人离开或被移出群组时运行 Agent。',
|
||||
behaviorReviewFriendRequests: '审核好友请求',
|
||||
behaviorReviewFriendRequestsDescription:
|
||||
'让 Agent 决定如何处理新的好友请求。',
|
||||
behaviorHandleModeration: '处理群管理事件',
|
||||
behaviorHandleModerationDescription: '群成员受到限制时运行 Agent。',
|
||||
behaviorCustom: '配置其他事件',
|
||||
behaviorCustomDescription: '添加路由,并从此适配器支持的全部事件中选择。',
|
||||
eventPattern: '事件',
|
||||
eventPatternPlaceholder: '选择事件',
|
||||
targetType: '目标类型',
|
||||
|
||||
Reference in New Issue
Block a user