11/**
22 * Agent Tree Node Component
3- * Elegant tree visualization with enhanced visual design
4- * Features: Animated connection lines, status indicators, smooth transitions
5- * Premium visual effects with depth and hierarchy
3+ * Clean tree visualization with simple connection lines
64 */
75
86import { useState , memo } from "react" ;
@@ -11,103 +9,92 @@ import { Badge } from "@/components/ui/badge";
119import { AGENT_STATUS_CONFIG } from "../constants" ;
1210import type { AgentTreeNodeItemProps } from "../types" ;
1311
14- // Agent type icons with enhanced colors and styling
12+ // Agent type icons
1513const AGENT_TYPE_ICONS : Record < string , React . ReactNode > = {
16- orchestrator : < Cpu className = "w-4 h-4 text-violet-500" /> ,
17- recon : < Scan className = "w-4 h-4 text-teal-500" /> ,
18- analysis : < FileSearch className = "w-4 h-4 text-amber-500" /> ,
19- verification : < ShieldCheck className = "w-4 h-4 text-emerald-500" /> ,
14+ orchestrator : < Cpu className = "w-4 h-4 text-violet-600 dark:text-violet- 500" /> ,
15+ recon : < Scan className = "w-4 h-4 text-teal-600 dark:text-teal- 500" /> ,
16+ analysis : < FileSearch className = "w-4 h-4 text-amber-600 dark:text-amber- 500" /> ,
17+ verification : < ShieldCheck className = "w-4 h-4 text-emerald-600 dark:text-emerald- 500" /> ,
2018} ;
2119
22- // Agent type background colors for icon container
20+ // Agent type background colors
2321const AGENT_TYPE_BG : Record < string , string > = {
24- orchestrator : 'bg-violet-500/15 border-violet-500/30' ,
25- recon : 'bg-teal-500/15 border-teal-500/30' ,
26- analysis : 'bg-amber-500/15 border-amber-500/30' ,
27- verification : 'bg-emerald-500/15 border-emerald-500/30' ,
28- } ;
29-
30- // Status colors for the glow effect
31- const STATUS_GLOW_COLORS : Record < string , string > = {
32- running : 'shadow-[0_0_15px_rgba(52,211,153,0.3)]' ,
33- completed : 'shadow-[0_0_10px_rgba(52,211,153,0.15)]' ,
34- failed : 'shadow-[0_0_12px_rgba(244,63,94,0.25)]' ,
35- waiting : 'shadow-[0_0_8px_rgba(251,191,36,0.2)]' ,
36- created : '' ,
22+ orchestrator : 'bg-violet-100 dark:bg-violet-500/15 border-violet-300 dark:border-violet-500/30' ,
23+ recon : 'bg-teal-100 dark:bg-teal-500/15 border-teal-300 dark:border-teal-500/30' ,
24+ analysis : 'bg-amber-100 dark:bg-amber-500/15 border-amber-300 dark:border-amber-500/30' ,
25+ verification : 'bg-emerald-100 dark:bg-emerald-500/15 border-emerald-300 dark:border-emerald-500/30' ,
3726} ;
3827
3928export const AgentTreeNodeItem = memo ( function AgentTreeNodeItem ( {
4029 node,
4130 depth = 0 ,
4231 selectedId,
43- onSelect
44- } : AgentTreeNodeItemProps ) {
32+ onSelect,
33+ isLast = false
34+ } : AgentTreeNodeItemProps & { isLast ?: boolean } ) {
4535 const [ expanded , setExpanded ] = useState ( true ) ;
4636 const hasChildren = node . children && node . children . length > 0 ;
4737 const isSelected = selectedId === node . agent_id ;
4838 const isRunning = node . status === 'running' ;
4939 const isCompleted = node . status === 'completed' ;
5040 const isFailed = node . status === 'failed' ;
5141
52- const statusConfig = AGENT_STATUS_CONFIG [ node . status ] || AGENT_STATUS_CONFIG . created ;
5342 const typeIcon = AGENT_TYPE_ICONS [ node . agent_type ] || < Bot className = "w-3.5 h-3.5 text-muted-foreground" /> ;
54- const typeBg = AGENT_TYPE_BG [ node . agent_type ] || 'bg-muted/50 border-border/50' ;
43+ const typeBg = AGENT_TYPE_BG [ node . agent_type ] || 'bg-muted border-border' ;
44+
45+ const indent = depth * 24 ;
5546
5647 return (
5748 < div className = "relative" >
58- { /* Connection line to parent - vertical line with gradient */ }
59- { depth > 0 && (
60- < div
61- className = "absolute top-0 w-px bg-gradient-to-b from-border/80 via-border/50 to-border/30"
62- style = { {
63- left : `${ depth * 20 - 10 } px` ,
64- height : '22px' ,
65- } }
66- />
67- ) }
68-
69- { /* Horizontal connector line with dot */ }
49+ { /* 树形连接线 */ }
7050 { depth > 0 && (
7151 < >
52+ { /* 垂直线 - 从父节点延伸下来 */ }
7253 < div
73- className = "absolute top-[22px] h-px bg-gradient-to-r from- border/60 to-border/30 "
54+ className = "absolute border-l-2 border-slate-300 dark: border-slate-600 "
7455 style = { {
75- left : `${ depth * 20 - 10 } px` ,
76- width : '10px' ,
56+ left : `${ indent - 12 } px` ,
57+ top : 0 ,
58+ height : isLast ? '20px' : '100%' ,
7759 } }
7860 />
61+ { /* 水平线 - 连接到当前节点 */ }
7962 < div
80- className = "absolute top-[20px] w-1.5 h-1.5 rounded-full bg- border/60 "
63+ className = "absolute border-t-2 border-slate-300 dark: border-slate-600 "
8164 style = { {
82- left : `${ depth * 20 - 11 } px` ,
65+ left : `${ indent - 12 } px` ,
66+ top : '20px' ,
67+ width : '12px' ,
8368 } }
8469 />
8570 </ >
8671 ) }
8772
88- { /* Node item with enhanced styling */ }
73+ { /* Node item */ }
8974 < div
9075 className = { `
91- group relative flex items-center gap-2.5 py-2.5 px-3 cursor-pointer rounded-lg
92- transition-all duration-300 ease-out backdrop-blur-sm
76+ relative flex items-center gap-2 py-2 px-2 cursor-pointer rounded-md
9377 ${ isSelected
94- ? 'bg-primary/15 border border-primary/50 shadow-[0_0_20px_rgba(255,107,44,0.15)]'
95- : 'border border-transparent hover:bg-card/60 hover:border-border/50'
78+ ? 'bg-primary/15 border-2 border-primary shadow-[0_0_12px_rgba(255,95,31,0.4)]'
79+ : isRunning
80+ ? 'bg-emerald-50 dark:bg-emerald-950/30 border-2 border-emerald-400 dark:border-emerald-500 shadow-[0_0_10px_rgba(52,211,153,0.3)]'
81+ : isCompleted
82+ ? 'bg-slate-50 dark:bg-card border border-emerald-300 dark:border-emerald-600'
83+ : isFailed
84+ ? 'bg-rose-50 dark:bg-rose-950/20 border border-rose-300 dark:border-rose-500'
85+ : node . status === 'waiting'
86+ ? 'bg-amber-50 dark:bg-amber-950/20 border border-amber-300 dark:border-amber-500'
87+ : 'bg-slate-50 dark:bg-card border border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'
9688 }
97- ${ STATUS_GLOW_COLORS [ node . status ] || '' }
9889 ` }
99- style = { { marginLeft : `${ depth * 20 } px` } }
90+ style = { { marginLeft : `${ indent } px` } }
10091 onClick = { ( ) => onSelect ( node . agent_id ) }
10192 >
102- { /* Expand/collapse button with enhanced styling */ }
93+ { /* Expand/collapse button */ }
10394 { hasChildren ? (
10495 < button
10596 onClick = { ( e ) => { e . stopPropagation ( ) ; setExpanded ( ! expanded ) ; } }
106- className = { `
107- flex-shrink-0 w-6 h-6 flex items-center justify-center rounded-md
108- transition-all duration-300
109- ${ expanded ? 'bg-muted/80 border border-border/50' : 'hover:bg-muted/50' }
110- ` }
97+ className = "flex-shrink-0 w-5 h-5 flex items-center justify-center rounded hover:bg-muted"
11198 >
11299 { expanded ? (
113100 < ChevronDown className = "w-4 h-4 text-muted-foreground" />
@@ -116,81 +103,65 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
116103 ) }
117104 </ button >
118105 ) : (
119- < span className = "w-6 " />
106+ < span className = "w-5 " />
120107 ) }
121108
122- { /* Status indicator with enhanced glow */ }
109+ { /* Status indicator */ }
123110 < div className = "relative flex-shrink-0" >
124111 < div className = { `
125- w-3 h-3 rounded-full transition-all duration-300
126- ${ isRunning ? 'bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.6)] ' : '' }
127- ${ isCompleted ? 'bg-emerald-500 shadow-[0_0_6px_rgba(52,211,153,0.4)] ' : '' }
128- ${ isFailed ? 'bg-rose-400 shadow-[0_0_6px_rgba(244,63,94,0.4)] ' : '' }
129- ${ node . status === 'waiting' ? 'bg-amber-400 shadow-[0_0_6px_rgba(251,191,36,0.4)] ' : '' }
130- ${ node . status === 'created' ? 'bg-muted-foreground/50 ' : '' }
112+ w-2.5 h-2.5 rounded-full
113+ ${ isRunning ? 'bg-emerald-500 ' : '' }
114+ ${ isCompleted ? 'bg-emerald-500' : '' }
115+ ${ isFailed ? 'bg-rose-500 ' : '' }
116+ ${ node . status === 'waiting' ? 'bg-amber-500 ' : '' }
117+ ${ node . status === 'created' ? 'bg-slate-400 ' : '' }
131118 ` } />
132119 { isRunning && (
133- < >
134- < div className = "absolute inset-0 w-3 h-3 rounded-full bg-emerald-400 animate-ping opacity-40" />
135- < div className = "absolute inset-[-2px] w-[calc(100%+4px)] h-[calc(100%+4px)] rounded-full border border-emerald-400/30 animate-pulse" />
136- </ >
120+ < div className = "absolute inset-0 w-2.5 h-2.5 rounded-full bg-emerald-500 animate-ping opacity-50" />
137121 ) }
138122 </ div >
139123
140- { /* Agent type icon with background */ }
141- < div className = { `flex-shrink-0 p-1.5 rounded-md border ${ typeBg } transition-all duration-300 group-hover:scale-105 ` } >
124+ { /* Agent type icon */ }
125+ < div className = { `flex-shrink-0 p-1 rounded border ${ typeBg } ` } >
142126 { typeIcon }
143127 </ div >
144128
145- { /* Agent name with enhanced styling */ }
129+ { /* Agent name */ }
146130 < span className = { `
147- text-sm font-mono truncate flex-1 transition-all duration-300
148- ${ isSelected ? 'text-foreground font-semibold' : 'text-foreground/90 group-hover:text-foreground ' }
131+ text-sm font-mono truncate flex-1
132+ ${ isSelected ? 'text-foreground font-semibold' : 'text-foreground' }
149133 ` } >
150134 { node . agent_name }
151135 </ span >
152136
153- { /* Metrics badges with enhanced styling */ }
154- < div className = "flex items-center gap-2 flex-shrink-0" >
155- { /* Iterations with icon */ }
137+ { /* Metrics */ }
138+ < div className = "flex items-center gap-1.5 flex-shrink-0" >
156139 { ( node . iterations ?? 0 ) > 0 && (
157- < div className = "flex items-center gap-1 text-xs text-muted-foreground font-mono bg-muted/80 px-2 py-1 rounded-md border border-border/50 " >
140+ < div className = "flex items-center gap-1 text-xs text-muted-foreground font-mono bg-muted px-1.5 py-0.5 rounded border border-border" >
158141 < Zap className = "w-3 h-3" />
159142 < span > { node . iterations } </ span >
160143 </ div >
161144 ) }
162145
163- { /* Findings count - Only show for Orchestrator (root agent) */ }
164146 { ! node . parent_agent_id && node . findings_count > 0 && (
165- < Badge className = "h-6 px-2.5 text-xs bg-rose-500/20 text-rose-600 dark:text-rose-300 border border-rose-500/40 font-mono font-bold shadow-[0_0_10px_rgba(244,63,94,0.15)] " >
166- { node . findings_count } findings
147+ < Badge className = "h-5 px-2 text-xs bg-rose-100 dark:bg-rose- 500/20 text-rose-600 dark:text-rose-300 border border-rose-300 dark:border-rose- 500/40 font-mono font-bold" >
148+ { node . findings_count }
167149 </ Badge >
168150 ) }
169151 </ div >
170152 </ div >
171153
172- { /* Children with animated reveal */ }
154+ { /* Children */ }
173155 { expanded && hasChildren && (
174- < div
175- className = "relative animate-in slide-in-from-top-1 duration-300"
176- >
177- { /* Vertical connection line for children with gradient */ }
178- < div
179- className = "absolute w-px bg-gradient-to-b from-border/60 via-border/40 to-transparent"
180- style = { {
181- left : `${ ( depth + 1 ) * 20 - 10 } px` ,
182- top : '0' ,
183- height : `calc(100% - 24px)` ,
184- } }
185- />
186-
187- { node . children . map ( ( child ) => (
156+ < div className = "relative" >
157+ { node . children . map ( ( child , index ) => (
188158 < AgentTreeNodeItem
189159 key = { child . agent_id }
190160 node = { child }
191161 depth = { depth + 1 }
192162 selectedId = { selectedId }
193163 onSelect = { onSelect }
164+ isLast = { index === node . children . length - 1 }
194165 />
195166 ) ) }
196167 </ div >
0 commit comments