diff --git a/web/src/app/home/agents/components/AgentCreateContent.tsx b/web/src/app/home/agents/components/AgentCreateContent.tsx index d9cdd4962..aa2c8cd88 100644 --- a/web/src/app/home/agents/components/AgentCreateContent.tsx +++ b/web/src/app/home/agents/components/AgentCreateContent.tsx @@ -5,15 +5,17 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; -import { Bot, Workflow } from 'lucide-react'; +import { Bot, CheckCircle2, Workflow } from 'lucide-react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { AgentKind } from '@/app/infra/entities/api'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardDescription, + CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; @@ -27,6 +29,7 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import EmojiPicker from '@/components/ui/emoji-picker'; +import ProcessorTypeDiagram from './ProcessorTypeDiagram'; export default function AgentCreateContent({ onCreated, @@ -110,38 +113,112 @@ export default function AgentCreateContent({
-
-
- {typeOptions.map((option) => { - const Icon = option.icon; - const selected = kind === option.kind; - return ( - + + + ); + })} +
+ + + + +
+
+ + {kind === 'agent' + ? t('agents.agentDiagramTitle') + : t('agents.pipelineDiagramTitle')} + + + {t('agents.diagramHint')} +
- - ); - })} + + {kind === 'agent' + ? t('agents.kindBadgeAgent') + : t('agents.kindBadgePipeline')} + +
+
+ + + + + {kind === 'agent' + ? t('agents.agentDiagramDescription') + : t('agents.pipelineDiagramDescription')} + +
diff --git a/web/src/app/home/agents/components/ProcessorTypeDiagram.tsx b/web/src/app/home/agents/components/ProcessorTypeDiagram.tsx new file mode 100644 index 000000000..7441dd8d1 --- /dev/null +++ b/web/src/app/home/agents/components/ProcessorTypeDiagram.tsx @@ -0,0 +1,364 @@ +import { useId } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { AgentKind } from '@/app/infra/entities/api'; + +function DiagramArrow({ id }: { id: string }) { + return ( + + + + ); +} + +function AgentDiagram() { + const { t } = useTranslation(); + const arrowId = `agent-arrow-${useId().replace(/:/g, '')}`; + + return ( + + {t('agents.agentDiagramTitle')} + {t('agents.agentDiagramDescription')} + + + + + + + + + + + {t('agents.diagramEvents')} + + + {[ + { + y: 98, + label: t('agents.diagramMessages'), + icon: 'M44 116h12l8 8v-8h28v-24H44z', + }, + { + y: 160, + label: t('agents.diagramMembers'), + icon: 'M51 167a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm-11 14c1-8 6-12 11-12s10 4 11 12m8-22a6 6 0 1 0 0-12m5 28c-1-6-4-9-9-10', + }, + { + y: 222, + label: t('agents.diagramFeedback'), + icon: 'M52 213l4 9 10 1-7 7 2 10-9-5-9 5 2-10-7-7 10-1z', + }, + ].map((item) => ( + + + + + {item.label} + + + ))} + + + + + + + + + + {t('agents.agentType')} + + + {t('agents.diagramDecide')} + + + + {[ + { y: 70, label: t('agents.diagramModel') }, + { y: 145, label: t('agents.diagramTools') }, + { y: 220, label: t('agents.diagramActions') }, + ].map((item, index) => ( + + + + + + {item.label} + + + ))} + + ); +} + +function PipelineDiagram() { + const { t } = useTranslation(); + const arrowId = `pipeline-arrow-${useId().replace(/:/g, '')}`; + const stages = [ + t('agents.diagramPreprocess'), + t('agents.diagramAI'), + t('agents.diagramPostprocess'), + t('agents.diagramOutput'), + ]; + + return ( + + {t('agents.pipelineDiagramTitle')} + {t('agents.pipelineDiagramDescription')} + + + + + + + + + + + + {t('agents.pipelineDiagramFlow')} + + + + + + {t('agents.diagramMessage')} + + + + + {stages.map((stage, index) => { + const x = 178 + index * 128; + return ( + + + + + {index + 1} + + + {stage} + + {index < stages.length - 1 && ( + + )} + + ); + })} + + ); +} + +export default function ProcessorTypeDiagram({ kind }: { kind: AgentKind }) { + return kind === 'agent' ? : ; +} diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 6cb69d4ca..b9d7543f4 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -711,6 +711,30 @@ const enUS = { 'Use a visual multi-stage flow to control message preprocessing, AI, postprocessing, extensions, and output. Message events only.', allEvents: 'Supports all events', messageEventsOnly: 'Message events only', + chooseType: 'Choose how it works', + chooseTypeDescription: + 'Choose the processor model first. You can configure it further after creation.', + diagramHint: 'How it works', + agentDiagramTitle: 'Agent: decide and act', + agentDiagramDescription: + 'An Agent can receive many platform events, use models and tools to understand the situation, and decide what to do next.', + pipelineDiagramTitle: 'Pipeline: follow a defined flow', + pipelineDiagramDescription: + 'A Pipeline handles message events only and runs each step in the order you configure.', + pipelineDiagramFlow: 'Messages move through a fixed sequence', + diagramEvents: 'Platform events', + diagramMessages: 'Messages', + diagramMembers: 'Members', + diagramFeedback: 'Feedback', + diagramDecide: 'Understand and decide', + diagramModel: 'Use model', + diagramTools: 'Use tools', + diagramActions: 'Take action', + diagramMessage: 'Message', + diagramPreprocess: 'Prepare', + diagramAI: 'AI', + diagramPostprocess: 'Refine', + diagramOutput: 'Output', basicInfo: 'Basic Information', basicInfoDescription: 'Set the name, icon and description', runnerSettings: 'Runner', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index f6eda5600..328e6a365 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -724,6 +724,30 @@ const jaJP = { 'ビジュアルな多段フローで、メッセージの前処理、AI、後処理、拡張、出力を制御します。メッセージイベントのみ処理できます。', allEvents: 'すべてのイベントに対応', messageEventsOnly: 'メッセージイベントのみ', + chooseType: '処理方法を選択', + chooseTypeDescription: + '最初にプロセッサーの動作方式を選びます。作成後に詳しく設定できます。', + diagramHint: '動作イメージ', + agentDiagramTitle: 'Agent:判断して行動', + agentDiagramDescription: + 'Agent はさまざまなプラットフォームイベントを受け取り、モデルやツールを使って状況を理解し、次の行動を判断します。', + pipelineDiagramTitle: 'Pipeline:決めたフローで処理', + pipelineDiagramDescription: + 'Pipeline はメッセージイベントのみを、設定した固定ステップの順番で処理します。', + pipelineDiagramFlow: 'メッセージを固定フローで順番に処理', + diagramEvents: 'イベント', + diagramMessages: 'メッセージ', + diagramMembers: 'メンバー', + diagramFeedback: 'フィードバック', + diagramDecide: '理解して判断', + diagramModel: 'モデル', + diagramTools: 'ツール', + diagramActions: 'アクション', + diagramMessage: 'メッセージ', + diagramPreprocess: '前処理', + diagramAI: 'AI', + diagramPostprocess: '後処理', + diagramOutput: '出力', basicInfo: '基本情報', basicInfoDescription: '名前、アイコン、説明を設定します', runnerSettings: 'Runner', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index c946fce82..9c2ba0315 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -680,6 +680,29 @@ const zhHans = { '通过可视化多阶段流程控制消息的预处理、AI、后处理、扩展和输出;仅处理消息事件。', allEvents: '支持全部事件', messageEventsOnly: '仅支持消息事件', + chooseType: '选择处理方式', + chooseTypeDescription: '先选择处理器的工作方式,创建后可继续完善配置。', + diagramHint: '工作方式示意', + agentDiagramTitle: 'Agent:自主判断并行动', + agentDiagramDescription: + 'Agent 可以接收多种平台事件,结合模型和工具理解当前情况,并自主决定下一步动作。', + pipelineDiagramTitle: 'Pipeline:按流程逐步处理', + pipelineDiagramDescription: + 'Pipeline 只处理消息事件,并严格按照你编排的固定步骤依次执行。', + pipelineDiagramFlow: '消息沿固定流程依次处理', + diagramEvents: '平台事件', + diagramMessages: '消息', + diagramMembers: '群成员', + diagramFeedback: '反馈', + diagramDecide: '理解事件并决定下一步', + diagramModel: '调用模型', + diagramTools: '使用工具', + diagramActions: '执行动作', + diagramMessage: '收到消息', + diagramPreprocess: '预处理', + diagramAI: 'AI 处理', + diagramPostprocess: '后处理', + diagramOutput: '输出', basicInfo: '基础信息', basicInfoDescription: '设置名称、图标和描述', runnerSettings: '运行器', diff --git a/web/tests/e2e/crud-smoke.spec.ts b/web/tests/e2e/crud-smoke.spec.ts index 556179d9a..5b1495d48 100644 --- a/web/tests/e2e/crud-smoke.spec.ts +++ b/web/tests/e2e/crud-smoke.spec.ts @@ -171,7 +171,22 @@ test.describe('frontend CRUD smoke flows', () => { await installLangBotApiMocks(page, { authenticated: true }); await page.goto('/home/agents?id=new'); + const agentTypeCard = page.locator('[data-processor-kind="agent"]'); + const pipelineTypeCard = page.locator('[data-processor-kind="pipeline"]'); + await expect(agentTypeCard).toHaveAttribute('data-slot', 'card'); + await expect(pipelineTypeCard).toHaveAttribute('data-slot', 'card'); + await expect( + agentTypeCard.getByRole('button', { name: /^Agent/ }), + ).toHaveAttribute('aria-pressed', 'true'); + await expect(page.getByTestId('agent-diagram')).toBeVisible(); + await expect(page.getByTestId('pipeline-diagram')).toHaveCount(0); + await page.getByRole('button', { name: /^Pipeline/ }).click(); + await expect( + pipelineTypeCard.getByRole('button', { name: /^Pipeline/ }), + ).toHaveAttribute('aria-pressed', 'true'); + await expect(page.getByTestId('pipeline-diagram')).toBeVisible(); + await expect(page.getByTestId('agent-diagram')).toHaveCount(0); await expect(page.locator('input[name="name"]')).toBeVisible(); await page.locator('input[name="name"]').fill('Escalation Pipeline');