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