mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-26 12:17:14 +00:00
fix(web): simplify route test dialog
This commit is contained in:
@@ -1013,82 +1013,70 @@ function RouteDryRunDialog({
|
||||
{t('bots.testRoute')}
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('bots.dryRunTitle')}</DialogTitle>
|
||||
<DialogDescription>{t('bots.dryRunDescription')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium">
|
||||
{t('bots.dryRunEventType')}
|
||||
</label>
|
||||
<Select value={eventType} onValueChange={setEventType}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{eventOptions.map((event) => (
|
||||
<SelectItem key={event} value={event}>
|
||||
{eventLabel(event, t)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="rounded-md border bg-muted/20 px-3 py-2.5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium">
|
||||
{t('bots.dryRunSampleReady')}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs leading-relaxed text-muted-foreground">
|
||||
{t('bots.dryRunSampleDescription', {
|
||||
event: eventLabel(eventType, t),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 shrink-0 px-2 text-xs"
|
||||
onClick={() => setAdvancedPayloadOpen((value) => !value)}
|
||||
>
|
||||
{advancedPayloadOpen ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{advancedPayloadOpen
|
||||
? t('bots.dryRunHidePayload')
|
||||
: t('bots.dryRunEditPayload')}
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-end">
|
||||
<div className="min-w-0 flex-1 space-y-1.5">
|
||||
<label className="text-sm font-medium">
|
||||
{t('bots.dryRunEventType')}
|
||||
</label>
|
||||
<Select value={eventType} onValueChange={setEventType}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{eventOptions.map((event) => (
|
||||
<SelectItem key={event} value={event}>
|
||||
{eventLabel(event, t)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{advancedPayloadOpen && (
|
||||
<div className="mt-3 space-y-1.5 border-t pt-3">
|
||||
<label className="text-xs font-medium">
|
||||
{t('bots.dryRunPayload')}
|
||||
</label>
|
||||
<Textarea
|
||||
value={payloadText}
|
||||
onChange={(e) => setPayloadText(e.target.value)}
|
||||
className="min-h-[118px] font-mono text-xs"
|
||||
spellCheck={false}
|
||||
placeholder='{"message_text": "hello"}'
|
||||
/>
|
||||
{payloadError ? (
|
||||
<p className="text-xs text-destructive">{payloadError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('bots.dryRunPayloadHint')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-9 shrink-0 self-start px-2 text-xs text-muted-foreground sm:self-auto"
|
||||
onClick={() => setAdvancedPayloadOpen((value) => !value)}
|
||||
>
|
||||
{advancedPayloadOpen ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{advancedPayloadOpen
|
||||
? t('bots.dryRunHidePayload')
|
||||
: t('bots.dryRunEditPayload')}
|
||||
</Button>
|
||||
</div>
|
||||
{advancedPayloadOpen && (
|
||||
<div className="space-y-1.5 rounded-md border bg-muted/20 p-3">
|
||||
<label className="text-xs font-medium">
|
||||
{t('bots.dryRunPayload')}
|
||||
</label>
|
||||
<Textarea
|
||||
value={payloadText}
|
||||
onChange={(e) => setPayloadText(e.target.value)}
|
||||
className="min-h-[110px] font-mono text-xs"
|
||||
spellCheck={false}
|
||||
placeholder='{"message_text": "hello"}'
|
||||
/>
|
||||
{payloadError ? (
|
||||
<p className="text-xs text-destructive">{payloadError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('bots.dryRunPayloadHint')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{runError && (
|
||||
@@ -1188,9 +1176,9 @@ function RouteDryRunDialog({
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Alert className="border-amber-200 bg-amber-50/60 text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-200">
|
||||
<Alert className="border-amber-200 bg-amber-50/60 px-3 py-2 text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-200">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<AlertDescription className="text-xs">
|
||||
{t('bots.routeTestSideEffectWarning')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -469,30 +469,29 @@ const enUS = {
|
||||
runner_failed: 'The Agent runner failed while processing the event.',
|
||||
delivery_failed: 'The processor finished, but delivery failed.',
|
||||
},
|
||||
routeTestAction: 'Run saved route',
|
||||
routeTestAction: 'Run saved configuration',
|
||||
routeTestRunning: 'Running…',
|
||||
routeTestFailed: 'Failed to run the saved route. Try again later.',
|
||||
routeTestDispatched:
|
||||
'The saved route ran successfully. {{count}} platform actions were blocked.',
|
||||
routeTestSideEffectWarning:
|
||||
'Running the saved route executes its processor. Platform messaging actions are blocked, but tools and external services may still have side effects.',
|
||||
dryRunTitle: 'Test event route',
|
||||
'Platform messaging is blocked, but the processor and tools still run.',
|
||||
dryRunTitle: 'Test route',
|
||||
dryRunDescription:
|
||||
'Preview the current form without running a processor, or run the saved route to validate the live configuration.',
|
||||
'Preview the match first. Run the saved configuration only when you need to test the processor.',
|
||||
dryRunEventType: 'Event type',
|
||||
dryRunSampleReady: 'Sample event is ready',
|
||||
dryRunSampleDescription:
|
||||
'LangBot prepared example data for {{event}}. Most route tests can use it as-is.',
|
||||
dryRunEditPayload: 'Edit advanced data',
|
||||
dryRunHidePayload: 'Hide advanced data',
|
||||
dryRunPayload: 'Advanced event data (JSON)',
|
||||
dryRunPayloadHint:
|
||||
'Use simple fields such as message_text, chat_type, and chat_id to test conditions.',
|
||||
dryRunEditPayload: 'Test data',
|
||||
dryRunHidePayload: 'Hide data',
|
||||
dryRunPayload: 'Test data (JSON)',
|
||||
dryRunPayloadHint: 'Use this to test message and conversation conditions.',
|
||||
dryRunPayloadJsonError: 'Enter valid JSON.',
|
||||
dryRunPayloadObjectError: 'Payload must be a JSON object.',
|
||||
dryRunNeedsSavedBot: 'Save the bot before testing routes.',
|
||||
dryRunFailed: 'Route test failed. Try again later.',
|
||||
dryRunAction: 'Preview route',
|
||||
dryRunAction: 'Preview match',
|
||||
dryRunRunning: 'Testing…',
|
||||
dryRunMatched: 'Route matched',
|
||||
dryRunNotMatched: 'No route matched',
|
||||
|
||||
@@ -476,32 +476,31 @@ const jaJP = {
|
||||
runner_failed: 'Agent Runner がイベント処理中に失敗しました。',
|
||||
delivery_failed: '処理は完了しましたが、結果の配信に失敗しました。',
|
||||
},
|
||||
routeTestAction: '保存済みルートを実行',
|
||||
routeTestAction: '保存済み設定を実行',
|
||||
routeTestRunning: '実行中…',
|
||||
routeTestFailed:
|
||||
'保存済みルートの実行に失敗しました。後でもう一度お試しください。',
|
||||
routeTestDispatched:
|
||||
'保存済みルートを実行しました。{{count}} 件のプラットフォーム操作を抑制しました。',
|
||||
routeTestSideEffectWarning:
|
||||
'保存済みルートを実行するとプロセッサーが動作します。プラットフォームのメッセージ操作は抑制されますが、ツールや外部サービスには副作用が生じる場合があります。',
|
||||
dryRunTitle: 'イベントルートをテスト',
|
||||
'プラットフォームへの送信は抑制されますが、プロセッサーとツールは実行されます。',
|
||||
dryRunTitle: 'ルートをテスト',
|
||||
dryRunDescription:
|
||||
'プロセッサーを実行せずに現在のフォームをプレビューするか、保存済みルートを実行して設定を検証します。',
|
||||
'まず一致結果を確認し、必要な場合のみ保存済み設定を実行します。',
|
||||
dryRunEventType: 'イベントタイプ',
|
||||
dryRunSampleReady: 'サンプルイベントを準備しました',
|
||||
dryRunSampleDescription:
|
||||
'LangBot が「{{event}}」用のサンプルデータを準備しました。通常はそのままテストできます。',
|
||||
dryRunEditPayload: '詳細データを編集',
|
||||
dryRunHidePayload: '詳細データを閉じる',
|
||||
dryRunPayload: '詳細イベントデータ(JSON)',
|
||||
dryRunPayloadHint:
|
||||
'message_text、chat_type、chat_id などの簡単なフィールドで条件をテストできます。',
|
||||
dryRunEditPayload: 'テストデータ',
|
||||
dryRunHidePayload: 'データを閉じる',
|
||||
dryRunPayload: 'テストデータ(JSON)',
|
||||
dryRunPayloadHint: 'メッセージや会話の条件テストに使用します。',
|
||||
dryRunPayloadJsonError: '有効な JSON を入力してください。',
|
||||
dryRunPayloadObjectError:
|
||||
'ペイロードは JSON オブジェクトである必要があります。',
|
||||
dryRunNeedsSavedBot: 'ルートをテストする前にボットを保存してください。',
|
||||
dryRunFailed: 'ルートテストに失敗しました。後でもう一度お試しください。',
|
||||
dryRunAction: 'ルートをプレビュー',
|
||||
dryRunAction: '一致を確認',
|
||||
dryRunRunning: 'テスト中…',
|
||||
dryRunMatched: 'ルートに一致しました',
|
||||
dryRunNotMatched: '一致するルートはありません',
|
||||
|
||||
@@ -447,29 +447,26 @@ const zhHans = {
|
||||
runner_failed: 'Agent Runner 处理事件时失败。',
|
||||
delivery_failed: '处理器已完成,但结果投递失败。',
|
||||
},
|
||||
routeTestAction: '运行已保存路由',
|
||||
routeTestAction: '运行已保存配置',
|
||||
routeTestRunning: '运行中…',
|
||||
routeTestFailed: '运行已保存路由失败,请稍后重试。',
|
||||
routeTestDispatched: '已保存路由运行成功,{{count}} 个平台操作已被阻止。',
|
||||
routeTestSideEffectWarning:
|
||||
'运行已保存路由会执行处理器。平台消息操作会被阻止,但工具和外部服务仍可能产生副作用。',
|
||||
dryRunTitle: '测试事件路由',
|
||||
dryRunDescription:
|
||||
'可以先预览当前表单而不运行处理器,也可以运行已保存路由来验证线上配置。',
|
||||
routeTestSideEffectWarning: '平台消息会被拦截,但处理器和工具仍会运行。',
|
||||
dryRunTitle: '测试路由',
|
||||
dryRunDescription: '先预览匹配结果;需要验证处理器时,再运行已保存配置。',
|
||||
dryRunEventType: '事件类型',
|
||||
dryRunSampleReady: '示例事件已准备好',
|
||||
dryRunSampleDescription:
|
||||
'LangBot 已为“{{event}}”准备示例数据,大多数路由测试可直接使用。',
|
||||
dryRunEditPayload: '编辑高级数据',
|
||||
dryRunHidePayload: '收起高级数据',
|
||||
dryRunPayload: '高级事件数据(JSON)',
|
||||
dryRunPayloadHint:
|
||||
'可填写 message_text、chat_type、chat_id 等简单字段,用于匹配触发条件。',
|
||||
dryRunEditPayload: '测试数据',
|
||||
dryRunHidePayload: '收起数据',
|
||||
dryRunPayload: '测试数据(JSON)',
|
||||
dryRunPayloadHint: '用于测试消息内容、会话类型等条件。',
|
||||
dryRunPayloadJsonError: '请输入合法 JSON。',
|
||||
dryRunPayloadObjectError: '载荷必须是 JSON 对象。',
|
||||
dryRunNeedsSavedBot: '请先保存机器人后再测试路由。',
|
||||
dryRunFailed: '路由测试失败,请稍后重试。',
|
||||
dryRunAction: '预览路由',
|
||||
dryRunAction: '预览匹配',
|
||||
dryRunRunning: '测试中…',
|
||||
dryRunMatched: '已命中路由',
|
||||
dryRunNotMatched: '未命中路由',
|
||||
|
||||
@@ -378,6 +378,30 @@ test.describe('bot advanced flows', () => {
|
||||
await expect(
|
||||
page.getByText('Failed to refresh route status.'),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Test route' }).click();
|
||||
const routeDialog = page.getByRole('dialog');
|
||||
await expect(
|
||||
routeDialog.getByText('Test route', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
routeDialog.getByText(
|
||||
'Preview the match first. Run the saved configuration only when you need to test the processor.',
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(routeDialog.getByText('Sample event is ready')).toHaveCount(0);
|
||||
await expect(
|
||||
routeDialog.getByRole('button', { name: 'Test data' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
routeDialog.getByRole('button', { name: 'Preview match' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
routeDialog.getByRole('button', { name: 'Run saved configuration' }),
|
||||
).toBeVisible();
|
||||
const dialogBox = await routeDialog.boundingBox();
|
||||
expect(dialogBox).not.toBeNull();
|
||||
expect(dialogBox!.height).toBeLessThan(500);
|
||||
});
|
||||
|
||||
test('toggles bot enable/disable state', async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user