/** * MemoryMapTab/RaptorTreeView.tsx — RAPTOR tree sub-tab (Part B). * * Fetches the hierarchical RAPTOR tree from /api/raptor-tree (defaults to the * most recent session with nodes) and renders it as level-indented summary * cards. No D3 needed — CSS indentation encodes hierarchy. */ import type React from "react"; import { useEffect, useState } from "react"; import { fetchRaptorTree, fetchRaptorBuildHistory } from "../../api/client"; import type { RaptorTreeResponse, RaptorNodeDTO, RaptorBuildHistoryResponse, } from "@contracts"; /** Per-level border color for node cards. */ function levelColor(level: number): string { if (level === 0) return "#d4a017"; // gold — root if (level === 1) return "#3b82f6"; // blue return "#14b8a6"; // teal — level 2+ } function formatDate(ts: number): string { if (!ts) return "—"; return new Date(ts).toLocaleString(); } function NodeCard({ node }: { node: RaptorNodeDTO }): React.ReactElement { const border = levelColor(node.level); const childrenLabel = node.children.length > 0 ? `${node.children.length} child${node.children.length === 1 ? "" : "ren"}` : "leaf"; return (
L{node.level} {childrenLabel} · {node.tokenEstimate} tok · {node.qualityMarker} {formatDate(node.builtAt)}
{node.summary}
); } export default function RaptorTreeView(): React.ReactElement { const [data, setData] = useState(null); const [history, setHistory] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; setLoading(true); setError(null); Promise.all([fetchRaptorTree(), fetchRaptorBuildHistory()]) .then(([d, h]) => { if (!cancelled) { setData(d); setHistory(h); setLoading(false); } }) .catch((e: unknown) => { if (!cancelled) { setError(e instanceof Error ? e.message : "Unknown error"); setLoading(false); } }); return () => { cancelled = true; }; }, []); if (loading) { return (
Loading RAPTOR tree...
); } if (error) { return (

Failed to load RAPTOR tree: {error}

); } if (!data || data.empty || data.nodes.length === 0) { return (

No RAPTOR tree built yet — run a compaction (the tree builds during compaction).

); } const latestBuild = history && !history.empty ? history.builds[0] : null; const coherencePct = latestBuild?.coherenceScore != null ? Math.round(latestBuild.coherenceScore * 100) : null; return (
{data.nodes.length} nodes {data.levels + 1} levels Built {formatDate(data.builtAt ?? 0)} {latestBuild && ( <> Coherence:{" "} = 70 ? "#22c55e" : "#f59e0b" }}> {coherencePct != null ? `${coherencePct}%` : "—"} Leaves: {latestBuild.leafCount} Depth: {latestBuild.depth} {latestBuild.timedOut && ( budget timeout )} )}
{history && !history.empty && history.builds.length > 1 && (
Build history ({history.builds.length} builds)
{history.builds.slice(0, 10).map((b) => (
{formatDate(b.completedAt)} {b.nodeCount} nodes {b.leafCount} leaves depth {b.depth} coherence{" "} {b.coherenceScore != null ? `${Math.round(b.coherenceScore * 100)}%` : "—"} {b.timedOut && timeout}
))}
)} {data.nodes.map((node) => ( ))}
); }