mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-28 13:17:14 +00:00
refactor(agents): polish processor creation layout
This commit is contained in:
@@ -113,84 +113,153 @@ export default function AgentCreateContent({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
<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"
|
||||
>
|
||||
{t('agents.chooseType')}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t('agents.chooseTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mx-auto max-w-6xl pb-6">
|
||||
<div className="grid items-stretch gap-5 lg:grid-cols-[minmax(340px,0.78fr)_minmax(0,1.22fr)]">
|
||||
<div className="space-y-5">
|
||||
<section
|
||||
aria-labelledby="processor-kind-heading"
|
||||
className="space-y-3"
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
id="processor-kind-heading"
|
||||
className="text-base font-semibold"
|
||||
>
|
||||
{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',
|
||||
)}
|
||||
<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-32 w-full items-start justify-start whitespace-normal rounded-xl p-4 text-left hover:bg-transparent"
|
||||
>
|
||||
<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
|
||||
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',
|
||||
)}
|
||||
</span>
|
||||
<Badge
|
||||
variant={selected ? 'default' : 'secondary'}
|
||||
className="font-normal"
|
||||
>
|
||||
{option.badge}
|
||||
</Badge>
|
||||
<span className="block text-sm leading-relaxed text-muted-foreground">
|
||||
{option.description}
|
||||
<Icon className="size-5" />
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
<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">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.basicInfo')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.basicInfoDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="agent-create-form"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex gap-4 items-start">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>
|
||||
{t('common.name')}
|
||||
<span className="text-destructive">*</span>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="emoji"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.icon')}</FormLabel>
|
||||
<FormControl>
|
||||
<EmojiPicker
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="min-h-[600px] overflow-hidden py-0 lg:min-h-[680px]">
|
||||
<CardHeader className="border-b bg-muted/20 py-5">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
@@ -210,7 +279,7 @@ export default function AgentCreateContent({
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center p-4 sm:p-6">
|
||||
<CardContent className="flex flex-1 items-center p-3 sm:p-5">
|
||||
<ProcessorTypeDiagram kind={kind} />
|
||||
</CardContent>
|
||||
<CardFooter className="border-t bg-muted/20 px-6 py-4 text-sm leading-relaxed text-muted-foreground">
|
||||
@@ -220,73 +289,6 @@ export default function AgentCreateContent({
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.basicInfo')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.basicInfoDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="agent-create-form"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex gap-4 items-start">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>
|
||||
{t('common.name')}
|
||||
<span className="text-destructive">*</span>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="emoji"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.icon')}</FormLabel>
|
||||
<FormControl>
|
||||
<EmojiPicker
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,29 +2,81 @@ import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { AgentKind } from '@/app/infra/entities/api';
|
||||
|
||||
function DiagramArrow({ id }: { id: string }) {
|
||||
type DiagramIds = {
|
||||
accent: string;
|
||||
arrow: string;
|
||||
glow: string;
|
||||
grid: string;
|
||||
surface: string;
|
||||
};
|
||||
|
||||
function useDiagramIds(prefix: string): DiagramIds {
|
||||
const id = useId().replace(/:/g, '');
|
||||
return {
|
||||
accent: `${prefix}-accent-${id}`,
|
||||
arrow: `${prefix}-arrow-${id}`,
|
||||
glow: `${prefix}-glow-${id}`,
|
||||
grid: `${prefix}-grid-${id}`,
|
||||
surface: `${prefix}-surface-${id}`,
|
||||
};
|
||||
}
|
||||
|
||||
function DiagramDefinitions({ ids }: { ids: DiagramIds }) {
|
||||
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>
|
||||
<defs>
|
||||
<linearGradient id={ids.surface} x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stopColor="var(--primary)" stopOpacity="0.12" />
|
||||
<stop offset="0.48" stopColor="var(--card)" stopOpacity="0.72" />
|
||||
<stop offset="1" stopColor="var(--chart-2)" stopOpacity="0.08" />
|
||||
</linearGradient>
|
||||
<linearGradient id={ids.accent} x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stopColor="var(--primary)" />
|
||||
<stop offset="1" stopColor="var(--chart-2)" />
|
||||
</linearGradient>
|
||||
<filter id={ids.glow} x="-80%" y="-80%" width="260%" height="260%">
|
||||
<feGaussianBlur stdDeviation="9" result="blur" />
|
||||
<feComposite in="blur" in2="SourceGraphic" operator="over" />
|
||||
</filter>
|
||||
<pattern
|
||||
id={ids.grid}
|
||||
width="28"
|
||||
height="28"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="var(--border)" opacity="0.72" />
|
||||
</pattern>
|
||||
<marker
|
||||
id={ids.arrow}
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
refX="8"
|
||||
refY="5"
|
||||
orient="auto"
|
||||
markerUnits="strokeWidth"
|
||||
>
|
||||
<path d="M0 0 10 5 0 10Z" fill="var(--primary)" />
|
||||
</marker>
|
||||
</defs>
|
||||
);
|
||||
}
|
||||
|
||||
function AgentDiagram() {
|
||||
const { t } = useTranslation();
|
||||
const arrowId = `agent-arrow-${useId().replace(/:/g, '')}`;
|
||||
const ids = useDiagramIds('agent');
|
||||
const events = [
|
||||
{ label: t('agents.diagramMessages'), y: 154 },
|
||||
{ label: t('agents.diagramMembers'), y: 270 },
|
||||
{ label: t('agents.diagramFeedback'), y: 386 },
|
||||
];
|
||||
const capabilities = [
|
||||
{ label: t('agents.diagramModel'), y: 154 },
|
||||
{ label: t('agents.diagramTools'), y: 270 },
|
||||
{ label: t('agents.diagramActions'), y: 386 },
|
||||
];
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 720 340"
|
||||
viewBox="0 0 760 600"
|
||||
role="img"
|
||||
aria-label={t('agents.agentDiagramTitle')}
|
||||
className="h-auto w-full"
|
||||
@@ -32,205 +84,258 @@ function AgentDiagram() {
|
||||
>
|
||||
<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>
|
||||
<DiagramDefinitions ids={ids} />
|
||||
|
||||
<rect
|
||||
x="24"
|
||||
y="42"
|
||||
width="184"
|
||||
height="256"
|
||||
rx="22"
|
||||
fill="var(--muted)"
|
||||
fillOpacity="0.48"
|
||||
x="10"
|
||||
y="16"
|
||||
width="740"
|
||||
height="568"
|
||||
rx="34"
|
||||
fill={`url(#${ids.surface})`}
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="16"
|
||||
width="740"
|
||||
height="568"
|
||||
rx="34"
|
||||
fill={`url(#${ids.grid})`}
|
||||
opacity="0.48"
|
||||
/>
|
||||
<circle cx="650" cy="94" r="80" fill="var(--primary)" opacity="0.035" />
|
||||
<circle cx="105" cy="510" r="104" fill="var(--chart-2)" opacity="0.045" />
|
||||
|
||||
<g fill="none" stroke="var(--primary)" strokeWidth="2">
|
||||
{events.map((event) => (
|
||||
<path
|
||||
key={event.label}
|
||||
d={`M226 ${event.y + 30} C286 ${event.y + 30}, 280 300, 323 300`}
|
||||
opacity="0.42"
|
||||
/>
|
||||
))}
|
||||
{capabilities.map((capability) => (
|
||||
<path
|
||||
key={capability.label}
|
||||
d={`M437 300 C480 300, 474 ${capability.y + 30}, 534 ${capability.y + 30}`}
|
||||
opacity="0.42"
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
|
||||
<text
|
||||
x="116"
|
||||
y="76"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="16"
|
||||
x="54"
|
||||
y="112"
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="600"
|
||||
letterSpacing="1.8"
|
||||
>
|
||||
{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}>
|
||||
{events.map((event, index) => (
|
||||
<g key={event.label}>
|
||||
<rect
|
||||
x="38"
|
||||
y={item.y}
|
||||
width="156"
|
||||
height="48"
|
||||
rx="13"
|
||||
x="46"
|
||||
y={event.y}
|
||||
width="180"
|
||||
height="60"
|
||||
rx="18"
|
||||
fill="var(--card)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<circle
|
||||
cx="78"
|
||||
cy={event.y + 30}
|
||||
r="13"
|
||||
fill="var(--primary)"
|
||||
opacity={0.09 + index * 0.045}
|
||||
/>
|
||||
{index === 0 && (
|
||||
<path
|
||||
d={`M69 ${event.y + 24}h18v12H76l-5 4v-4h-2z`}
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="1.8"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
)}
|
||||
{index === 1 && (
|
||||
<g fill="none" stroke="var(--primary)" strokeWidth="1.8">
|
||||
<circle cx="75" cy={event.y + 26} r="4" />
|
||||
<circle cx="83" cy={event.y + 29} r="3.5" />
|
||||
<path d={`M68 ${event.y + 40}c1-6 4-9 8-9s7 3 8 9`} />
|
||||
</g>
|
||||
)}
|
||||
{index === 2 && (
|
||||
<path
|
||||
d={`m78 ${event.y + 20} 3 6 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1z`}
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="1.8"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
)}
|
||||
<text
|
||||
x="104"
|
||||
y={event.y + 35}
|
||||
fill="var(--foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="600"
|
||||
>
|
||||
{event.label}
|
||||
</text>
|
||||
<circle cx="226" cy={event.y + 30} r="3.5" fill="var(--primary)" />
|
||||
</g>
|
||||
))}
|
||||
|
||||
<g>
|
||||
<circle
|
||||
cx="380"
|
||||
cy="300"
|
||||
r="122"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeDasharray="4 12"
|
||||
opacity="0.22"
|
||||
/>
|
||||
<circle
|
||||
cx="380"
|
||||
cy="300"
|
||||
r="96"
|
||||
fill="var(--primary)"
|
||||
opacity="0.08"
|
||||
filter={`url(#${ids.glow})`}
|
||||
/>
|
||||
<circle
|
||||
cx="380"
|
||||
cy="300"
|
||||
r="82"
|
||||
fill={`url(#${ids.accent})`}
|
||||
opacity="0.96"
|
||||
/>
|
||||
<circle cx="347" cy="193" r="7" fill="var(--chart-2)" />
|
||||
<circle cx="466" cy="329" r="5" fill="var(--primary)" />
|
||||
<circle cx="314" cy="385" r="4" fill="var(--chart-4)" />
|
||||
<rect
|
||||
x="348"
|
||||
y="250"
|
||||
width="64"
|
||||
height="48"
|
||||
rx="16"
|
||||
fill="var(--primary-foreground)"
|
||||
opacity="0.96"
|
||||
/>
|
||||
<circle cx="365" cy="273" r="4" fill="var(--primary)" />
|
||||
<circle cx="395" cy="273" r="4" fill="var(--primary)" />
|
||||
<path
|
||||
d="M364 285c8 7 24 7 32 0M380 250v-12m-7 0h14"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<text
|
||||
x="380"
|
||||
y="329"
|
||||
textAnchor="middle"
|
||||
fill="var(--primary-foreground)"
|
||||
fontSize="20"
|
||||
fontWeight="750"
|
||||
>
|
||||
{t('agents.agentType')}
|
||||
</text>
|
||||
<text
|
||||
x="380"
|
||||
y="352"
|
||||
textAnchor="middle"
|
||||
fill="var(--primary-foreground)"
|
||||
fontSize="11"
|
||||
opacity="0.82"
|
||||
>
|
||||
{t('agents.diagramDecide')}
|
||||
</text>
|
||||
</g>
|
||||
|
||||
{capabilities.map((capability, index) => (
|
||||
<g key={capability.label}>
|
||||
<circle
|
||||
cx="534"
|
||||
cy={capability.y + 30}
|
||||
r="3.5"
|
||||
fill="var(--primary)"
|
||||
/>
|
||||
<rect
|
||||
x="534"
|
||||
y={capability.y}
|
||||
width="180"
|
||||
height="60"
|
||||
rx="18"
|
||||
fill="var(--card)"
|
||||
stroke={index === 1 ? 'var(--primary)' : 'var(--border)'}
|
||||
strokeWidth={index === 1 ? 1.5 : 1}
|
||||
/>
|
||||
<circle
|
||||
cx="566"
|
||||
cy={capability.y + 30}
|
||||
r="13"
|
||||
fill="var(--primary)"
|
||||
opacity={index === 1 ? 0.18 : 0.09}
|
||||
/>
|
||||
<path
|
||||
d={item.icon}
|
||||
transform={`translate(0 ${item.y - 92})`}
|
||||
d={
|
||||
index === 0
|
||||
? `M560 ${capability.y + 30}h12m-6-6v12`
|
||||
: index === 1
|
||||
? `M560 ${capability.y + 35}l12-12m-8 0h8v8`
|
||||
: `M560 ${capability.y + 30}h12m-4-5 5 5-5 5`
|
||||
}
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeWidth="1.8"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<text
|
||||
x="120"
|
||||
y={item.y + 30}
|
||||
textAnchor="middle"
|
||||
x="592"
|
||||
y={capability.y + 35}
|
||||
fill="var(--foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="500"
|
||||
fontWeight="600"
|
||||
>
|
||||
{item.label}
|
||||
{capability.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"
|
||||
d="M704 494c-88 46-246 63-388 31"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="1.5"
|
||||
strokeDasharray="2 8"
|
||||
opacity="0.24"
|
||||
markerEnd={`url(#${ids.arrow})`}
|
||||
/>
|
||||
<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'),
|
||||
const ids = useDiagramIds('pipeline');
|
||||
const stations = [
|
||||
{ x: 198, y: 312, label: t('agents.diagramPreprocess') },
|
||||
{ x: 338, y: 242, label: t('agents.diagramAI') },
|
||||
{ x: 478, y: 312, label: t('agents.diagramPostprocess') },
|
||||
{ x: 618, y: 242, label: t('agents.diagramOutput') },
|
||||
];
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 720 340"
|
||||
viewBox="0 0 760 600"
|
||||
role="img"
|
||||
aria-label={t('agents.pipelineDiagramTitle')}
|
||||
className="h-auto w-full"
|
||||
@@ -238,123 +343,168 @@ function PipelineDiagram() {
|
||||
>
|
||||
<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>
|
||||
<DiagramDefinitions ids={ids} />
|
||||
|
||||
<rect
|
||||
x="24"
|
||||
y="65"
|
||||
width="672"
|
||||
height="210"
|
||||
rx="28"
|
||||
fill="url(#pipeline-flow)"
|
||||
x="10"
|
||||
y="16"
|
||||
width="740"
|
||||
height="568"
|
||||
rx="34"
|
||||
fill={`url(#${ids.surface})`}
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="16"
|
||||
width="740"
|
||||
height="568"
|
||||
rx="34"
|
||||
fill={`url(#${ids.grid})`}
|
||||
opacity="0.48"
|
||||
/>
|
||||
<circle cx="640" cy="110" r="112" fill="var(--chart-2)" opacity="0.045" />
|
||||
<circle cx="105" cy="500" r="96" fill="var(--primary)" opacity="0.045" />
|
||||
|
||||
<text
|
||||
x="360"
|
||||
y="104"
|
||||
x="380"
|
||||
y="105"
|
||||
textAnchor="middle"
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize="14"
|
||||
fontWeight="500"
|
||||
fontSize="13"
|
||||
fontWeight="600"
|
||||
letterSpacing="1.5"
|
||||
>
|
||||
{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"
|
||||
d="M72 360 C128 360 146 312 198 312 S286 242 338 242 S426 312 478 312 S566 242 618 242 S674 286 704 286"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="28"
|
||||
strokeLinecap="round"
|
||||
opacity="0.06"
|
||||
/>
|
||||
<text
|
||||
x="92"
|
||||
y="228"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="500"
|
||||
>
|
||||
{t('agents.diagramMessage')}
|
||||
</text>
|
||||
|
||||
<path
|
||||
d="M144 171H168"
|
||||
d="M72 360 C128 360 146 312 198 312 S286 242 338 242 S426 312 478 312 S566 242 618 242 S674 286 704 286"
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
stroke={`url(#${ids.accent})`}
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
markerEnd={`url(#${ids.arrow})`}
|
||||
/>
|
||||
<path
|
||||
d="M92 405 C180 486 314 478 380 430 S548 388 678 430"
|
||||
fill="none"
|
||||
stroke="var(--chart-2)"
|
||||
strokeWidth="1.5"
|
||||
strokeDasharray="3 10"
|
||||
opacity="0.22"
|
||||
/>
|
||||
|
||||
{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}
|
||||
/>
|
||||
<g>
|
||||
<circle
|
||||
cx="72"
|
||||
cy="360"
|
||||
r="42"
|
||||
fill="var(--card)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<path
|
||||
d="M54 348h36v24H67l-9 8v-8h-4z"
|
||||
fill="none"
|
||||
stroke="var(--primary)"
|
||||
strokeWidth="2.5"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<text
|
||||
x="72"
|
||||
y="424"
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="600"
|
||||
>
|
||||
{t('agents.diagramMessage')}
|
||||
</text>
|
||||
</g>
|
||||
|
||||
{stations.map((station, index) => (
|
||||
<g key={station.label}>
|
||||
<circle
|
||||
cx={station.x}
|
||||
cy={station.y}
|
||||
r={index === 1 ? 58 : 50}
|
||||
fill="var(--primary)"
|
||||
opacity={index === 1 ? 0.12 : 0.07}
|
||||
filter={index === 1 ? `url(#${ids.glow})` : undefined}
|
||||
/>
|
||||
<circle
|
||||
cx={station.x}
|
||||
cy={station.y}
|
||||
r={index === 1 ? 45 : 39}
|
||||
fill={index === 1 ? `url(#${ids.accent})` : 'var(--card)'}
|
||||
stroke={index === 1 ? 'var(--primary)' : 'var(--border)'}
|
||||
strokeWidth={index === 1 ? 2 : 1}
|
||||
/>
|
||||
<text
|
||||
x={station.x}
|
||||
y={station.y + 6}
|
||||
textAnchor="middle"
|
||||
fill={index === 1 ? 'var(--primary-foreground)' : 'var(--primary)'}
|
||||
fontSize="17"
|
||||
fontWeight="750"
|
||||
>
|
||||
{index + 1}
|
||||
</text>
|
||||
<rect
|
||||
x={station.x - 61}
|
||||
y={station.y + (index % 2 === 0 ? 66 : -91)}
|
||||
width="122"
|
||||
height="38"
|
||||
rx="14"
|
||||
fill="var(--card)"
|
||||
stroke="var(--border)"
|
||||
/>
|
||||
<text
|
||||
x={station.x}
|
||||
y={station.y + (index % 2 === 0 ? 90 : -67)}
|
||||
textAnchor="middle"
|
||||
fill="var(--foreground)"
|
||||
fontSize="13"
|
||||
fontWeight="600"
|
||||
>
|
||||
{station.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
<g opacity="0.7">
|
||||
<circle cx="704" cy="286" r="12" fill="var(--primary)" opacity="0.14" />
|
||||
<circle cx="704" cy="286" r="4" fill="var(--primary)" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(178 494)">
|
||||
{[0, 1, 2, 3].map((step) => (
|
||||
<g key={step} transform={`translate(${step * 106} 0)`}>
|
||||
<circle
|
||||
cx={x + 52}
|
||||
cy="154"
|
||||
r="14"
|
||||
fill="var(--primary)"
|
||||
fillOpacity={index === 1 ? 0.22 : 0.1}
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="5"
|
||||
fill={step === 1 ? 'var(--primary)' : 'var(--border)'}
|
||||
/>
|
||||
<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 && (
|
||||
{step < 3 && (
|
||||
<path
|
||||
d={`M${x + 104} 171H${x + 122}`}
|
||||
fill="none"
|
||||
stroke="var(--muted-foreground)"
|
||||
d="M10 0h86"
|
||||
stroke="var(--border)"
|
||||
strokeWidth="2"
|
||||
markerEnd={`url(#${arrowId})`}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user