mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-28 13:17:14 +00:00
feat(agents): improve processor creation choice
This commit is contained in:
@@ -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({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
<div className="mx-auto max-w-2xl space-y-6">
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{typeOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
const selected = kind === option.kind;
|
||||
return (
|
||||
<button
|
||||
key={option.kind}
|
||||
type="button"
|
||||
onClick={() => handleKindChange(option.kind)}
|
||||
className={cn(
|
||||
'rounded-lg border bg-card p-4 text-left transition-colors',
|
||||
selected
|
||||
? 'border-primary ring-2 ring-primary/20'
|
||||
: 'hover:border-primary/60',
|
||||
)}
|
||||
<div className="mx-auto max-w-5xl space-y-6 pb-6">
|
||||
<div className="grid items-stretch gap-5 lg:grid-cols-[minmax(0,0.82fr)_minmax(0,1.18fr)]">
|
||||
<section
|
||||
aria-labelledby="processor-kind-heading"
|
||||
className="space-y-3"
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
id="processor-kind-heading"
|
||||
className="text-base font-semibold"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Icon className="mt-0.5 size-5 text-blue-500" />
|
||||
<div className="space-y-1">
|
||||
<div className="font-medium">{option.title}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{option.badge}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{option.description}
|
||||
</p>
|
||||
</div>
|
||||
{t('agents.chooseType')}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t('agents.chooseTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-1">
|
||||
{typeOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
const selected = kind === option.kind;
|
||||
return (
|
||||
<Card
|
||||
key={option.kind}
|
||||
data-processor-kind={option.kind}
|
||||
className={cn(
|
||||
'gap-0 py-0 transition-[border-color,box-shadow,background-color]',
|
||||
selected
|
||||
? 'border-primary bg-primary/[0.035] shadow-sm ring-1 ring-primary/20'
|
||||
: 'hover:border-primary/50',
|
||||
)}
|
||||
>
|
||||
<CardContent className="h-full p-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
aria-pressed={selected}
|
||||
onClick={() => handleKindChange(option.kind)}
|
||||
className="h-full min-h-36 w-full items-start justify-start whitespace-normal rounded-xl p-4 text-left hover:bg-transparent"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'flex size-10 shrink-0 items-center justify-center rounded-lg border bg-background',
|
||||
selected &&
|
||||
'border-primary/30 bg-primary/10 text-primary',
|
||||
)}
|
||||
>
|
||||
<Icon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 space-y-2">
|
||||
<span className="flex items-center justify-between gap-3">
|
||||
<span className="font-semibold">
|
||||
{option.title}
|
||||
</span>
|
||||
{selected && (
|
||||
<CheckCircle2 className="size-4 shrink-0 text-primary" />
|
||||
)}
|
||||
</span>
|
||||
<Badge
|
||||
variant={selected ? 'default' : 'secondary'}
|
||||
className="font-normal"
|
||||
>
|
||||
{option.badge}
|
||||
</Badge>
|
||||
<span className="block text-sm leading-relaxed text-muted-foreground">
|
||||
{option.description}
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Card className="min-h-[360px] overflow-hidden py-0">
|
||||
<CardHeader className="border-b bg-muted/20 py-5">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{kind === 'agent'
|
||||
? t('agents.agentDiagramTitle')
|
||||
: t('agents.pipelineDiagramTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-1.5">
|
||||
{t('agents.diagramHint')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<Badge variant="outline">
|
||||
{kind === 'agent'
|
||||
? t('agents.kindBadgeAgent')
|
||||
: t('agents.kindBadgePipeline')}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center p-4 sm:p-6">
|
||||
<ProcessorTypeDiagram kind={kind} />
|
||||
</CardContent>
|
||||
<CardFooter className="border-t bg-muted/20 px-6 py-4 text-sm leading-relaxed text-muted-foreground">
|
||||
{kind === 'agent'
|
||||
? t('agents.agentDiagramDescription')
|
||||
: t('agents.pipelineDiagramDescription')}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
|
||||
@@ -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 (
|
||||
<marker
|
||||
id={id}
|
||||
markerWidth="8"
|
||||
markerHeight="8"
|
||||
refX="7"
|
||||
refY="4"
|
||||
orient="auto"
|
||||
markerUnits="strokeWidth"
|
||||
>
|
||||
<path d="M0,0 L8,4 L0,8 Z" fill="var(--muted-foreground)" />
|
||||
</marker>
|
||||
);
|
||||
}
|
||||
|
||||
function AgentDiagram() {
|
||||
const { t } = useTranslation();
|
||||
const arrowId = `agent-arrow-${useId().replace(/:/g, '')}`;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 720 340"
|
||||
role="img"
|
||||
aria-label={t('agents.agentDiagramTitle')}
|
||||
className="h-auto w-full"
|
||||
data-testid="agent-diagram"
|
||||
>
|
||||
<title>{t('agents.agentDiagramTitle')}</title>
|
||||
<desc>{t('agents.agentDiagramDescription')}</desc>
|
||||
<defs>
|
||||
<linearGradient id="agent-core" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stopColor="var(--primary)" stopOpacity="0.22" />
|
||||
<stop offset="1" stopColor="var(--primary)" stopOpacity="0.06" />
|
||||
</linearGradient>
|
||||
<DiagramArrow id={arrowId} />
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
x="24"
|
||||
y="42"
|
||||
width="184"
|
||||
height="256"
|
||||
rx="22"
|
||||
fill="var(--muted)"
|
||||
fillOpacity="0.48"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<text
|
||||
x="116"
|
||||
y="76"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="16"
|
||||
fontWeight="600"
|
||||
>
|
||||
{t('agents.diagramEvents')}
|
||||
</text>
|
||||
|
||||
{[
|
||||
{
|
||||
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) => (
|
||||
<g key={item.label}>
|
||||
<rect
|
||||
x="38"
|
||||
y={item.y}
|
||||
width="156"
|
||||
height="48"
|
||||
rx="13"
|
||||
fill="var(--card)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<path
|
||||
d={item.icon}
|
||||
transform={`translate(0 ${item.y - 92})`}
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<text
|
||||
x="120"
|
||||
y={item.y + 30}
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="500"
|
||||
>
|
||||
{item.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
<path
|
||||
d="M208 170H270"
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
/>
|
||||
|
||||
<rect
|
||||
x="284"
|
||||
y="82"
|
||||
width="164"
|
||||
height="176"
|
||||
rx="30"
|
||||
fill="url(#agent-core)"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<rect
|
||||
x="331"
|
||||
y="112"
|
||||
width="70"
|
||||
height="54"
|
||||
rx="16"
|
||||
fill="var(--card)"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<circle cx="350" cy="139" r="5" fill="var(--primary)" />
|
||||
<circle cx="382" cy="139" r="5" fill="var(--primary)" />
|
||||
<path
|
||||
d="M350 153c8 7 24 7 32 0M366 112V99m-8 0h16"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<text
|
||||
x="366"
|
||||
y="202"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="18"
|
||||
fontWeight="700"
|
||||
>
|
||||
{t('agents.agentType')}
|
||||
</text>
|
||||
<text
|
||||
x="366"
|
||||
y="228"
|
||||
textAnchor="middle"
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize="13"
|
||||
>
|
||||
{t('agents.diagramDecide')}
|
||||
</text>
|
||||
|
||||
<path
|
||||
d="M448 170H506"
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
/>
|
||||
{[
|
||||
{ y: 70, label: t('agents.diagramModel') },
|
||||
{ y: 145, label: t('agents.diagramTools') },
|
||||
{ y: 220, label: t('agents.diagramActions') },
|
||||
].map((item, index) => (
|
||||
<g key={item.label}>
|
||||
<path
|
||||
d={`M476 170 C500 170 492 ${item.y + 26} 516 ${item.y + 26}`}
|
||||
fill="none"
|
||||
stroke="var(--border)"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<rect
|
||||
x="516"
|
||||
y={item.y}
|
||||
width="176"
|
||||
height="52"
|
||||
rx="15"
|
||||
fill="var(--card)"
|
||||
stroke={index === 1 ? 'var(--primary)' : 'var(--border)'}
|
||||
/>
|
||||
<circle
|
||||
cx="544"
|
||||
cy={item.y + 26}
|
||||
r="10"
|
||||
fill="var(--primary)"
|
||||
fillOpacity={index === 1 ? 0.22 : 0.1}
|
||||
/>
|
||||
<text
|
||||
x="608"
|
||||
y={item.y + 31}
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="500"
|
||||
>
|
||||
{item.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<svg
|
||||
viewBox="0 0 720 340"
|
||||
role="img"
|
||||
aria-label={t('agents.pipelineDiagramTitle')}
|
||||
className="h-auto w-full"
|
||||
data-testid="pipeline-diagram"
|
||||
>
|
||||
<title>{t('agents.pipelineDiagramTitle')}</title>
|
||||
<desc>{t('agents.pipelineDiagramDescription')}</desc>
|
||||
<defs>
|
||||
<linearGradient id="pipeline-flow" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stopColor="var(--primary)" stopOpacity="0.06" />
|
||||
<stop offset="0.5" stopColor="var(--primary)" stopOpacity="0.18" />
|
||||
<stop offset="1" stopColor="var(--primary)" stopOpacity="0.06" />
|
||||
</linearGradient>
|
||||
<DiagramArrow id={arrowId} />
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
x="24"
|
||||
y="65"
|
||||
width="672"
|
||||
height="210"
|
||||
rx="28"
|
||||
fill="url(#pipeline-flow)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<text
|
||||
x="360"
|
||||
y="104"
|
||||
textAnchor="middle"
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="500"
|
||||
>
|
||||
{t('agents.pipelineDiagramFlow')}
|
||||
</text>
|
||||
|
||||
<rect
|
||||
x="40"
|
||||
y="137"
|
||||
width="104"
|
||||
height="68"
|
||||
rx="18"
|
||||
fill="var(--card)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<path
|
||||
d="M63 156h38v25H76l-9 8v-8h-4z"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<text
|
||||
x="92"
|
||||
y="228"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="500"
|
||||
>
|
||||
{t('agents.diagramMessage')}
|
||||
</text>
|
||||
|
||||
<path
|
||||
d="M144 171H168"
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
/>
|
||||
|
||||
{stages.map((stage, index) => {
|
||||
const x = 178 + index * 128;
|
||||
return (
|
||||
<g key={stage}>
|
||||
<rect
|
||||
x={x}
|
||||
y="127"
|
||||
width="104"
|
||||
height="88"
|
||||
rx="18"
|
||||
fill="var(--card)"
|
||||
stroke={index === 1 ? 'var(--primary)' : 'var(--border)'}
|
||||
strokeWidth={index === 1 ? 2 : 1}
|
||||
/>
|
||||
<circle
|
||||
cx={x + 52}
|
||||
cy="154"
|
||||
r="14"
|
||||
fill="var(--primary)"
|
||||
fillOpacity={index === 1 ? 0.22 : 0.1}
|
||||
/>
|
||||
<text
|
||||
x={x + 52}
|
||||
y="159"
|
||||
textAnchor="middle"
|
||||
fill="var(--primary)"
|
||||
fontSize="13"
|
||||
fontWeight="700"
|
||||
>
|
||||
{index + 1}
|
||||
</text>
|
||||
<text
|
||||
x={x + 52}
|
||||
y="190"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="600"
|
||||
>
|
||||
{stage}
|
||||
</text>
|
||||
{index < stages.length - 1 && (
|
||||
<path
|
||||
d={`M${x + 104} 171H${x + 122}`}
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProcessorTypeDiagram({ kind }: { kind: AgentKind }) {
|
||||
return kind === 'agent' ? <AgentDiagram /> : <PipelineDiagram />;
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '运行器',
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user