11import React , { useState , useEffect } from "react" ;
22
3- const PHASES = [ "reason " , "act " , "observe" , "decide "] as const ;
3+ const PHASES = [ "thought " , "action " , "observation " ] as const ;
44type 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
1675const 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