Skip to content

Commit 3a27b27

Browse files
committed
some improvements to visuals
1 parent af69110 commit 3a27b27

File tree

9 files changed

+644
-260
lines changed

9 files changed

+644
-260
lines changed

frontend/docs/components/AgentLoopDiagram.tsx

Lines changed: 188 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,107 @@
11
import React, { useState, useEffect } from "react";
22

3-
const PHASES = ["reason", "act", "observe", "decide"] as const;
3+
const PHASES = ["thought", "action", "observation"] as const;
44
type Phase = (typeof PHASES)[number];
55

6-
const PHASE_CONFIG: Record<
7-
Phase,
8-
{ label: string; color: string; icon: string }
9-
> = {
10-
reason: { label: "Reason", color: "#818cf8", icon: "🧠" },
11-
act: { label: "Act", color: "#38bdf8", icon: "⚡" },
12-
observe: { label: "Observe", color: "#fbbf24", icon: "👁" },
13-
decide: { label: "Decide", color: "#34d399", icon: "🔀" },
6+
const PHASE_CONFIG: Record<Phase, { label: string; color: string }> = {
7+
thought: { label: "Thought", color: "#818cf8" },
8+
action: { label: "Action", color: "#38bdf8" },
9+
observation: { label: "Observation", color: "#fbbf24" },
10+
};
11+
12+
/** Small SVG icons rendered inline, no emojis */
13+
const PhaseIcon: React.FC<{ phase: Phase; active: boolean }> = ({
14+
phase,
15+
active,
16+
}) => {
17+
const color = active ? PHASE_CONFIG[phase].color : "#6b7280";
18+
const size = 18;
19+
20+
switch (phase) {
21+
case "thought":
22+
// Lightbulb icon
23+
return (
24+
<svg
25+
width={size}
26+
height={size}
27+
viewBox="0 0 24 24"
28+
fill="none"
29+
stroke={color}
30+
strokeWidth="2"
31+
strokeLinecap="round"
32+
strokeLinejoin="round"
33+
>
34+
<path d="M9 18h6" />
35+
<path d="M10 22h4" />
36+
<path d="M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z" />
37+
</svg>
38+
);
39+
case "action":
40+
// Zap/bolt icon
41+
return (
42+
<svg
43+
width={size}
44+
height={size}
45+
viewBox="0 0 24 24"
46+
fill="none"
47+
stroke={color}
48+
strokeWidth="2"
49+
strokeLinecap="round"
50+
strokeLinejoin="round"
51+
>
52+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
53+
</svg>
54+
);
55+
case "observation":
56+
// Eye icon
57+
return (
58+
<svg
59+
width={size}
60+
height={size}
61+
viewBox="0 0 24 24"
62+
fill="none"
63+
stroke={color}
64+
strokeWidth="2"
65+
strokeLinecap="round"
66+
strokeLinejoin="round"
67+
>
68+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
69+
<circle cx="12" cy="12" r="3" />
70+
</svg>
71+
);
72+
}
1473
};
1574

1675
const AgentLoopDiagram: React.FC = () => {
17-
const [phase, setPhase] = useState<Phase>("reason");
76+
const [phaseIdx, setPhaseIdx] = useState(0);
1877
const [iteration, setIteration] = useState(1);
1978

2079
useEffect(() => {
2180
const interval = setInterval(() => {
22-
setPhase((prev) => {
23-
const idx = PHASES.indexOf(prev);
24-
if (idx === PHASES.length - 1) {
81+
setPhaseIdx((prev) => {
82+
if (prev === PHASES.length - 1) {
2583
setIteration((i) => (i >= 3 ? 1 : i + 1));
26-
return PHASES[0];
84+
return 0;
2785
}
28-
return PHASES[idx + 1];
86+
return prev + 1;
2987
});
30-
}, 1200);
88+
}, 1400);
3189
return () => clearInterval(interval);
3290
}, []);
3391

34-
const cx = 200;
35-
const cy = 140;
36-
const r = 90;
92+
const phase = PHASES[phaseIdx];
93+
94+
// Horizontal layout: 3 nodes evenly spaced
95+
const svgW = 520;
96+
const svgH = 160;
97+
const nodeY = 70;
98+
const nodeSpacing = 160;
99+
const startX = 100;
37100

38-
const nodePositions = PHASES.map((_, i) => {
39-
const angle = (i * Math.PI * 2) / PHASES.length - Math.PI / 2;
40-
return { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) };
41-
});
101+
const nodes = PHASES.map((_, i) => ({
102+
x: startX + i * nodeSpacing,
103+
y: nodeY,
104+
}));
42105

43106
return (
44107
<div
@@ -49,99 +112,138 @@ const AgentLoopDiagram: React.FC = () => {
49112
}}
50113
>
51114
<svg
52-
viewBox="0 0 400 280"
115+
viewBox={`0 0 ${svgW} ${svgH}`}
53116
className="mx-auto w-full"
54-
style={{ maxWidth: 500 }}
117+
style={{ maxWidth: 560 }}
55118
>
56-
{/* Circular arrows between phases */}
57-
{PHASES.map((_, i) => {
58-
const from = nodePositions[i];
59-
const to = nodePositions[(i + 1) % PHASES.length];
60-
const midX = (from.x + to.x) / 2;
61-
const midY = (from.y + to.y) / 2;
62-
const dx = to.x - from.x;
63-
const dy = to.y - from.y;
64-
const len = Math.sqrt(dx * dx + dy * dy);
65-
const nx = -dy / len;
66-
const ny = dx / len;
67-
const bulge = 20;
68-
const cpX = midX + nx * bulge;
69-
const cpY = midY + ny * bulge;
70-
71-
const phaseIdx = PHASES.indexOf(phase);
72-
const isActive = i === phaseIdx;
119+
<defs>
120+
<marker
121+
id="arrow"
122+
viewBox="0 0 10 7"
123+
refX="9"
124+
refY="3.5"
125+
markerWidth="8"
126+
markerHeight="6"
127+
orient="auto-start-reverse"
128+
>
129+
<polygon points="0 0, 10 3.5, 0 7" fill="#4b5563" />
130+
</marker>
131+
<marker
132+
id="arrow-active"
133+
viewBox="0 0 10 7"
134+
refX="9"
135+
refY="3.5"
136+
markerWidth="8"
137+
markerHeight="6"
138+
orient="auto-start-reverse"
139+
>
140+
<polygon points="0 0, 10 3.5, 0 7" fill="#818cf8" />
141+
</marker>
142+
</defs>
143+
144+
{/* Forward arrows between nodes */}
145+
{nodes.slice(0, -1).map((from, i) => {
146+
const to = nodes[i + 1];
147+
const isActive = phaseIdx === i;
148+
return (
149+
<line
150+
key={`fwd-${i}`}
151+
x1={from.x + 34}
152+
y1={from.y}
153+
x2={to.x - 34}
154+
y2={to.y}
155+
stroke={isActive ? PHASE_CONFIG[PHASES[i]].color : "#4b5563"}
156+
strokeWidth={isActive ? 2 : 1.5}
157+
markerEnd={isActive ? "url(#arrow-active)" : "url(#arrow)"}
158+
opacity={isActive ? 1 : 0.5}
159+
style={{ transition: "all 0.4s ease" }}
160+
/>
161+
);
162+
})}
73163

164+
{/* Return arrow: curved path from Observation back to Thought */}
165+
{(() => {
166+
const from = nodes[nodes.length - 1];
167+
const to = nodes[0];
168+
const isActive = phaseIdx === PHASES.length - 1;
169+
const curveY = nodeY + 58;
74170
return (
75171
<path
76-
key={`arrow-${i}`}
77-
d={`M ${from.x} ${from.y} Q ${cpX} ${cpY} ${to.x} ${to.y}`}
172+
d={`M ${from.x} ${from.y + 30} C ${from.x} ${curveY + 10}, ${to.x} ${curveY + 10}, ${to.x} ${to.y + 30}`}
78173
fill="none"
79-
stroke={isActive ? PHASE_CONFIG[PHASES[i]].color : "#374151"}
80-
strokeWidth={isActive ? 2.5 : 1.5}
81-
strokeDasharray={isActive ? "6 3" : "none"}
82-
opacity={isActive ? 1 : 0.4}
83-
style={{
84-
transition: "all 0.4s ease",
85-
}}
174+
stroke={isActive ? PHASE_CONFIG.observation.color : "#4b5563"}
175+
strokeWidth={isActive ? 2 : 1.5}
176+
strokeDasharray="6 4"
177+
opacity={isActive ? 1 : 0.35}
178+
style={{ transition: "all 0.4s ease" }}
86179
/>
87180
);
88-
})}
181+
})()}
182+
183+
{/* Loop label on return arrow */}
184+
<text
185+
x={svgW / 2}
186+
y={nodeY + 78}
187+
textAnchor="middle"
188+
fontSize="10"
189+
fill="#6b7280"
190+
fontStyle="italic"
191+
>
192+
iteration {iteration}/3
193+
</text>
89194

90195
{/* Phase nodes */}
91196
{PHASES.map((p, i) => {
92-
const pos = nodePositions[i];
197+
const pos = nodes[i];
93198
const config = PHASE_CONFIG[p];
94199
const isActive = phase === p;
95200

96201
return (
97202
<g key={p}>
98-
{/* Glow */}
203+
{/* Glow ring */}
99204
{isActive && (
100-
<circle
101-
cx={pos.x}
102-
cy={pos.y}
103-
r={36}
205+
<rect
206+
x={pos.x - 36}
207+
y={pos.y - 36}
208+
width={72}
209+
height={72}
210+
rx={16}
104211
fill={config.color}
105-
opacity={0.15}
212+
opacity={0.1}
106213
>
107-
<animate
108-
attributeName="r"
109-
values="36;42;36"
110-
dur="1.5s"
111-
repeatCount="indefinite"
112-
/>
113214
<animate
114215
attributeName="opacity"
115-
values="0.15;0.08;0.15"
116-
dur="1.5s"
216+
values="0.1;0.05;0.1"
217+
dur="1.8s"
117218
repeatCount="indefinite"
118219
/>
119-
</circle>
220+
</rect>
120221
)}
121-
{/* Circle */}
122-
<circle
123-
cx={pos.x}
124-
cy={pos.y}
125-
r={30}
126-
fill={isActive ? `${config.color}22` : "#1f2937"}
222+
{/* Node box */}
223+
<rect
224+
x={pos.x - 30}
225+
y={pos.y - 30}
226+
width={60}
227+
height={60}
228+
rx={14}
229+
fill={isActive ? `${config.color}18` : "#1f2937"}
127230
stroke={isActive ? config.color : "#374151"}
128231
strokeWidth={isActive ? 2 : 1.5}
129232
style={{ transition: "all 0.4s ease" }}
130233
/>
131234
{/* Icon */}
132-
<text
133-
x={pos.x}
134-
y={pos.y - 4}
135-
textAnchor="middle"
136-
fontSize="16"
137-
style={{ transition: "all 0.4s ease" }}
235+
<foreignObject
236+
x={pos.x - 9}
237+
y={pos.y - 18}
238+
width={18}
239+
height={18}
138240
>
139-
{config.icon}
140-
</text>
241+
<PhaseIcon phase={p} active={isActive} />
242+
</foreignObject>
141243
{/* Label */}
142244
<text
143245
x={pos.x}
144-
y={pos.y + 16}
246+
y={pos.y + 14}
145247
textAnchor="middle"
146248
fontSize="10"
147249
fontWeight={isActive ? 600 : 400}
@@ -153,31 +255,10 @@ const AgentLoopDiagram: React.FC = () => {
153255
</g>
154256
);
155257
})}
156-
157-
{/* Center iteration counter */}
158-
<text
159-
x={cx}
160-
y={cy - 8}
161-
textAnchor="middle"
162-
fontSize="11"
163-
fill="#6b7280"
164-
>
165-
Iteration
166-
</text>
167-
<text
168-
x={cx}
169-
y={cy + 10}
170-
textAnchor="middle"
171-
fontSize="18"
172-
fontWeight={700}
173-
fill="#e5e7eb"
174-
>
175-
{iteration}/3
176-
</text>
177258
</svg>
178259

179-
{/* Status bar */}
180-
<div className="mt-4 flex items-center justify-center gap-3">
260+
{/* Status indicators */}
261+
<div className="mt-3 flex items-center justify-center gap-3">
181262
{PHASES.map((p) => (
182263
<div
183264
key={p}
@@ -192,7 +273,7 @@ const AgentLoopDiagram: React.FC = () => {
192273
transition: "all 0.4s ease",
193274
}}
194275
>
195-
<span>{PHASE_CONFIG[p].icon}</span>
276+
<PhaseIcon phase={p} active={phase === p} />
196277
{PHASE_CONFIG[p].label}
197278
</div>
198279
))}

0 commit comments

Comments
 (0)