'use client' import { useCallback, useEffect, useState, useMemo } from 'react' import { ReactFlow, Node, Edge, addEdge, useNodesState, useEdgesState, Controls, Background, BackgroundVariant, Connection, } from '@xyflow/react' import '@xyflow/react/dist/style.css' import { Agent, Session } from '@/types' import { sessionToAgent, generateNodePosition } from '@/lib/utils' import { AgentCoreNode } from '@/components/ui/agent-core-node' interface AgentNetworkProps { agents: Agent[] sessions: Session[] } // SVG icons for agent types (16x16, stroke-based) function CrownIcon() { return ( ) } function BotIcon() { return ( ) } function ClockIcon() { return ( ) } function GroupIcon() { return ( ) } function FileIcon() { return ( ) } // Custom node component for agents function AgentNode({ data }: { data: any }) { const { agent, status } = data const getStatusClasses = () => { switch (status) { case 'active': return 'border-void-cyan glow-cyan' case 'idle': return 'border-void-amber/50' case 'error': return 'border-void-crimson badge-glow-error' default: return 'border-border' } } const getTypeIcon = () => { switch (agent.type) { case 'main': return case 'subagent': return case 'cron': return case 'group': return default: return } } const getRoleBadge = () => { switch (agent.type) { case 'main': return { label: 'LEAD', color: 'bg-void-violet/20 text-void-violet border-void-violet/30' } case 'subagent': return { label: 'WORKER', color: 'bg-void-cyan/20 text-void-cyan border-void-cyan/30' } case 'cron': return { label: 'CRON', color: 'bg-void-amber/20 text-void-amber border-void-amber/30' } default: return { label: 'SYSTEM', color: 'bg-muted text-muted-foreground border-border' } } } const roleBadge = getRoleBadge() const isWorking = status === 'active' return (
{getTypeIcon()} {isWorking && ( WORKING )}
{agent.name}
{roleBadge.label}
{(typeof agent.model === 'string' ? agent.model : '').split('/').pop() || 'unknown'}
{agent.session && (
{agent.session.key.split(':').pop()}
)}
) } const nodeTypes = { agent: AgentNode, core: AgentCoreNode, } export function AgentNetwork({ agents, sessions }: AgentNetworkProps) { const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) // Convert sessions to nodes and edges const { nodeData, edgeData } = useMemo(() => { const agentList = sessions.map(sessionToAgent) // Add CORE hub node at center const coreNode: Node = { id: '__core__', type: 'core', position: { x: 0, y: 0 }, data: { label: 'CORE', agentCount: agentList.length }, style: { background: 'transparent', border: 'none' }, } const agentNodes: Node[] = agentList.map((agent, index) => ({ id: agent.id, type: 'agent', position: generateNodePosition(index, agentList.length), data: { agent, status: agent.status, label: agent.name }, style: { background: 'transparent', border: 'none', } })) const nodes = [coreNode, ...agentNodes] // Create edges — all agents connect to CORE, plus hierarchical edges const edges: Edge[] = [] const cyanStroke = 'hsl(var(--void-cyan))' const cyanDimStroke = 'hsl(var(--void-cyan) / 0.4)' const amberStroke = 'hsl(var(--void-amber) / 0.5)' const mainAgents = agentList.filter(a => a.type === 'main') const subagents = agentList.filter(a => a.type === 'subagent') const cronAgents = agentList.filter(a => a.type === 'cron') // Connect all agents to CORE hub agentList.forEach(agent => { edges.push({ id: `core-${agent.id}`, source: '__core__', target: agent.id, animated: agent.status === 'active', style: { stroke: agent.status === 'active' ? cyanStroke : cyanDimStroke, strokeWidth: 1.5, }, type: 'smoothstep', }) }) // Connect main agents to subagents mainAgents.forEach(main => { subagents.forEach(sub => { edges.push({ id: `${main.id}-${sub.id}`, source: main.id, target: sub.id, animated: sub.status === 'active', style: { stroke: cyanStroke, strokeWidth: 2, }, type: 'smoothstep' }) }) // Connect main agents to cron jobs cronAgents.forEach(cron => { edges.push({ id: `${main.id}-${cron.id}`, source: main.id, target: cron.id, animated: false, style: { stroke: amberStroke, strokeWidth: 1, strokeDasharray: '5,5', }, type: 'smoothstep' }) }) }) return { nodeData: nodes, edgeData: edges } }, [sessions]) useEffect(() => { setNodes(nodeData) setEdges(edgeData) }, [nodeData, edgeData, setNodes, setEdges]) const onConnect = useCallback( (params: Connection) => setEdges((eds) => addEdge(params, eds)), [setEdges] ) if (sessions.length === 0) { return (

No agent network to display

Agent connections will appear here

) } return (

Agent Network

Visual representation of agent relationships

) }