'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
)
}