/**
* 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) => (
))}
);
}