mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-29 05:37:14 +00:00
style(agents): animate processor diagram connections
This commit is contained in:
@@ -5,6 +5,36 @@ import type { AgentKind } from '@/app/infra/entities/api';
|
|||||||
const LANGBOT_BLUE = '#2288ee';
|
const LANGBOT_BLUE = '#2288ee';
|
||||||
const LANGBOT_CYAN = '#19b8c9';
|
const LANGBOT_CYAN = '#19b8c9';
|
||||||
|
|
||||||
|
function DiagramMotionStyles() {
|
||||||
|
return (
|
||||||
|
<style>{`
|
||||||
|
@keyframes processor-line-flow {
|
||||||
|
to { stroke-dashoffset: -30; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes processor-link-breathe {
|
||||||
|
0%, 100% { stroke-opacity: 0.28; }
|
||||||
|
50% { stroke-opacity: 0.72; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.processor-line-flow {
|
||||||
|
animation: processor-line-flow 1.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.processor-link-breathe {
|
||||||
|
animation: processor-link-breathe 2.8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.processor-line-flow,
|
||||||
|
.processor-link-breathe {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ArrowMarker({ id }: { id: string }) {
|
function ArrowMarker({ id }: { id: string }) {
|
||||||
return (
|
return (
|
||||||
<defs>
|
<defs>
|
||||||
@@ -80,6 +110,7 @@ function AgentDiagram() {
|
|||||||
>
|
>
|
||||||
<desc>{t('agents.agentDiagramDescription')}</desc>
|
<desc>{t('agents.agentDiagramDescription')}</desc>
|
||||||
<ArrowMarker id={arrowId} />
|
<ArrowMarker id={arrowId} />
|
||||||
|
<DiagramMotionStyles />
|
||||||
|
|
||||||
<rect width="760" height="620" fill="var(--card)" />
|
<rect width="760" height="620" fill="var(--card)" />
|
||||||
|
|
||||||
@@ -100,8 +131,12 @@ function AgentDiagram() {
|
|||||||
fill="none"
|
fill="none"
|
||||||
stroke={LANGBOT_BLUE}
|
stroke={LANGBOT_BLUE}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
|
strokeDasharray="4 11"
|
||||||
opacity="0.32"
|
opacity="0.32"
|
||||||
markerEnd={`url(#${arrowId})`}
|
markerEnd={`url(#${arrowId})`}
|
||||||
|
className="processor-line-flow"
|
||||||
|
data-motion="flow"
|
||||||
|
style={{ animationDelay: `${index * -0.35}s` }}
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
x="64"
|
x="64"
|
||||||
@@ -158,7 +193,7 @@ function AgentDiagram() {
|
|||||||
{t('agents.diagramAgentCanUse')}
|
{t('agents.diagramAgentCanUse')}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{outputs.map((item) => (
|
{outputs.map((item, index) => (
|
||||||
<g key={item.label}>
|
<g key={item.label}>
|
||||||
<path
|
<path
|
||||||
d={`M436 310 C468 310 474 ${item.y + 29} 538 ${item.y + 29}`}
|
d={`M436 310 C468 310 474 ${item.y + 29} 538 ${item.y + 29}`}
|
||||||
@@ -167,6 +202,9 @@ function AgentDiagram() {
|
|||||||
strokeWidth="1.8"
|
strokeWidth="1.8"
|
||||||
strokeDasharray="5 6"
|
strokeDasharray="5 6"
|
||||||
opacity="0.48"
|
opacity="0.48"
|
||||||
|
className="processor-link-breathe"
|
||||||
|
data-motion="relation"
|
||||||
|
style={{ animationDelay: `${index * -0.45}s` }}
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
x="538"
|
x="538"
|
||||||
@@ -224,6 +262,7 @@ function PipelineDiagram() {
|
|||||||
>
|
>
|
||||||
<desc>{t('agents.pipelineDiagramDescription')}</desc>
|
<desc>{t('agents.pipelineDiagramDescription')}</desc>
|
||||||
<ArrowMarker id={arrowId} />
|
<ArrowMarker id={arrowId} />
|
||||||
|
<DiagramMotionStyles />
|
||||||
|
|
||||||
<rect width="760" height="620" fill="var(--card)" />
|
<rect width="760" height="620" fill="var(--card)" />
|
||||||
<text
|
<text
|
||||||
@@ -243,6 +282,16 @@ function PipelineDiagram() {
|
|||||||
opacity="0.32"
|
opacity="0.32"
|
||||||
markerEnd={`url(#${arrowId})`}
|
markerEnd={`url(#${arrowId})`}
|
||||||
/>
|
/>
|
||||||
|
<path
|
||||||
|
d="M156 310H724"
|
||||||
|
fill="none"
|
||||||
|
stroke={LANGBOT_BLUE}
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeDasharray="4 14"
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="processor-line-flow"
|
||||||
|
data-motion="flow"
|
||||||
|
/>
|
||||||
|
|
||||||
{steps.map((step, index) => {
|
{steps.map((step, index) => {
|
||||||
const x = 40 + index * 142;
|
const x = 40 + index * 142;
|
||||||
|
|||||||
@@ -183,11 +183,20 @@ test.describe('frontend CRUD smoke flows', () => {
|
|||||||
);
|
);
|
||||||
await expect(agentTypeCard).toHaveAttribute('aria-checked', 'true');
|
await expect(agentTypeCard).toHaveAttribute('aria-checked', 'true');
|
||||||
await expect(page.getByTestId('agent-diagram')).toBeVisible();
|
await expect(page.getByTestId('agent-diagram')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('agent-diagram').locator('[data-motion="flow"]'),
|
||||||
|
).toHaveCount(3);
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('agent-diagram').locator('[data-motion="relation"]'),
|
||||||
|
).toHaveCount(3);
|
||||||
await expect(page.getByTestId('pipeline-diagram')).toHaveCount(0);
|
await expect(page.getByTestId('pipeline-diagram')).toHaveCount(0);
|
||||||
|
|
||||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||||
await expect(pipelineTypeCard).toHaveAttribute('aria-checked', 'true');
|
await expect(pipelineTypeCard).toHaveAttribute('aria-checked', 'true');
|
||||||
await expect(page.getByTestId('pipeline-diagram')).toBeVisible();
|
await expect(page.getByTestId('pipeline-diagram')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('pipeline-diagram').locator('[data-motion="flow"]'),
|
||||||
|
).toHaveCount(1);
|
||||||
await expect(page.getByTestId('agent-diagram')).toHaveCount(0);
|
await expect(page.getByTestId('agent-diagram')).toHaveCount(0);
|
||||||
|
|
||||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user