pi-mega-compact 0.11.7 → 0.11.8
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/routes-memory-map.js +8 -15
- package/dist/extensions/mega-commands.js +65 -5
- package/dist/extensions/mega-config.js +6 -0
- package/dist/extensions/mega-events/context-handler.js +65 -0
- package/dist/src/config/turns.js +2 -1
- package/dist/src/memoryGraph/embedding.js +50 -0
- package/dist/src/memoryGraph/gates.js +379 -0
- package/dist/src/memoryGraph/sources.js +390 -0
- package/dist/src/memoryGraph.js +133 -259
- package/dist/src/store/sqlite/embedding-cache.js +71 -0
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/topics/cluster.js +10 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-BESF1HT1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-BESF1HT1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-8RzNnz8u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-8RzNnz8u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js → CacheTab-CUIiUgh3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js.map → CacheTab-CUIiUgh3.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-D85fB9HP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-D85fB9HP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-TCLR81G5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-TCLR81G5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-BqneNRYH.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-BqneNRYH.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js → MaintenanceTab-LblMg3NB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js.map → MaintenanceTab-LblMg3NB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-LxNtNzLQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-LxNtNzLQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-BXKUZbT6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-BXKUZbT6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-DTooKeAi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-DTooKeAi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-Devulgm9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-Devulgm9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-By9eqUoE.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-By9eqUoE.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-z4M8TDLm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-z4M8TDLm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-m_XzE0H4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-m_XzE0H4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-PRkilPXw.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-PRkilPXw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-CGzgc2IK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-CGzgc2IK.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
- package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
- package/extensions/dashboard-server/api-contracts/index.ts +2 -1
- package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
- package/extensions/dashboard-server/routes-memory-map.ts +21 -29
- package/extensions/mega-commands.ts +63 -6
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-events/context-handler.ts +80 -0
- package/package.json +1 -1
- package/src/config/turns.ts +5 -2
- package/src/memoryGraph/embedding.ts +63 -0
- package/src/memoryGraph/gates.ts +451 -0
- package/src/memoryGraph/sources.ts +484 -0
- package/src/memoryGraph.ts +188 -350
- package/src/store/sqlite/embedding-cache.ts +81 -0
- package/src/store/sqlite.ts +1 -0
- package/src/topics/cluster.ts +11 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./index-
|
|
2
|
-
//# sourceMappingURL=useSSE-
|
|
1
|
+
import{r as e}from"./index-PRkilPXw.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
|
|
2
|
+
//# sourceMappingURL=useSSE-CGzgc2IK.js.map
|
package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-CGzgc2IK.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSSE-
|
|
1
|
+
{"version":3,"file":"useSSE-CGzgc2IK.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mega-compact dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-PRkilPXw.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-map-layout.ts — Force-directed layout engine for MemoryMapTab (D3).
|
|
3
|
+
*
|
|
4
|
+
* Uses Coulomb repulsion + Hooke attraction + center gravity to arrange nodes.
|
|
5
|
+
* No external graph library. Mutates layout in place.
|
|
6
|
+
* Self-contained types: only needs id + edges from graph data.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Types (self-contained — mirrors the subset used by the layout engine)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
interface GraphNodeMin {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly nodeType: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly summaryTruncated: string;
|
|
18
|
+
readonly tokenEstimate: number;
|
|
19
|
+
readonly timestamp: number;
|
|
20
|
+
readonly dedupStatus: string | undefined;
|
|
21
|
+
readonly raptorLevel: number;
|
|
22
|
+
readonly topicSummary: string | undefined;
|
|
23
|
+
readonly decisionCount: number;
|
|
24
|
+
readonly textSnippet: string;
|
|
25
|
+
readonly sessionId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface GraphEdgeMin {
|
|
29
|
+
readonly source: string;
|
|
30
|
+
readonly target: string;
|
|
31
|
+
readonly weight: number;
|
|
32
|
+
readonly type: "temporal" | "semantic" | "raptor_parent";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Position {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LayoutNode extends GraphNodeMin {
|
|
41
|
+
pos: Position;
|
|
42
|
+
vx: number;
|
|
43
|
+
vy: number;
|
|
44
|
+
pinned: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LayoutEdge {
|
|
48
|
+
source: number;
|
|
49
|
+
target: number;
|
|
50
|
+
weight: number;
|
|
51
|
+
type: GraphEdge["type"];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Layout {
|
|
55
|
+
nodes: LayoutNode[];
|
|
56
|
+
edges: LayoutEdge[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Constants
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const REPULSION = 5_000;
|
|
64
|
+
const ATTRACTION = 0.005;
|
|
65
|
+
const CENTER_GRAVITY = 0.01;
|
|
66
|
+
const DAMPING = 0.9;
|
|
67
|
+
const MIN_VELOCITY = 0.1;
|
|
68
|
+
const SPEED_LIMIT = 50;
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Build
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/** Build initial layout from graph data. */
|
|
75
|
+
export function buildLayout(data: { nodes: GraphNodeMin[]; edges: GraphEdgeMin[] }): Layout {
|
|
76
|
+
const nodes: LayoutNode[] = data.nodes.map((n, i) => {
|
|
77
|
+
const angle = (2 * Math.PI * i) / Math.max(data.nodes.length, 1);
|
|
78
|
+
const radius = 200;
|
|
79
|
+
return {
|
|
80
|
+
...n,
|
|
81
|
+
pos: { x: Math.cos(angle) * radius + 300, y: Math.sin(angle) * radius + 200 },
|
|
82
|
+
vx: 0,
|
|
83
|
+
vy: 0,
|
|
84
|
+
pinned: false,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const nodeIndex = new Map<string, number>();
|
|
89
|
+
nodes.forEach((n, i) => nodeIndex.set(n.id, i));
|
|
90
|
+
|
|
91
|
+
const edges: LayoutEdge[] = data.edges
|
|
92
|
+
.map((e) => {
|
|
93
|
+
const si = nodeIndex.get(e.source);
|
|
94
|
+
const ti = nodeIndex.get(e.target);
|
|
95
|
+
if (si === undefined || ti === undefined || si === ti) return null;
|
|
96
|
+
return { source: si, target: ti, weight: e.weight, type: e.type };
|
|
97
|
+
})
|
|
98
|
+
.filter((e): e is LayoutEdge => e !== null);
|
|
99
|
+
|
|
100
|
+
return { nodes, edges };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Apply forces
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/** Apply one iteration of the force simulation. Mutates layout in place. */
|
|
108
|
+
export function applyForces(layout: Layout): void {
|
|
109
|
+
const { nodes, edges } = layout;
|
|
110
|
+
const n = nodes.length;
|
|
111
|
+
if (n === 0) return;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < n; i++) {
|
|
114
|
+
if (nodes[i].pinned) continue;
|
|
115
|
+
let fx = 0;
|
|
116
|
+
let fy = 0;
|
|
117
|
+
|
|
118
|
+
// Coulomb repulsion
|
|
119
|
+
for (let j = 0; j < n; j++) {
|
|
120
|
+
if (i === j) continue;
|
|
121
|
+
const dx = nodes[i].pos.x - nodes[j].pos.x;
|
|
122
|
+
const dy = nodes[i].pos.y - nodes[j].pos.y;
|
|
123
|
+
const distSq = dx * dx + dy * dy || 1;
|
|
124
|
+
const force = REPULSION / distSq;
|
|
125
|
+
const dist = Math.sqrt(distSq);
|
|
126
|
+
fx += (dx / dist) * force;
|
|
127
|
+
fy += (dy / dist) * force;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Hooke attraction along edges
|
|
131
|
+
for (const e of edges) {
|
|
132
|
+
let other: number;
|
|
133
|
+
if (e.source === i) other = e.target;
|
|
134
|
+
else if (e.target === i) other = e.source;
|
|
135
|
+
else continue;
|
|
136
|
+
const dx = nodes[other].pos.x - nodes[i].pos.x;
|
|
137
|
+
const dy = nodes[other].pos.y - nodes[i].pos.y;
|
|
138
|
+
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
139
|
+
fx += dx * ATTRACTION * e.weight;
|
|
140
|
+
fy += dy * ATTRACTION * e.weight;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Center gravity
|
|
144
|
+
fx -= nodes[i].pos.x * CENTER_GRAVITY;
|
|
145
|
+
fy -= nodes[i].pos.y * CENTER_GRAVITY;
|
|
146
|
+
|
|
147
|
+
nodes[i].vx = (nodes[i].vx + fx) * DAMPING;
|
|
148
|
+
nodes[i].vy = (nodes[i].vy + fy) * DAMPING;
|
|
149
|
+
|
|
150
|
+
const speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);
|
|
151
|
+
if (speed > SPEED_LIMIT) {
|
|
152
|
+
nodes[i].vx = (nodes[i].vx / speed) * SPEED_LIMIT;
|
|
153
|
+
nodes[i].vy = (nodes[i].vy / speed) * SPEED_LIMIT;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (speed > MIN_VELOCITY) {
|
|
157
|
+
nodes[i].pos.x += nodes[i].vx;
|
|
158
|
+
nodes[i].pos.y += nodes[i].vy;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-map-shapes.tsx — Node shape rendering utilities for MemoryMapTab (D3).
|
|
3
|
+
*
|
|
4
|
+
* Node shape encodes nodeType: checkpoint = filled circle, turn = hollow circle,
|
|
5
|
+
* turn-content = hollow+ring, memory = diamond.
|
|
6
|
+
* Delegate-shell pattern: thin rendering helpers, no state, no side effects.
|
|
7
|
+
*/
|
|
8
|
+
import type React from "react";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export type NodeType = "checkpoint" | "turn" | "turn-content" | "memory";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Constants
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export const NODE_COLORS: Record<NodeType, string> = {
|
|
21
|
+
checkpoint: "#6366f1",
|
|
22
|
+
turn: "#58a6ff",
|
|
23
|
+
"turn-content": "#22c55e",
|
|
24
|
+
memory: "#f59e0b",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const NODE_TYPE_LABELS: Record<NodeType, string> = {
|
|
28
|
+
checkpoint: "Checkpoint",
|
|
29
|
+
turn: "Turn",
|
|
30
|
+
"turn-content": "Turn Content",
|
|
31
|
+
memory: "Memory",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** SVG path for a diamond centered at (0,0) with given half-width/height. */
|
|
39
|
+
function diamondPath(hw: number, hh: number): string {
|
|
40
|
+
return `M 0,-${hh} L ${hw},0 L 0,${hh} L -${hw},0 Z`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function nodeOpacity(dedupStatus: string | undefined): number {
|
|
44
|
+
if (!dedupStatus || dedupStatus === "active") return 1;
|
|
45
|
+
return 0.5;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Render function
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/** Render an SVG shape for a node based on its nodeType. */
|
|
53
|
+
export function renderNodeShape(
|
|
54
|
+
cx: number,
|
|
55
|
+
cy: number,
|
|
56
|
+
radius: number,
|
|
57
|
+
nodeType: NodeType,
|
|
58
|
+
isSelected: boolean,
|
|
59
|
+
isFiltered: boolean,
|
|
60
|
+
dedupStatus: string | undefined,
|
|
61
|
+
): React.ReactElement {
|
|
62
|
+
const fill = isFiltered ? NODE_COLORS[nodeType] ?? "#6366f1" : "#d1d5db";
|
|
63
|
+
const strokeColor = isSelected ? "#ef4444" : "none";
|
|
64
|
+
const opacity = nodeOpacity(dedupStatus);
|
|
65
|
+
|
|
66
|
+
switch (nodeType) {
|
|
67
|
+
case "checkpoint":
|
|
68
|
+
return (
|
|
69
|
+
<circle
|
|
70
|
+
cx={cx}
|
|
71
|
+
cy={cy}
|
|
72
|
+
r={isSelected ? radius + 3 : radius}
|
|
73
|
+
fill={fill}
|
|
74
|
+
opacity={opacity}
|
|
75
|
+
stroke={strokeColor}
|
|
76
|
+
strokeWidth={isSelected ? 2 : 0}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
case "turn":
|
|
80
|
+
return (
|
|
81
|
+
<circle
|
|
82
|
+
cx={cx}
|
|
83
|
+
cy={cy}
|
|
84
|
+
r={isSelected ? radius + 3 : radius}
|
|
85
|
+
fill="none"
|
|
86
|
+
stroke={fill}
|
|
87
|
+
strokeWidth={2.5}
|
|
88
|
+
opacity={opacity}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
case "turn-content":
|
|
92
|
+
return (
|
|
93
|
+
<g>
|
|
94
|
+
<circle
|
|
95
|
+
cx={cx}
|
|
96
|
+
cy={cy}
|
|
97
|
+
r={isSelected ? radius + 3 : radius}
|
|
98
|
+
fill="none"
|
|
99
|
+
stroke={fill}
|
|
100
|
+
strokeWidth={2.5}
|
|
101
|
+
opacity={opacity}
|
|
102
|
+
/>
|
|
103
|
+
<circle
|
|
104
|
+
cx={cx}
|
|
105
|
+
cy={cy}
|
|
106
|
+
r={Math.max(3, radius * 0.3)}
|
|
107
|
+
fill={fill}
|
|
108
|
+
opacity={opacity}
|
|
109
|
+
/>
|
|
110
|
+
</g>
|
|
111
|
+
);
|
|
112
|
+
case "memory":
|
|
113
|
+
const hw = radius * 0.85;
|
|
114
|
+
const hh = radius * 0.85;
|
|
115
|
+
return (
|
|
116
|
+
<path
|
|
117
|
+
d={diamondPath(hw, hh)}
|
|
118
|
+
transform={`translate(${cx}, ${cy})`}
|
|
119
|
+
fill={fill}
|
|
120
|
+
opacity={opacity}
|
|
121
|
+
stroke={strokeColor}
|
|
122
|
+
strokeWidth={isSelected ? 2 : 0}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|