refactor(agents): polish processor creation layout

This commit is contained in:
RockChinQ
2026-08-27 14:39:56 +08:00
parent 847bfc8df5
commit 6b40c2cf66
2 changed files with 554 additions and 402 deletions
@@ -113,8 +113,9 @@ export default function AgentCreateContent({
</div> </div>
<div className="flex-1 overflow-y-auto min-h-0"> <div className="flex-1 overflow-y-auto min-h-0">
<div className="mx-auto max-w-5xl space-y-6 pb-6"> <div className="mx-auto max-w-6xl pb-6">
<div className="grid items-stretch gap-5 lg:grid-cols-[minmax(0,0.82fr)_minmax(0,1.18fr)]"> <div className="grid items-stretch gap-5 lg:grid-cols-[minmax(340px,0.78fr)_minmax(0,1.22fr)]">
<div className="space-y-5">
<section <section
aria-labelledby="processor-kind-heading" aria-labelledby="processor-kind-heading"
className="space-y-3" className="space-y-3"
@@ -152,7 +153,7 @@ export default function AgentCreateContent({
variant="ghost" variant="ghost"
aria-pressed={selected} aria-pressed={selected}
onClick={() => handleKindChange(option.kind)} 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" className="h-full min-h-32 w-full items-start justify-start whitespace-normal rounded-xl p-4 text-left hover:bg-transparent"
> >
<span <span
className={cn( className={cn(
@@ -190,37 +191,6 @@ export default function AgentCreateContent({
</div> </div>
</section> </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>
<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> <Card>
<CardHeader> <CardHeader>
<CardTitle>{t('agents.basicInfo')}</CardTitle> <CardTitle>{t('agents.basicInfo')}</CardTitle>
@@ -288,6 +258,38 @@ export default function AgentCreateContent({
</CardContent> </CardContent>
</Card> </Card>
</div> </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>
<CardTitle>
{kind === 'agent'
? t('agents.agentDiagramTitle')
: t('agents.pipelineDiagramTitle')}
</CardTitle>
<CardDescription className="mt-1.5">
{t('agents.diagramHint')}
</CardDescription>
</div>
<Badge variant="outline">
{kind === 'agent'
? t('agents.kindBadgeAgent')
: t('agents.kindBadgePipeline')}
</Badge>
</div>
</CardHeader>
<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">
{kind === 'agent'
? t('agents.agentDiagramDescription')
: t('agents.pipelineDiagramDescription')}
</CardFooter>
</Card>
</div>
</div>
</div> </div>
</div> </div>
); );
@@ -2,29 +2,81 @@ import { useId } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { AgentKind } from '@/app/infra/entities/api'; 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 ( return (
<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 <marker
id={id} id={ids.arrow}
markerWidth="8" markerWidth="10"
markerHeight="8" markerHeight="10"
refX="7" refX="8"
refY="4" refY="5"
orient="auto" orient="auto"
markerUnits="strokeWidth" markerUnits="strokeWidth"
> >
<path d="M0,0 L8,4 L0,8 Z" fill="var(--muted-foreground)" /> <path d="M0 0 10 5 0 10Z" fill="var(--primary)" />
</marker> </marker>
</defs>
); );
} }
function AgentDiagram() { function AgentDiagram() {
const { t } = useTranslation(); 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 ( return (
<svg <svg
viewBox="0 0 720 340" viewBox="0 0 760 600"
role="img" role="img"
aria-label={t('agents.agentDiagramTitle')} aria-label={t('agents.agentDiagramTitle')}
className="h-auto w-full" className="h-auto w-full"
@@ -32,205 +84,258 @@ function AgentDiagram() {
> >
<title>{t('agents.agentDiagramTitle')}</title> <title>{t('agents.agentDiagramTitle')}</title>
<desc>{t('agents.agentDiagramDescription')}</desc> <desc>{t('agents.agentDiagramDescription')}</desc>
<defs> <DiagramDefinitions ids={ids} />
<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 <rect
x="24" x="10"
y="42" y="16"
width="184" width="740"
height="256" height="568"
rx="22" rx="34"
fill="var(--muted)" fill={`url(#${ids.surface})`}
fillOpacity="0.48"
stroke="var(--border)" 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 <text
x="116" x="54"
y="76" y="112"
textAnchor="middle" fill="var(--muted-foreground)"
fill="var(--foreground)" fontSize="13"
fontSize="16"
fontWeight="600" fontWeight="600"
letterSpacing="1.8"
> >
{t('agents.diagramEvents')} {t('agents.diagramEvents')}
</text> </text>
{[ {events.map((event, index) => (
{ <g key={event.label}>
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 <rect
x="38" x="46"
y={item.y} y={event.y}
width="156" width="180"
height="48" height="60"
rx="13" rx="18"
fill="var(--card)" fill="var(--card)"
stroke="var(--border)" stroke="var(--border)"
/> />
<circle
cx="78"
cy={event.y + 30}
r="13"
fill="var(--primary)"
opacity={0.09 + index * 0.045}
/>
{index === 0 && (
<path <path
d={item.icon} d={`M69 ${event.y + 24}h18v12H76l-5 4v-4h-2z`}
transform={`translate(0 ${item.y - 92})`}
fill="none" fill="none"
stroke="var(--primary)" stroke="var(--primary)"
strokeWidth="2" strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round" 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 <text
x="120" x="104"
y={item.y + 30} y={event.y + 35}
textAnchor="middle"
fill="var(--foreground)" fill="var(--foreground)"
fontSize="14" fontSize="14"
fontWeight="500" fontWeight="600"
> >
{item.label} {event.label}
</text> </text>
<circle cx="226" cy={event.y + 30} r="3.5" fill="var(--primary)" />
</g> </g>
))} ))}
<path <g>
d="M208 170H270" <circle
cx="380"
cy="300"
r="122"
fill="none" 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)" stroke="var(--primary)"
strokeWidth="2" 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 <rect
x="331" x="348"
y="112" y="250"
width="70" width="64"
height="54" height="48"
rx="16" rx="16"
fill="var(--card)" fill="var(--primary-foreground)"
stroke="var(--primary)" opacity="0.96"
strokeWidth="2"
/> />
<circle cx="350" cy="139" r="5" fill="var(--primary)" /> <circle cx="365" cy="273" r="4" fill="var(--primary)" />
<circle cx="382" cy="139" r="5" fill="var(--primary)" /> <circle cx="395" cy="273" r="4" fill="var(--primary)" />
<path <path
d="M350 153c8 7 24 7 32 0M366 112V99m-8 0h16" d="M364 285c8 7 24 7 32 0M380 250v-12m-7 0h14"
fill="none" fill="none"
stroke="var(--primary)" stroke="var(--primary)"
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
/> />
<text <text
x="366" x="380"
y="202" y="329"
textAnchor="middle" textAnchor="middle"
fill="var(--foreground)" fill="var(--primary-foreground)"
fontSize="18" fontSize="20"
fontWeight="700" fontWeight="750"
> >
{t('agents.agentType')} {t('agents.agentType')}
</text> </text>
<text <text
x="366" x="380"
y="228" y="352"
textAnchor="middle" textAnchor="middle"
fill="var(--muted-foreground)" fill="var(--primary-foreground)"
fontSize="13" fontSize="11"
opacity="0.82"
> >
{t('agents.diagramDecide')} {t('agents.diagramDecide')}
</text> </text>
</g>
<path {capabilities.map((capability, index) => (
d="M448 170H506" <g key={capability.label}>
fill="none" <circle
stroke="var(--muted-foreground)" cx="534"
strokeWidth="2" cy={capability.y + 30}
markerEnd={`url(#${arrowId})`} r="3.5"
/> fill="var(--primary)"
{[
{ 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 <rect
x="516" x="534"
y={item.y} y={capability.y}
width="176" width="180"
height="52" height="60"
rx="15" rx="18"
fill="var(--card)" fill="var(--card)"
stroke={index === 1 ? 'var(--primary)' : 'var(--border)'} stroke={index === 1 ? 'var(--primary)' : 'var(--border)'}
strokeWidth={index === 1 ? 1.5 : 1}
/> />
<circle <circle
cx="544" cx="566"
cy={item.y + 26} cy={capability.y + 30}
r="10" r="13"
fill="var(--primary)" fill="var(--primary)"
fillOpacity={index === 1 ? 0.22 : 0.1} opacity={index === 1 ? 0.18 : 0.09}
/>
<path
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="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/> />
<text <text
x="608" x="592"
y={item.y + 31} y={capability.y + 35}
textAnchor="middle"
fill="var(--foreground)" fill="var(--foreground)"
fontSize="14" fontSize="14"
fontWeight="500" fontWeight="600"
> >
{item.label} {capability.label}
</text> </text>
</g> </g>
))} ))}
<path
d="M704 494c-88 46-246 63-388 31"
fill="none"
stroke="var(--primary)"
strokeWidth="1.5"
strokeDasharray="2 8"
opacity="0.24"
markerEnd={`url(#${ids.arrow})`}
/>
</svg> </svg>
); );
} }
function PipelineDiagram() { function PipelineDiagram() {
const { t } = useTranslation(); const { t } = useTranslation();
const arrowId = `pipeline-arrow-${useId().replace(/:/g, '')}`; const ids = useDiagramIds('pipeline');
const stages = [ const stations = [
t('agents.diagramPreprocess'), { x: 198, y: 312, label: t('agents.diagramPreprocess') },
t('agents.diagramAI'), { x: 338, y: 242, label: t('agents.diagramAI') },
t('agents.diagramPostprocess'), { x: 478, y: 312, label: t('agents.diagramPostprocess') },
t('agents.diagramOutput'), { x: 618, y: 242, label: t('agents.diagramOutput') },
]; ];
return ( return (
<svg <svg
viewBox="0 0 720 340" viewBox="0 0 760 600"
role="img" role="img"
aria-label={t('agents.pipelineDiagramTitle')} aria-label={t('agents.pipelineDiagramTitle')}
className="h-auto w-full" className="h-auto w-full"
@@ -238,123 +343,168 @@ function PipelineDiagram() {
> >
<title>{t('agents.pipelineDiagramTitle')}</title> <title>{t('agents.pipelineDiagramTitle')}</title>
<desc>{t('agents.pipelineDiagramDescription')}</desc> <desc>{t('agents.pipelineDiagramDescription')}</desc>
<defs> <DiagramDefinitions ids={ids} />
<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 <rect
x="24" x="10"
y="65" y="16"
width="672" width="740"
height="210" height="568"
rx="28" rx="34"
fill="url(#pipeline-flow)" fill={`url(#${ids.surface})`}
stroke="var(--border)" 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 <text
x="360" x="380"
y="104" y="105"
textAnchor="middle" textAnchor="middle"
fill="var(--muted-foreground)" fill="var(--muted-foreground)"
fontSize="14" fontSize="13"
fontWeight="500" fontWeight="600"
letterSpacing="1.5"
> >
{t('agents.pipelineDiagramFlow')} {t('agents.pipelineDiagramFlow')}
</text> </text>
<rect <path
x="40" 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"
y="137" fill="none"
width="104" stroke="var(--primary)"
height="68" strokeWidth="28"
rx="18" strokeLinecap="round"
opacity="0.06"
/>
<path
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={`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"
/>
<g>
<circle
cx="72"
cy="360"
r="42"
fill="var(--card)" fill="var(--card)"
stroke="var(--border)" stroke="var(--border)"
/> />
<path <path
d="M63 156h38v25H76l-9 8v-8h-4z" d="M54 348h36v24H67l-9 8v-8h-4z"
fill="none" fill="none"
stroke="var(--primary)" stroke="var(--primary)"
strokeWidth="2" strokeWidth="2.5"
strokeLinejoin="round" strokeLinejoin="round"
/> />
<text <text
x="92" x="72"
y="228" y="424"
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" textAnchor="middle"
fill="var(--foreground)" fill="var(--foreground)"
fontSize="13" fontSize="13"
fontWeight="600" fontWeight="600"
> >
{stage} {t('agents.diagramMessage')}
</text> </text>
{index < stages.length - 1 && ( </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="0"
cy="0"
r="5"
fill={step === 1 ? 'var(--primary)' : 'var(--border)'}
/>
{step < 3 && (
<path <path
d={`M${x + 104} 171H${x + 122}`} d="M10 0h86"
fill="none" stroke="var(--border)"
stroke="var(--muted-foreground)"
strokeWidth="2" strokeWidth="2"
markerEnd={`url(#${arrowId})`} strokeLinecap="round"
/> />
)} )}
</g> </g>
); ))}
})} </g>
</svg> </svg>
); );
} }