import type { TopologyGraph } from "@/lib/api/topology"; const ROW_KINDS = new Set(["pipeline", "ring"]); const STAR_KINDS = new Set(["hierarchical", "hub_spoke", "star_moe", "market"]); const W = 640; const H = 340; /** Position nodes by topology kind: a row for pipeline/ring, a center+row star * for delegation kinds, a circle otherwise. */ function layout(kind: string, n: number): { x: number; y: number }[] { const cx = W / 2; const cy = H / 2; if (n <= 1) return [{ x: cx, y: cy }]; if (ROW_KINDS.has(kind)) { const gap = (W - 120) / (n - 1); return Array.from({ length: n }, (_, i) => ({ x: 60 + i * gap, y: cy })); } if (STAR_KINDS.has(kind)) { const pts = [{ x: cx, y: 72 }]; const spokes = n - 1; const gap = spokes > 1 ? (W - 120) / (spokes - 1) : 0; for (let i = 0; i < spokes; i++) { pts.push({ x: spokes > 1 ? 60 + i * gap : cx, y: H - 72 }); } return pts; } const r = Math.min(W, H) / 2 - 64; return Array.from({ length: n }, (_, i) => { const a = (i / n) * Math.PI * 2 - Math.PI / 2; return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) }; }); } /** Renders a topology graph as a lightweight SVG (no external deps). */ export function TopologyGraphView({ graph }: { graph: TopologyGraph }) { const index = new Map(graph.nodes.map((node, i) => [node.id, i])); const pos = layout(graph.kind, graph.nodes.length); return ( {graph.edges.map((edge, i) => { const a = pos[index.get(edge.from) ?? -1]; const b = pos[index.get(edge.to) ?? -1]; if (!a || !b) return null; return ( ); })} {graph.nodes.map((node, i) => { const p = pos[i]; return ( {node.id} {node.role} ); })} ); }