pi-mega-compact 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
- package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
- package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
- package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
- package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
- package/dist/extensions/dashboard-server/routes-setup.js +1 -1
- package/dist/extensions/dashboard-server/routes.js +2 -0
- package/dist/extensions/dashboard-server/server.js +5 -1
- package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
- package/dist/extensions/mega-commands/dataCommands.js +151 -0
- package/dist/extensions/mega-commands/helpers.js +61 -0
- package/dist/extensions/mega-commands/historyCommands.js +97 -0
- package/dist/extensions/mega-commands/setupCommand.js +200 -0
- package/dist/extensions/mega-commands.js +14 -499
- package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
- package/dist/extensions/mega-compact.test/_helpers.js +225 -0
- package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
- package/dist/extensions/mega-events/agent-handlers.js +4 -799
- package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
- package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
- package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
- package/dist/extensions/mega-events/context-handler.js +11 -198
- package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
- package/dist/src/config.js +2 -0
- package/dist/src/dedup-engine.test/_helpers.js +55 -0
- package/dist/src/e2e.test/_helpers.js +52 -0
- package/dist/src/embedder.js +1 -0
- package/dist/src/httpEmbedder.js +6 -0
- package/dist/src/hyde.js +146 -0
- package/dist/src/importance.test/_helpers.js +5 -0
- package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
- package/dist/src/memoryGraph/sources/edges.js +17 -0
- package/dist/src/memoryGraph/sources/flags.js +22 -0
- package/dist/src/memoryGraph/sources/helpers.js +14 -0
- package/dist/src/memoryGraph/sources/memories.js +88 -0
- package/dist/src/memoryGraph/sources/raptor.js +26 -0
- package/dist/src/memoryGraph/sources/turns.js +176 -0
- package/dist/src/memoryGraph/sources.js +15 -441
- package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
- package/dist/src/ratio.bench.test/_helpers.js +198 -0
- package/dist/src/recall/async.js +112 -0
- package/dist/src/recall/format.js +149 -0
- package/dist/src/recall/memory.js +62 -0
- package/dist/src/recall/reformulate.js +82 -0
- package/dist/src/recall/sync.js +169 -0
- package/dist/src/recall/types.js +1 -0
- package/dist/src/recall.js +13 -530
- package/dist/src/store/sqlite/schema/core.js +232 -0
- package/dist/src/store/sqlite/schema/game.js +65 -0
- package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
- package/dist/src/store/sqlite/schema/turns.js +59 -0
- package/dist/src/store/sqlite/schema.js +8 -441
- package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
- package/dist/src/store/turns/sqlite-store/admin.js +171 -0
- package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
- package/dist/src/store/turns/sqlite-store/read.js +111 -0
- package/dist/src/store/turns/sqlite-store/rows.js +42 -0
- package/dist/src/store/turns/sqlite-store/write.js +102 -0
- package/dist/src/store/turns/sqlite-store.js +31 -367
- package/dist/src/vectorStore/class.js +356 -0
- package/dist/src/vectorStore/hash.js +10 -0
- package/dist/src/vectorStore/types.js +2 -0
- package/dist/src/vectorStore.js +8 -359
- package/dist/src/vectorStore.test/_helpers.js +17 -0
- package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js → CacheTab-DPRHjYZ0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js → EventsTab-CgwYq-Qf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js → HealthTab-BgOCjVzR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js → MetricsTab-CcH9fP8P.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js → OverviewTab-BOs8ZNev.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js → ReposTab-B-MfSPvg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js → SessionsTab-BmeN649Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js → TopicsTab-BODHOg_b.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js → TurnsTab-BK0I7hKr.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-Cucgohga.js → index-mOlljTFI.js} +9 -9
- package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js → useSSE-BYOtv1-3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js.map → useSSE-BYOtv1-3.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/client.ts +16 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +13 -483
- package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
- package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
- package/extensions/dashboard-server/api-contracts/index.ts +6 -0
- package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
- package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
- package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
- package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
- package/extensions/dashboard-server/routes-raptor.ts +128 -0
- package/extensions/dashboard-server/routes-setup.ts +1 -1
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +4 -0
- package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
- package/extensions/mega-commands/dataCommands.ts +169 -0
- package/extensions/mega-commands/helpers.ts +62 -0
- package/extensions/mega-commands/historyCommands.ts +117 -0
- package/extensions/mega-commands/setupCommand.ts +235 -0
- package/extensions/mega-commands.ts +25 -538
- package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
- package/extensions/mega-compact.test/_helpers.ts +237 -0
- package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
- package/extensions/mega-events/agent-handlers.ts +15 -861
- package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
- package/extensions/mega-events/context-handler/messageText.ts +20 -0
- package/extensions/mega-events/context-handler/tailResult.ts +63 -0
- package/extensions/mega-events/context-handler.ts +17 -235
- package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/dedup-engine.test/_helpers.ts +79 -0
- package/src/e2e.test/_helpers.ts +59 -0
- package/src/embedder.ts +4 -0
- package/src/httpEmbedder.ts +7 -0
- package/src/hyde.ts +162 -0
- package/src/importance.test/_helpers.ts +5 -0
- package/src/memoryGraph/sources/checkpoints.ts +99 -0
- package/src/memoryGraph/sources/edges.ts +23 -0
- package/src/memoryGraph/sources/flags.ts +26 -0
- package/src/memoryGraph/sources/helpers.ts +13 -0
- package/src/memoryGraph/sources/memories.ts +108 -0
- package/src/memoryGraph/sources/raptor.ts +37 -0
- package/src/memoryGraph/sources/turns.ts +226 -0
- package/src/memoryGraph/sources.ts +19 -544
- package/src/memoryGraph-gates.test/_helpers.ts +164 -0
- package/src/ratio.bench.test/_helpers.ts +268 -0
- package/src/recall/async.ts +142 -0
- package/src/recall/format.ts +173 -0
- package/src/recall/memory.ts +90 -0
- package/src/recall/reformulate.ts +111 -0
- package/src/recall/sync.ts +196 -0
- package/src/recall/types.ts +63 -0
- package/src/recall.ts +25 -712
- package/src/store/sqlite/schema/core.ts +233 -0
- package/src/store/sqlite/schema/game.ts +66 -0
- package/src/store/sqlite/schema/plan-v2.ts +108 -0
- package/src/store/sqlite/schema/turns.ts +60 -0
- package/src/store/sqlite/schema.ts +13 -441
- package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
- package/src/store/turns/sqlite-store/admin.ts +249 -0
- package/src/store/turns/sqlite-store/ctx.ts +14 -0
- package/src/store/turns/sqlite-store/read.ts +174 -0
- package/src/store/turns/sqlite-store/rows.ts +55 -0
- package/src/store/turns/sqlite-store/write.ts +193 -0
- package/src/store/turns/sqlite-store.ts +33 -514
- package/src/vectorStore/class.ts +433 -0
- package/src/vectorStore/hash.ts +11 -0
- package/src/vectorStore/types.ts +58 -0
- package/src/vectorStore.test/_helpers.ts +20 -0
- package/src/vectorStore.ts +13 -489
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-Cucgohga.js.map +0 -1
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryMapTab/MemoryMapView.tsx — D3 memory map with force-directed graph.
|
|
3
|
+
* Node shape encodes nodeType: checkpoint=filled circle, turn=hollow circle,
|
|
4
|
+
* turn-content=hollow+ring, memory=diamond.
|
|
5
|
+
* Fetches from /api/memory-map, renders SVG force layout.
|
|
6
|
+
*/
|
|
7
|
+
import type React from "react";
|
|
8
|
+
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
|
|
9
|
+
import { renderNodeShape, NODE_COLORS, NODE_TYPE_LABELS } from "../../memory-map-shapes.js";
|
|
10
|
+
import type { NodeType } from "../../memory-map-shapes.js";
|
|
11
|
+
import { buildLayout, applyForces } from "../../memory-map-layout.js";
|
|
12
|
+
import type { LayoutNode, LayoutEdge } from "../../memory-map-layout.js";
|
|
13
|
+
|
|
14
|
+
// Types (mirror of api-contracts/memory-map.ts — client-side only)
|
|
15
|
+
|
|
16
|
+
interface GraphNode {
|
|
17
|
+
id: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
label: string;
|
|
20
|
+
summaryTruncated: string;
|
|
21
|
+
tokenEstimate: number;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
dedupStatus: string | undefined;
|
|
24
|
+
raptorLevel: number;
|
|
25
|
+
topicSummary: string | undefined;
|
|
26
|
+
decisionCount: number;
|
|
27
|
+
textSnippet: string;
|
|
28
|
+
/** Source type discriminator for UI node-shape encoding. */
|
|
29
|
+
nodeType: "checkpoint" | "turn" | "turn-content" | "memory";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface GraphEdge {
|
|
33
|
+
source: string;
|
|
34
|
+
target: string;
|
|
35
|
+
weight: number;
|
|
36
|
+
type: "temporal" | "semantic" | "raptor_parent";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface GraphValidationReport {
|
|
40
|
+
readonly gatesRun: number;
|
|
41
|
+
readonly gatesPassed: number;
|
|
42
|
+
readonly dropped: { nodes: number; edges: number };
|
|
43
|
+
readonly warnings: Array<{ gate: string; code: string; count: number }>;
|
|
44
|
+
readonly sources: {
|
|
45
|
+
checkpoint: number;
|
|
46
|
+
turn: number;
|
|
47
|
+
turnContent: number;
|
|
48
|
+
memory: number;
|
|
49
|
+
};
|
|
50
|
+
readonly builtAt: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface GraphMetadata {
|
|
54
|
+
totalNodes: number;
|
|
55
|
+
totalEdges: number;
|
|
56
|
+
avgWeight: number;
|
|
57
|
+
nodeTypeBreakdown: Record<string, number>;
|
|
58
|
+
edgeTypeBreakdown: Record<string, number>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface MemoryMapResponse {
|
|
62
|
+
nodes: GraphNode[];
|
|
63
|
+
edges: GraphEdge[];
|
|
64
|
+
metadata: GraphMetadata;
|
|
65
|
+
/** Validation report from the 9-gate pipeline. Optional for backward compat. */
|
|
66
|
+
validation?: GraphValidationReport;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Simulation rendering constants (layout math is in memory-map-layout.ts)
|
|
70
|
+
|
|
71
|
+
const MAX_ITERATIONS = 300;
|
|
72
|
+
const EDGE_ALPHA = 0.3;
|
|
73
|
+
const SVG_WIDTH = 900;
|
|
74
|
+
const SVG_HEIGHT = 600;
|
|
75
|
+
const PADDING = 40;
|
|
76
|
+
|
|
77
|
+
// Helpers
|
|
78
|
+
|
|
79
|
+
function edgeColor(type: GraphEdge["type"]): string {
|
|
80
|
+
switch (type) {
|
|
81
|
+
case "temporal":
|
|
82
|
+
return "#6366f1"; // indigo
|
|
83
|
+
case "semantic":
|
|
84
|
+
return "#22c55e"; // green
|
|
85
|
+
case "raptor_parent":
|
|
86
|
+
return "#f59e0b"; // amber
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Minimal legend swatch component
|
|
91
|
+
interface SwatchProps {
|
|
92
|
+
type: "node" | "edge";
|
|
93
|
+
color: string;
|
|
94
|
+
shape: "filled-circle" | "hollow-circle" | "hollow-ring" | "diamond" | "line";
|
|
95
|
+
label: string;
|
|
96
|
+
}
|
|
97
|
+
const Swatch: React.FC<SwatchProps> = ({ color, shape, label }) => {
|
|
98
|
+
const base: React.CSSProperties = {
|
|
99
|
+
display: "inline-block",
|
|
100
|
+
width: "10px",
|
|
101
|
+
height: "10px",
|
|
102
|
+
verticalAlign: "middle",
|
|
103
|
+
marginRight: "3px",
|
|
104
|
+
borderRadius: shape.startsWith("hollow") || shape === "filled-circle" ? "50%" : undefined,
|
|
105
|
+
};
|
|
106
|
+
let swatch: JSX.Element;
|
|
107
|
+
if (shape === "line") {
|
|
108
|
+
return <span style={{ color, marginRight: "6px" }}>{label}</span>;
|
|
109
|
+
} else if (shape === "filled-circle") {
|
|
110
|
+
swatch = <span style={{ ...base, backgroundColor: color }} />;
|
|
111
|
+
} else if (shape === "hollow-circle") {
|
|
112
|
+
swatch = <span style={{ ...base, border: `2px solid ${color}`, backgroundColor: "transparent" }} />;
|
|
113
|
+
} else if (shape === "hollow-ring") {
|
|
114
|
+
swatch = (
|
|
115
|
+
<span style={{ ...base, border: `2px solid ${color}`, backgroundColor: "transparent", position: "relative" }}>
|
|
116
|
+
<span style={{ display: "block", width: "4px", height: "4px", borderRadius: "50%", backgroundColor: color, margin: "2px auto" }} />
|
|
117
|
+
</span>
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
swatch = <span style={{ ...base, clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)", backgroundColor: color }} />;
|
|
121
|
+
}
|
|
122
|
+
return <><span className="memory-map-legend-item">{swatch}{label}</span> </>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Force simulation engine (delegated to memory-map-layout.ts)
|
|
126
|
+
const MemoryMapView: React.FC = () => {
|
|
127
|
+
const [data, setData] = useState<MemoryMapResponse | null>(null);
|
|
128
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
129
|
+
const [error, setError] = useState<string | null>(null);
|
|
130
|
+
const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
|
|
131
|
+
const [searchQuery, setSearchQuery] = useState<string>("");
|
|
132
|
+
const [filterSession] = useState<string>("");
|
|
133
|
+
const [frame, setFrame] = useState<number>(0);
|
|
134
|
+
|
|
135
|
+
const layoutRef = useRef<{ nodes: LayoutNode[]; edges: LayoutEdge[] } | null>(null);
|
|
136
|
+
const animRef = useRef<number>(0);
|
|
137
|
+
|
|
138
|
+
// Fetch graph data when filterSession changes
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
let cancelled = false;
|
|
141
|
+
const params = new URLSearchParams();
|
|
142
|
+
if (filterSession) params.set("sessionId", filterSession);
|
|
143
|
+
params.set("threshold", "0.7");
|
|
144
|
+
params.set("maxEdgesPerNode", "4");
|
|
145
|
+
|
|
146
|
+
setLoading(true);
|
|
147
|
+
setError(null);
|
|
148
|
+
|
|
149
|
+
fetch(`/api/memory-map?${params.toString()}`)
|
|
150
|
+
.then((r) => {
|
|
151
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
152
|
+
return r.json() as Promise<MemoryMapResponse>;
|
|
153
|
+
})
|
|
154
|
+
.then((d) => {
|
|
155
|
+
if (!cancelled) {
|
|
156
|
+
setData(d);
|
|
157
|
+
layoutRef.current = buildLayout(d);
|
|
158
|
+
setFrame(1);
|
|
159
|
+
setLoading(false);
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
.catch((e: unknown) => {
|
|
163
|
+
if (!cancelled) {
|
|
164
|
+
setError(e instanceof Error ? e.message : "Unknown error");
|
|
165
|
+
setLoading(false);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return () => {
|
|
170
|
+
cancelled = true;
|
|
171
|
+
cancelAnimationFrame(animRef.current);
|
|
172
|
+
};
|
|
173
|
+
}, [filterSession]);
|
|
174
|
+
|
|
175
|
+
// Run force simulation via requestAnimationFrame
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!layoutRef.current || frame === 0) return;
|
|
178
|
+
|
|
179
|
+
const layout = layoutRef.current;
|
|
180
|
+
let iter = 0;
|
|
181
|
+
|
|
182
|
+
const tick = () => {
|
|
183
|
+
if (iter >= MAX_ITERATIONS) return;
|
|
184
|
+
applyForces(layout);
|
|
185
|
+
iter++;
|
|
186
|
+
setFrame(iter);
|
|
187
|
+
animRef.current = requestAnimationFrame(tick);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
animRef.current = requestAnimationFrame(tick);
|
|
191
|
+
return () => cancelAnimationFrame(animRef.current);
|
|
192
|
+
}, [frame]);
|
|
193
|
+
|
|
194
|
+
// Toggle pin / select a node
|
|
195
|
+
const handleNodeClick = useCallback((node: LayoutNode, index: number) => {
|
|
196
|
+
const layout = layoutRef.current;
|
|
197
|
+
if (!layout) return;
|
|
198
|
+
layout.nodes[index].pinned = !layout.nodes[index].pinned;
|
|
199
|
+
if (!layout.nodes[index].pinned) {
|
|
200
|
+
layout.nodes[index].vx = 0;
|
|
201
|
+
layout.nodes[index].vy = 0;
|
|
202
|
+
}
|
|
203
|
+
setSelectedNode((prev) => (prev?.id === node.id ? null : (node as unknown as GraphNode)));
|
|
204
|
+
setFrame((s) => s + 1);
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
// Filtered node indices for search
|
|
208
|
+
const filteredSet = useMemo<Set<number> | null>(() => {
|
|
209
|
+
const layout = layoutRef.current;
|
|
210
|
+
if (!layout || !searchQuery) return null;
|
|
211
|
+
const q = searchQuery.toLowerCase();
|
|
212
|
+
return new Set(
|
|
213
|
+
layout.nodes
|
|
214
|
+
.map((n, i) =>
|
|
215
|
+
n.summaryTruncated.toLowerCase().includes(q) || n.label.toLowerCase().includes(q) ? i : -1,
|
|
216
|
+
)
|
|
217
|
+
.filter((i) => i >= 0),
|
|
218
|
+
);
|
|
219
|
+
}, [searchQuery]);
|
|
220
|
+
|
|
221
|
+
// Health indicator helpers (D3)
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Derive graph health from validation report:
|
|
225
|
+
* green = all gates passed + no warnings
|
|
226
|
+
* yellow = warnings present but no critical gate failed
|
|
227
|
+
* red = a critical gate (gatesRun > gatesPassed) failed
|
|
228
|
+
*/
|
|
229
|
+
const graphHealth = useMemo<{ level: "green" | "yellow" | "red"; label: string }>(() => {
|
|
230
|
+
const v = data?.validation;
|
|
231
|
+
if (!v) return { level: "yellow", label: "No validation" };
|
|
232
|
+
if (v.gatesPassed < v.gatesRun) return { level: "red", label: "Gate failure" };
|
|
233
|
+
if (v.warnings.length > 0) return { level: "yellow", label: `${v.warnings.length} warning(s)` };
|
|
234
|
+
return { level: "green", label: "Healthy" };
|
|
235
|
+
}, [data?.validation]);
|
|
236
|
+
|
|
237
|
+
const healthColor: Record<string, string> = {
|
|
238
|
+
green: "#22c55e",
|
|
239
|
+
yellow: "#eab308",
|
|
240
|
+
red: "#ef4444",
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Source availability indicators (D3)
|
|
244
|
+
|
|
245
|
+
/** Count badge fragments from validation.sources. */
|
|
246
|
+
const countBadge = useMemo<string>(() => {
|
|
247
|
+
const v = data?.validation;
|
|
248
|
+
if (!v) {
|
|
249
|
+
const totalNodes = data?.metadata.totalNodes ?? 0;
|
|
250
|
+
return `${totalNodes} nodes`;
|
|
251
|
+
}
|
|
252
|
+
const parts: string[] = [];
|
|
253
|
+
if (v.sources.checkpoint > 0) parts.push(`${v.sources.checkpoint} checkpoints`);
|
|
254
|
+
if (v.sources.turn > 0) parts.push(`${v.sources.turn} turns`);
|
|
255
|
+
if (v.sources.turnContent > 0) parts.push(`${v.sources.turnContent} turn-content`);
|
|
256
|
+
if (v.sources.memory > 0) parts.push(`${v.sources.memory} memories`);
|
|
257
|
+
if (parts.length === 0) {
|
|
258
|
+
parts.push(`${data?.metadata.totalNodes ?? 0} nodes`);
|
|
259
|
+
}
|
|
260
|
+
return parts.join(" · ");
|
|
261
|
+
}, [data?.validation, data?.metadata.totalNodes]);
|
|
262
|
+
|
|
263
|
+
/** Per-source availability: ✓ for available sources, ✗ for empty ones. */
|
|
264
|
+
const sourceAvail = useMemo<string>(() => {
|
|
265
|
+
const v = data?.validation;
|
|
266
|
+
if (!v) return "";
|
|
267
|
+
const parts: string[] = [];
|
|
268
|
+
parts.push(v.sources.checkpoint > 0 ? "✓ checkpoints" : "✗ checkpoints");
|
|
269
|
+
parts.push(v.sources.turn > 0 ? "✓ turns" : "✗ turns");
|
|
270
|
+
parts.push(v.sources.turnContent > 0 ? "✓ turn-content" : "✗ turn-content");
|
|
271
|
+
parts.push(v.sources.memory > 0 ? "✓ memories" : "✗ memories");
|
|
272
|
+
return parts.join(" | ");
|
|
273
|
+
}, [data?.validation]);
|
|
274
|
+
|
|
275
|
+
/** Whether all four sources are empty (true empty state). */
|
|
276
|
+
const allSourcesEmpty = useMemo<boolean>(() => {
|
|
277
|
+
const v = data?.validation;
|
|
278
|
+
if (!v) return data?.metadata.totalNodes === 0;
|
|
279
|
+
return (
|
|
280
|
+
v.sources.checkpoint === 0 &&
|
|
281
|
+
v.sources.turn === 0 &&
|
|
282
|
+
v.sources.turnContent === 0 &&
|
|
283
|
+
v.sources.memory === 0
|
|
284
|
+
);
|
|
285
|
+
}, [data?.validation, data?.metadata.totalNodes]);
|
|
286
|
+
|
|
287
|
+
// Loading state
|
|
288
|
+
|
|
289
|
+
if (loading) {
|
|
290
|
+
return <div className="memory-map-loading">Loading memory graph...</div>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Error state
|
|
294
|
+
|
|
295
|
+
if (error) {
|
|
296
|
+
return (
|
|
297
|
+
<div className="memory-map-error">
|
|
298
|
+
<p>Failed to load memory map: {error}</p>
|
|
299
|
+
<button
|
|
300
|
+
className="memory-map-retry-btn"
|
|
301
|
+
onClick={() => setFrame((s) => s + 1)}
|
|
302
|
+
>
|
|
303
|
+
Retry
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Empty state (D2)
|
|
310
|
+
|
|
311
|
+
if (!data || !layoutRef.current || allSourcesEmpty) {
|
|
312
|
+
return (
|
|
313
|
+
<div className="memory-map-empty">
|
|
314
|
+
<p>Memories appear after your first compaction. The graph shows checkpoints (compaction summaries) linked by semantic similarity and time. Run a longer session or lower the compaction tier to see it sooner.</p>
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const layout = layoutRef.current;
|
|
320
|
+
|
|
321
|
+
// Render
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<div className="memory-map-container">
|
|
325
|
+
{/* Toolbar */}
|
|
326
|
+
<div className="memory-map-toolbar">
|
|
327
|
+
<input
|
|
328
|
+
type="text"
|
|
329
|
+
placeholder="Search memories..."
|
|
330
|
+
value={searchQuery}
|
|
331
|
+
onInput={(e: React.FormEvent<HTMLInputElement>) =>
|
|
332
|
+
setSearchQuery((e.target as HTMLInputElement).value)
|
|
333
|
+
}
|
|
334
|
+
className="memory-map-search"
|
|
335
|
+
/>
|
|
336
|
+
<span className="memory-map-stats">
|
|
337
|
+
{/* Count badge */}
|
|
338
|
+
<span className="memory-map-count-badge">{countBadge}</span>
|
|
339
|
+
<br />
|
|
340
|
+
<span className="memory-map-edges">{data.edges.length} edges</span>
|
|
341
|
+
</span>
|
|
342
|
+
{/* Graph-health indicator */}
|
|
343
|
+
<span
|
|
344
|
+
className="memory-map-health-badge"
|
|
345
|
+
style={{
|
|
346
|
+
backgroundColor: healthColor[graphHealth.level] ?? "#6b7280",
|
|
347
|
+
color: "#fff",
|
|
348
|
+
padding: "2px 8px",
|
|
349
|
+
borderRadius: "10px",
|
|
350
|
+
fontSize: "11px",
|
|
351
|
+
fontWeight: 600,
|
|
352
|
+
marginLeft: "8px",
|
|
353
|
+
}}
|
|
354
|
+
title={`Validation: ${graphHealth.label}`}
|
|
355
|
+
>
|
|
356
|
+
{graphHealth.level === "green" ? "Healthy" : graphHealth.level === "yellow" ? "Warnings" : "Critical"}
|
|
357
|
+
</span>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
{/* Source availability indicators */}
|
|
361
|
+
{sourceAvail ? (
|
|
362
|
+
<div className="memory-map-source-avail" style={{ fontSize: "11px", padding: "2px 12px", color: "#6b7280" }}>
|
|
363
|
+
{sourceAvail}
|
|
364
|
+
</div>
|
|
365
|
+
) : null}
|
|
366
|
+
|
|
367
|
+
{/* Legend — node types + edge types */}
|
|
368
|
+
<div className="memory-map-legend">
|
|
369
|
+
<span className="memory-map-legend-section">Nodes:</span>
|
|
370
|
+
<Swatch type="node" color={NODE_COLORS.checkpoint} shape="filled-circle" label="Checkpoint" />
|
|
371
|
+
<Swatch type="node" color={NODE_COLORS.turn} shape="hollow-circle" label="Turn" />
|
|
372
|
+
<Swatch type="node" color={NODE_COLORS["turn-content"]} shape="hollow-ring" label="Turn Content" />
|
|
373
|
+
<Swatch type="node" color={NODE_COLORS.memory} shape="diamond" label="Memory" />
|
|
374
|
+
<span className="memory-map-legend-sep">|</span>
|
|
375
|
+
<span className="memory-map-legend-section">Edges:</span>
|
|
376
|
+
<Swatch type="edge" color={edgeColor("semantic")} shape="line" label="Semantic" />
|
|
377
|
+
<Swatch type="edge" color={edgeColor("temporal")} shape="line" label="Temporal" />
|
|
378
|
+
<Swatch type="edge" color={edgeColor("raptor_parent")} shape="line" label="Raptor" />
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
{/* SVG graph */}
|
|
382
|
+
<svg
|
|
383
|
+
width={SVG_WIDTH}
|
|
384
|
+
height={SVG_HEIGHT}
|
|
385
|
+
viewBox={`0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`}
|
|
386
|
+
className="memory-map-svg"
|
|
387
|
+
>
|
|
388
|
+
{/* Edges */}
|
|
389
|
+
{layout.edges.map((e, i) => {
|
|
390
|
+
const src = layout.nodes[e.source];
|
|
391
|
+
const tgt = layout.nodes[e.target];
|
|
392
|
+
const color = edgeColor(e.type);
|
|
393
|
+
const strokeW = Math.max(1, e.weight * 2.5);
|
|
394
|
+
return (
|
|
395
|
+
<line
|
|
396
|
+
key={`edge-${i}`}
|
|
397
|
+
x1={PADDING + (src.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING)}
|
|
398
|
+
y1={PADDING + (src.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING)}
|
|
399
|
+
x2={PADDING + (tgt.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING)}
|
|
400
|
+
y2={PADDING + (tgt.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING)}
|
|
401
|
+
stroke={color}
|
|
402
|
+
strokeWidth={strokeW}
|
|
403
|
+
opacity={EDGE_ALPHA}
|
|
404
|
+
/>
|
|
405
|
+
);
|
|
406
|
+
})}
|
|
407
|
+
|
|
408
|
+
{/* Nodes — shape encodes nodeType */}
|
|
409
|
+
{layout.nodes.map((n, i) => {
|
|
410
|
+
const cx =
|
|
411
|
+
PADDING + (n.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING);
|
|
412
|
+
const cy =
|
|
413
|
+
PADDING + (n.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING);
|
|
414
|
+
const radius = Math.min(20, 8 + n.tokenEstimate / 500);
|
|
415
|
+
const isFiltered = filteredSet ? filteredSet.has(i) : true;
|
|
416
|
+
const isSelected = selectedNode?.id === n.id;
|
|
417
|
+
const nodeLabel = n.label.length > 20 ? n.label.slice(0, 18) + "..." : n.label;
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<g
|
|
421
|
+
key={`node-${i}`}
|
|
422
|
+
className="memory-map-node-group"
|
|
423
|
+
onClick={() => handleNodeClick(n, i)}
|
|
424
|
+
style={{ cursor: "pointer" }}
|
|
425
|
+
>
|
|
426
|
+
{renderNodeShape(cx, cy, radius, n.nodeType as NodeType, isSelected, isFiltered, n.dedupStatus)}
|
|
427
|
+
<text
|
|
428
|
+
x={cx}
|
|
429
|
+
y={cy + radius + 12}
|
|
430
|
+
textAnchor="middle"
|
|
431
|
+
fontSize="9px"
|
|
432
|
+
fill="#374151"
|
|
433
|
+
className="memory-map-label"
|
|
434
|
+
>
|
|
435
|
+
{nodeLabel}
|
|
436
|
+
</text>
|
|
437
|
+
</g>
|
|
438
|
+
);
|
|
439
|
+
})}
|
|
440
|
+
</svg>
|
|
441
|
+
|
|
442
|
+
{/* Node detail panel */}
|
|
443
|
+
{selectedNode ? (
|
|
444
|
+
<div className="memory-map-detail">
|
|
445
|
+
<h3>
|
|
446
|
+
<span
|
|
447
|
+
className="memory-map-detail-type"
|
|
448
|
+
style={{
|
|
449
|
+
display: "inline-block",
|
|
450
|
+
fontSize: "12px",
|
|
451
|
+
fontWeight: 400,
|
|
452
|
+
color: "#6b7280",
|
|
453
|
+
marginRight: "6px",
|
|
454
|
+
}}
|
|
455
|
+
>
|
|
456
|
+
{NODE_TYPE_LABELS[selectedNode.nodeType]}
|
|
457
|
+
</span>
|
|
458
|
+
{selectedNode.label}
|
|
459
|
+
</h3>
|
|
460
|
+
<p>{selectedNode.summaryTruncated}</p>
|
|
461
|
+
{selectedNode.topicSummary ? (
|
|
462
|
+
<p className="memory-map-topic">Topic: {selectedNode.topicSummary}</p>
|
|
463
|
+
) : null}
|
|
464
|
+
<p>
|
|
465
|
+
Tokens: {selectedNode.tokenEstimate} | Decisions: {selectedNode.decisionCount}
|
|
466
|
+
</p>
|
|
467
|
+
<p>Session: {selectedNode.sessionId}</p>
|
|
468
|
+
<p>Raptor Level: {selectedNode.raptorLevel}</p>
|
|
469
|
+
<p className="memory-map-snippet">{selectedNode.textSnippet}</p>
|
|
470
|
+
</div>
|
|
471
|
+
) : null}
|
|
472
|
+
</div>
|
|
473
|
+
);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
export default MemoryMapView;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryMapTab/RaptorTreeView.tsx — RAPTOR tree sub-tab (Part B).
|
|
3
|
+
*
|
|
4
|
+
* Fetches the hierarchical RAPTOR tree from /api/raptor-tree (defaults to the
|
|
5
|
+
* most recent session with nodes) and renders it as level-indented summary
|
|
6
|
+
* cards. No D3 needed — CSS indentation encodes hierarchy.
|
|
7
|
+
*/
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { useEffect, useState } from "react";
|
|
10
|
+
import { fetchRaptorTree } from "../../api/client";
|
|
11
|
+
import type { RaptorTreeResponse, RaptorNodeDTO } from "@contracts";
|
|
12
|
+
|
|
13
|
+
/** Per-level border color for node cards. */
|
|
14
|
+
function levelColor(level: number): string {
|
|
15
|
+
if (level === 0) return "#d4a017"; // gold — root
|
|
16
|
+
if (level === 1) return "#3b82f6"; // blue
|
|
17
|
+
return "#14b8a6"; // teal — level 2+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatDate(ts: number): string {
|
|
21
|
+
if (!ts) return "—";
|
|
22
|
+
return new Date(ts).toLocaleString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const containerStyle: React.CSSProperties = {
|
|
26
|
+
padding: "1rem",
|
|
27
|
+
width: "100%",
|
|
28
|
+
boxSizing: "border-box",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const headerStyle: React.CSSProperties = {
|
|
32
|
+
display: "flex",
|
|
33
|
+
gap: "1.5rem",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
marginBottom: "1rem",
|
|
36
|
+
color: "#a0a0c0",
|
|
37
|
+
fontSize: "0.9rem",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const emptyStyle: React.CSSProperties = {
|
|
41
|
+
padding: "2rem",
|
|
42
|
+
border: "1px dashed #2a2a4e",
|
|
43
|
+
borderRadius: "8px",
|
|
44
|
+
color: "#a0a0c0",
|
|
45
|
+
textAlign: "center",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const loadingStyle: React.CSSProperties = {
|
|
49
|
+
padding: "1rem",
|
|
50
|
+
color: "#a0a0c0",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function NodeCard({ node }: { node: RaptorNodeDTO }): React.ReactElement {
|
|
54
|
+
const border = levelColor(node.level);
|
|
55
|
+
const childrenLabel =
|
|
56
|
+
node.children.length > 0
|
|
57
|
+
? `${node.children.length} child${node.children.length === 1 ? "" : "ren"}`
|
|
58
|
+
: "leaf";
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
style={{
|
|
62
|
+
background: "#14142b",
|
|
63
|
+
border: `1px solid ${border}`,
|
|
64
|
+
borderRadius: "8px",
|
|
65
|
+
padding: "0.75rem 1rem",
|
|
66
|
+
marginBottom: "0.6rem",
|
|
67
|
+
marginLeft: node.level * 24,
|
|
68
|
+
maxWidth: "90%",
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<div style={{ display: "flex", gap: "0.75rem", alignItems: "center", marginBottom: "0.35rem" }}>
|
|
72
|
+
<span
|
|
73
|
+
style={{
|
|
74
|
+
display: "inline-block",
|
|
75
|
+
background: border,
|
|
76
|
+
color: "#fff",
|
|
77
|
+
borderRadius: "10px",
|
|
78
|
+
padding: "1px 8px",
|
|
79
|
+
fontSize: "11px",
|
|
80
|
+
fontWeight: 600,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
L{node.level}
|
|
84
|
+
</span>
|
|
85
|
+
<span style={{ fontSize: "12px", color: "#6b7280" }}>
|
|
86
|
+
{childrenLabel} · {node.tokenEstimate} tok · {node.qualityMarker}
|
|
87
|
+
</span>
|
|
88
|
+
<span style={{ fontSize: "12px", color: "#6b7280", marginLeft: "auto" }}>
|
|
89
|
+
{formatDate(node.builtAt)}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div style={{ fontSize: "0.92rem", color: "#e5e7eb" }}>{node.summary}</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default function RaptorTreeView(): React.ReactElement {
|
|
98
|
+
const [data, setData] = useState<RaptorTreeResponse | null>(null);
|
|
99
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
100
|
+
const [error, setError] = useState<string | null>(null);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
let cancelled = false;
|
|
104
|
+
setLoading(true);
|
|
105
|
+
setError(null);
|
|
106
|
+
fetchRaptorTree()
|
|
107
|
+
.then((d) => {
|
|
108
|
+
if (!cancelled) {
|
|
109
|
+
setData(d);
|
|
110
|
+
setLoading(false);
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.catch((e: unknown) => {
|
|
114
|
+
if (!cancelled) {
|
|
115
|
+
setError(e instanceof Error ? e.message : "Unknown error");
|
|
116
|
+
setLoading(false);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
if (loading) {
|
|
125
|
+
return <div style={loadingStyle}>Loading RAPTOR tree...</div>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (error) {
|
|
129
|
+
return (
|
|
130
|
+
<div style={emptyStyle}>
|
|
131
|
+
<p>Failed to load RAPTOR tree: {error}</p>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!data || data.empty || data.nodes.length === 0) {
|
|
137
|
+
return (
|
|
138
|
+
<div style={emptyStyle}>
|
|
139
|
+
<p>
|
|
140
|
+
No RAPTOR tree built yet — run a compaction (the tree builds during
|
|
141
|
+
compaction).
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div style={containerStyle}>
|
|
149
|
+
<div style={headerStyle}>
|
|
150
|
+
<span>
|
|
151
|
+
<strong>{data.nodes.length}</strong> nodes
|
|
152
|
+
</span>
|
|
153
|
+
<span>
|
|
154
|
+
<strong>{data.levels + 1}</strong> levels
|
|
155
|
+
</span>
|
|
156
|
+
<span>Built {formatDate(data.builtAt ?? 0)}</span>
|
|
157
|
+
</div>
|
|
158
|
+
{data.nodes.map((node) => (
|
|
159
|
+
<NodeCard key={node.id} node={node} />
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|