import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { askNode as apiAskNode, decideApproval, fetchAgents, fetchApprovals, fetchFeed, fetchGpu, fetchNodeDetail, fetchStatus, fetchTerminals, fetchWorkload, probeNode as apiProbeNode, sendPrompt as apiSendPrompt, } from '../lib/api' import { AGENT_NODE, NODE_ALIASES, wsUrl } from '../lib/constants' import { resolveInfraNode } from '../lib/infraCatalog' import type { Agent, AgentAnim, Approval, ChatMessage, FeedEntry, GpuStatus, NodeDetail, StatusData, TerminalLine, TopologyNode, WorkloadData, } from '../types' function resolveProbeId(nodeId: string) { const aliased = NODE_ALIASES[nodeId] || nodeId const infra = resolveInfraNode(nodeId) || resolveInfraNode(aliased) return infra?.id || aliased } export function useCommandCenter() { const [agents, setAgents] = useState([]) const [agentsLoading, setAgentsLoading] = useState(true) const [status, setStatus] = useState(null) const [workload, setWorkload] = useState(null) const [gpu, setGpu] = useState(null) const [feed, setFeed] = useState([]) const [approvals, setApprovals] = useState([]) const [chat, setChat] = useState([]) const [anims, setAnims] = useState>({}) const [selectedAgentId, setSelectedAgentId] = useState(null) const [promptBusy, setPromptBusy] = useState(false) const [terminals, setTerminals] = useState>({}) const [selectedNodeId, setSelectedNodeId] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const [nodeDetail, setNodeDetail] = useState(null) const [nodeBusy, setNodeBusy] = useState(false) const [mainView, setMainView] = useState<'platform' | 'approvals' | 'presentation' | 'dataquality' | 'knowledge' | 'storage'>('platform') const [approvalHighlight, setApprovalHighlight] = useState(false) const [chatExpanded, setChatExpanded] = useState(false) const promptTimeoutRef = useRef | null>(null) const [terminalExpanded, setTerminalExpanded] = useState(true) const appendTerminal = useCallback((line: TerminalLine) => { setTerminals((prev) => { const cur = prev[line.agent_id] || [] return { ...prev, [line.agent_id]: [...cur, line].slice(-300) } }) }, []) const selectedAgent = useMemo( () => agents.find((a) => a.id === selectedAgentId) || null, [agents, selectedAgentId], ) const reloadFast = useCallback(async () => { const [a, s, f, ap, g, t] = await Promise.all([ fetchAgents(), fetchStatus(), fetchFeed(), fetchApprovals(), fetchGpu(), fetchTerminals(), ]) setAgents(a) setAgentsLoading(false) setStatus(s) setGpu(g || s?.gpu || null) setFeed(f) setApprovals(ap) setTerminals(t) }, []) const reloadWorkload = useCallback(async () => { const w = await fetchWorkload() if (w?.zones) setWorkload(w) }, []) const reload = useCallback(async () => { await reloadFast() reloadWorkload() }, [reloadFast, reloadWorkload]) useEffect(() => { reloadFast().then(() => reloadWorkload()) const ws = new WebSocket(wsUrl()) ws.onmessage = (ev) => { const msg = JSON.parse(ev.data) if (msg.type === 'status') { setStatus(msg.data) if (msg.data.gpu) setGpu(msg.data.gpu) } if (msg.type === 'workload') setWorkload(msg.data) if (msg.type === 'terminal') appendTerminal(msg.line) if (msg.type === 'terminal_history' && msg.terminals) setTerminals(msg.terminals) if (msg.type === 'feed') setFeed((prev) => [msg.entry, ...prev].slice(0, 100)) if (msg.type === 'agent_dispatch') { setSelectedAgentId(msg.agent_id) setAnims((p) => ({ ...p, [msg.agent_id]: { agentId: msg.agent_id, state: 'walk', zone: msg.zone } })) } if (msg.type === 'agent_fetch') { setAnims((p) => ({ ...p, [msg.agent_id]: { agentId: msg.agent_id, state: 'fetch', zone: msg.zone } })) } if (msg.type === 'agent_return') { setAnims((p) => ({ ...p, [msg.agent_id]: { agentId: msg.agent_id, state: 'return', zone: msg.zone } })) setTimeout(() => { setAnims((p) => ({ ...p, [msg.agent_id]: { agentId: msg.agent_id, state: 'idle' } })) }, 1200) } if (msg.type === 'prompt_result') { setChat((c) => [...c, { role: 'agent', text: msg.answer, agent: msg.agent_id, ts: new Date().toISOString() }]) setPromptBusy(false) reloadFast() } if (msg.type === 'approval_new') { setApprovals((prev) => { if (prev.some((a) => a.id === msg.approval?.id)) return prev return [msg.approval, ...prev] }) if (msg.approval?.status === 'pending') setApprovalHighlight(true) } if (msg.type === 'approval_update' && msg.approval) { setApprovals((prev) => prev.filter((a) => a.id !== msg.approval.id)) } if (msg.type === 'node_ask_result') { setNodeBusy(false) if (msg.agent_id) setSelectedAgentId(msg.agent_id) } } const iv = setInterval(reloadFast, 15000) const wv = setInterval(reloadWorkload, 45000) return () => { ws.close(); clearInterval(iv); clearInterval(wv) } }, [reloadFast, reloadWorkload, appendTerminal]) const findNodeStub = useCallback((nodeId: string): TopologyNode | null => { const resolved = NODE_ALIASES[nodeId] || nodeId const allNodes = [ ...(workload?.topology?.nodes || []), ...Object.values(workload?.topologies || {}).flatMap((v) => v.nodes), ] const wn = allNodes.find((n) => n.id === nodeId) || allNodes.find((n) => n.id === resolved) if (wn) return wn const infra = resolveInfraNode(nodeId) || resolveInfraNode(resolved) if (infra) { return { id: infra.id, label: infra.label, vm: infra.vm, ip: infra.ip, x: 50, y: 50, color: infra.accent, level: 'ok', role: infra.description, apps: infra.apps.map((a) => ({ name: a.label, state: 'link', image: a.url, ports: a.port ? [a.port] : [] })), running: 1, total: 1, } } const agent = agents.find((a) => a.id === nodeId) if (agent) { return { id: agent.id, label: agent.name, vm: 'agent', ip: '10.0.21.33', x: 50, y: 50, color: agent.color, level: 'ok', role: agent.role, apps: [], running: 1, total: 1, agent_id: agent.id, } } const zone = workload?.zones.find((z) => z.id === nodeId || z.id === resolved) if (zone) { return { id: nodeId, label: zone.label, vm: zone.vm || zone.id, ip: zone.ip || '', x: 50, y: 50, color: zone.color, level: zone.level, role: 'zone', apps: zone.apps, running: zone.running, total: zone.total, } } return null }, [workload, agents]) const probeNodeId = useCallback((nodeId: string) => { setNodeBusy(true) setTerminalExpanded(true) apiProbeNode(resolveProbeId(nodeId)).finally(() => setNodeBusy(false)) }, []) const openTerminal = useCallback((nodeId: string) => { setTerminalExpanded(true) setSelectedNodeId(nodeId) }, []) const selectNode = useCallback(async (nodeId: string) => { const stub = findNodeStub(nodeId) if (!stub) return const probeId = resolveProbeId(nodeId) setSelectedNodeId(probeId) setSelectedNode({ ...stub, id: probeId }) setNodeDetail(null) setTerminalExpanded(true) const infra = resolveInfraNode(nodeId) const linked = agents.find( (a) => a.id === nodeId || AGENT_NODE[a.id] === probeId || a.id === infra?.agentId, ) if (linked) setSelectedAgentId(linked.id) try { const detail = await fetchNodeDetail(probeId) if (!detail.error) setNodeDetail(detail as NodeDetail) } catch { /* ok */ } probeNodeId(nodeId) }, [findNodeStub, agents, probeNodeId]) const selectAgent = useCallback((id: string) => { setSelectedAgentId(id) setTerminalExpanded(true) const agent = agents.find((a) => a.id === id) if (!agent) return const nodeId = agent.supervisor ? id : (AGENT_NODE[id] || agent.zone) if (nodeId) { const stub = findNodeStub(nodeId) if (stub) { selectNode(nodeId) return } } setSelectedNodeId(null) setSelectedNode(null) setNodeDetail(null) }, [agents, findNodeStub, selectNode]) const clearSelection = useCallback(() => { setSelectedNodeId(null) setSelectedNode(null) setNodeDetail(null) setSelectedAgentId(null) }, []) const probeNode = useCallback(() => { if (selectedNodeId) probeNodeId(selectedNodeId) }, [selectedNodeId, probeNodeId]) const askNode = useCallback(async (message: string) => { if (!selectedNodeId) return setNodeBusy(true) setTerminalExpanded(true) await apiAskNode(resolveProbeId(selectedNodeId), message) }, [selectedNodeId]) const sendPrompt = useCallback(async (message: string, agentId?: string) => { setPromptBusy(true) setChatExpanded(true) setChat((c) => [...c, { role: 'user', text: message, ts: new Date().toISOString() }]) if (agentId) setSelectedAgentId(agentId) await apiSendPrompt(message, agentId) }, []) const decide = useCallback(async (id: string, approved: boolean, decidedBy = 'mo-commander', note = '') => { setApprovals((prev) => prev.filter((a) => a.id !== id)) await decideApproval(id, approved, decidedBy, note) reloadFast() }, [reloadFast]) const terminalSubjectId = selectedNodeId || selectedAgentId const inspectorLines = useMemo(() => { if (!terminalSubjectId) return [] const probeId = resolveProbeId(terminalSubjectId) return terminals[probeId] || terminals[terminalSubjectId] || terminals[selectedAgentId || ''] || [] }, [terminalSubjectId, terminals, selectedAgentId]) const focusApprovals = useCallback(() => { setApprovalHighlight(true) setMainView('approvals') }, []) return { agents, agentsLoading, status, workload, gpu, feed, approvals, chat, anims, selectedAgentId, selectedAgent, promptBusy, selectedNodeId, selectedNode, nodeDetail, nodeBusy, mainView, setMainView, approvalHighlight, setApprovalHighlight, inspectorLines, terminalSubjectId, terminalExpanded, setTerminalExpanded, selectNode, selectAgent, clearSelection, probeNode, probeNodeId, openTerminal, askNode, sendPrompt, decide, focusApprovals, reload, chatExpanded, setChatExpanded, } }