shieldcortex 2.1.1 → 2.1.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/README.md +1 -1
- package/hooks/clawdbot/cortex-memory/HOOK.md +2 -2
- package/package.json +13 -9
- package/dashboard/components.json +0 -22
- package/dashboard/eslint.config.mjs +0 -42
- package/dashboard/next.config.ts +0 -7
- package/dashboard/package-lock.json +0 -8053
- package/dashboard/package.json +0 -44
- package/dashboard/postcss.config.mjs +0 -7
- package/dashboard/public/file.svg +0 -1
- package/dashboard/public/globe.svg +0 -1
- package/dashboard/public/next.svg +0 -1
- package/dashboard/public/vercel.svg +0 -1
- package/dashboard/public/window.svg +0 -1
- package/dashboard/scripts/ensure-api.mjs +0 -76
- package/dashboard/src/app/error.tsx +0 -49
- package/dashboard/src/app/favicon.ico +0 -0
- package/dashboard/src/app/globals.css +0 -130
- package/dashboard/src/app/layout.tsx +0 -35
- package/dashboard/src/app/page.tsx +0 -364
- package/dashboard/src/components/Providers.tsx +0 -27
- package/dashboard/src/components/brain/ActivityPulseSystem.tsx +0 -229
- package/dashboard/src/components/brain/BrainMesh.tsx +0 -133
- package/dashboard/src/components/brain/BrainRegions.tsx +0 -254
- package/dashboard/src/components/brain/BrainScene.tsx +0 -255
- package/dashboard/src/components/brain/CategoryLabels.tsx +0 -103
- package/dashboard/src/components/brain/CoreSphere.tsx +0 -215
- package/dashboard/src/components/brain/DataFlowParticles.tsx +0 -123
- package/dashboard/src/components/brain/DataStreamRings.tsx +0 -161
- package/dashboard/src/components/brain/ElectronFlow.tsx +0 -323
- package/dashboard/src/components/brain/HolographicGrid.tsx +0 -235
- package/dashboard/src/components/brain/MemoryLinks.tsx +0 -271
- package/dashboard/src/components/brain/MemoryNode.tsx +0 -245
- package/dashboard/src/components/brain/NeuralPathways.tsx +0 -441
- package/dashboard/src/components/brain/SynapseNodes.tsx +0 -312
- package/dashboard/src/components/brain/TimelineControls.tsx +0 -205
- package/dashboard/src/components/chip/ChipScene.tsx +0 -497
- package/dashboard/src/components/chip/ChipSubstrate.tsx +0 -238
- package/dashboard/src/components/chip/CortexCore.tsx +0 -210
- package/dashboard/src/components/chip/DataBus.tsx +0 -416
- package/dashboard/src/components/chip/MemoryCell.tsx +0 -225
- package/dashboard/src/components/chip/MemoryGrid.tsx +0 -328
- package/dashboard/src/components/chip/QuantumCell.tsx +0 -316
- package/dashboard/src/components/chip/SectionLabel.tsx +0 -113
- package/dashboard/src/components/chip/index.ts +0 -14
- package/dashboard/src/components/controls/ControlPanel.tsx +0 -106
- package/dashboard/src/components/controls/VersionPanel.tsx +0 -185
- package/dashboard/src/components/dashboard/StatsPanel.tsx +0 -164
- package/dashboard/src/components/debug/ActivityLog.tsx +0 -250
- package/dashboard/src/components/debug/DebugPanel.tsx +0 -101
- package/dashboard/src/components/debug/QueryTester.tsx +0 -192
- package/dashboard/src/components/debug/RelationshipGraph.tsx +0 -403
- package/dashboard/src/components/debug/SqlConsole.tsx +0 -319
- package/dashboard/src/components/graph/KnowledgeGraph.tsx +0 -230
- package/dashboard/src/components/graph/OntologyGraph.tsx +0 -631
- package/dashboard/src/components/insights/ActivityHeatmap.tsx +0 -131
- package/dashboard/src/components/insights/InsightsView.tsx +0 -46
- package/dashboard/src/components/insights/KnowledgeMapPanel.tsx +0 -80
- package/dashboard/src/components/insights/QualityPanel.tsx +0 -116
- package/dashboard/src/components/memories/MemoriesView.tsx +0 -150
- package/dashboard/src/components/memories/MemoryCard.tsx +0 -103
- package/dashboard/src/components/memory/MemoryDetail.tsx +0 -325
- package/dashboard/src/components/nav/NavRail.tsx +0 -54
- package/dashboard/src/components/ui/button.tsx +0 -62
- package/dashboard/src/components/ui/card.tsx +0 -92
- package/dashboard/src/components/ui/input.tsx +0 -21
- package/dashboard/src/hooks/useDebouncedValue.ts +0 -24
- package/dashboard/src/hooks/useMemories.ts +0 -458
- package/dashboard/src/hooks/useSuggestions.ts +0 -46
- package/dashboard/src/lib/category-colors.ts +0 -84
- package/dashboard/src/lib/position-algorithm.ts +0 -177
- package/dashboard/src/lib/simplex-noise.ts +0 -217
- package/dashboard/src/lib/store.ts +0 -88
- package/dashboard/src/lib/utils.ts +0 -6
- package/dashboard/src/lib/websocket.ts +0 -249
- package/dashboard/src/types/memory.ts +0 -73
- package/dashboard/tsconfig.json +0 -34
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Relationship Graph Component - Focus Mode
|
|
5
|
-
*
|
|
6
|
-
* Clean visualization: click a memory to see its direct connections.
|
|
7
|
-
* Unselected state shows all nodes dimmed, selected shows focus view.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useEffect, useRef, useState, useMemo } from 'react';
|
|
11
|
-
import { useMemoryLinks, useMemories } from '@/hooks/useMemories';
|
|
12
|
-
// Memory and MemoryLink types used implicitly via hooks
|
|
13
|
-
|
|
14
|
-
interface Node {
|
|
15
|
-
id: number;
|
|
16
|
-
title: string;
|
|
17
|
-
category: string;
|
|
18
|
-
salience: number;
|
|
19
|
-
x: number;
|
|
20
|
-
y: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface Edge {
|
|
24
|
-
source: number;
|
|
25
|
-
target: number;
|
|
26
|
-
relationship: string;
|
|
27
|
-
strength: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const RELATIONSHIP_COLORS: Record<string, string> = {
|
|
31
|
-
related: '#6366f1',
|
|
32
|
-
extends: '#22c55e',
|
|
33
|
-
references: '#3b82f6',
|
|
34
|
-
contradicts: '#ef4444',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const CATEGORY_COLORS: Record<string, string> = {
|
|
38
|
-
architecture: '#8b5cf6',
|
|
39
|
-
pattern: '#3b82f6',
|
|
40
|
-
error: '#ef4444',
|
|
41
|
-
learning: '#22c55e',
|
|
42
|
-
preference: '#f59e0b',
|
|
43
|
-
context: '#6366f1',
|
|
44
|
-
todo: '#ec4899',
|
|
45
|
-
note: '#64748b',
|
|
46
|
-
relationship: '#14b8a6',
|
|
47
|
-
custom: '#64748b',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export function RelationshipGraph() {
|
|
51
|
-
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
52
|
-
const [selectedNodeId, setSelectedNodeId] = useState<number | null>(null);
|
|
53
|
-
const [hoveredNodeId, setHoveredNodeId] = useState<number | null>(null);
|
|
54
|
-
const [relationshipFilter, setRelationshipFilter] = useState<string | null>(null);
|
|
55
|
-
|
|
56
|
-
const { data: links = [] } = useMemoryLinks();
|
|
57
|
-
const { data: memories = [] } = useMemories({ limit: 200 });
|
|
58
|
-
|
|
59
|
-
// Build graph data with stable positions
|
|
60
|
-
const { nodes, edges, nodeMap } = useMemo(() => {
|
|
61
|
-
const linkedIds = new Set<number>();
|
|
62
|
-
for (const link of links) {
|
|
63
|
-
linkedIds.add(link.source_id);
|
|
64
|
-
linkedIds.add(link.target_id);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const linkedMemories = memories.filter((m) => linkedIds.has(m.id));
|
|
68
|
-
|
|
69
|
-
// Create stable grid layout
|
|
70
|
-
const cols = Math.ceil(Math.sqrt(linkedMemories.length));
|
|
71
|
-
const cellWidth = 100;
|
|
72
|
-
const cellHeight = 80;
|
|
73
|
-
|
|
74
|
-
const nodes: Node[] = linkedMemories.map((m, i) => ({
|
|
75
|
-
id: m.id,
|
|
76
|
-
title: m.title,
|
|
77
|
-
category: m.category,
|
|
78
|
-
salience: m.salience,
|
|
79
|
-
x: (i % cols) * cellWidth + cellWidth / 2 + 50,
|
|
80
|
-
y: Math.floor(i / cols) * cellHeight + cellHeight / 2 + 50,
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
const edges: Edge[] = links.map((l) => ({
|
|
84
|
-
source: l.source_id,
|
|
85
|
-
target: l.target_id,
|
|
86
|
-
relationship: l.relationship,
|
|
87
|
-
strength: l.strength,
|
|
88
|
-
}));
|
|
89
|
-
|
|
90
|
-
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
91
|
-
|
|
92
|
-
return { nodes, edges, nodeMap };
|
|
93
|
-
}, [memories, links]);
|
|
94
|
-
|
|
95
|
-
// Filter edges
|
|
96
|
-
const filteredEdges = relationshipFilter
|
|
97
|
-
? edges.filter((e) => e.relationship === relationshipFilter)
|
|
98
|
-
: edges;
|
|
99
|
-
|
|
100
|
-
// Get connections for selected node
|
|
101
|
-
const selectedConnections = useMemo(() => {
|
|
102
|
-
if (!selectedNodeId) return { connectedIds: new Set<number>(), connectedEdges: [] };
|
|
103
|
-
|
|
104
|
-
const connectedIds = new Set<number>();
|
|
105
|
-
const connectedEdges: Edge[] = [];
|
|
106
|
-
|
|
107
|
-
for (const edge of filteredEdges) {
|
|
108
|
-
if (edge.source === selectedNodeId) {
|
|
109
|
-
connectedIds.add(edge.target);
|
|
110
|
-
connectedEdges.push(edge);
|
|
111
|
-
} else if (edge.target === selectedNodeId) {
|
|
112
|
-
connectedIds.add(edge.source);
|
|
113
|
-
connectedEdges.push(edge);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { connectedIds, connectedEdges };
|
|
118
|
-
}, [selectedNodeId, filteredEdges]);
|
|
119
|
-
|
|
120
|
-
// Get unique relationship types
|
|
121
|
-
const relationshipTypes = [...new Set(edges.map((e) => e.relationship))];
|
|
122
|
-
|
|
123
|
-
// Get selected node details
|
|
124
|
-
const selectedNode = selectedNodeId ? nodeMap.get(selectedNodeId) : null;
|
|
125
|
-
|
|
126
|
-
// Canvas rendering
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
const canvas = canvasRef.current;
|
|
129
|
-
if (!canvas) return;
|
|
130
|
-
|
|
131
|
-
const ctx = canvas.getContext('2d');
|
|
132
|
-
if (!ctx) return;
|
|
133
|
-
|
|
134
|
-
const rect = canvas.getBoundingClientRect();
|
|
135
|
-
canvas.width = rect.width * window.devicePixelRatio;
|
|
136
|
-
canvas.height = rect.height * window.devicePixelRatio;
|
|
137
|
-
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
138
|
-
|
|
139
|
-
const width = rect.width;
|
|
140
|
-
const height = rect.height;
|
|
141
|
-
|
|
142
|
-
// Clear
|
|
143
|
-
ctx.fillStyle = '#0f172a';
|
|
144
|
-
ctx.fillRect(0, 0, width, height);
|
|
145
|
-
|
|
146
|
-
if (nodes.length === 0) return;
|
|
147
|
-
|
|
148
|
-
// Calculate layout to fit in canvas
|
|
149
|
-
const padding = 60;
|
|
150
|
-
const cols = Math.ceil(Math.sqrt(nodes.length));
|
|
151
|
-
const rows = Math.ceil(nodes.length / cols);
|
|
152
|
-
const cellWidth = (width - padding * 2) / cols;
|
|
153
|
-
const cellHeight = (height - padding * 2) / rows;
|
|
154
|
-
|
|
155
|
-
// Update positions to fit canvas
|
|
156
|
-
nodes.forEach((node, i) => {
|
|
157
|
-
node.x = (i % cols) * cellWidth + cellWidth / 2 + padding;
|
|
158
|
-
node.y = Math.floor(i / cols) * cellHeight + cellHeight / 2 + padding;
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const { connectedIds, connectedEdges } = selectedConnections;
|
|
162
|
-
|
|
163
|
-
// Draw edges (only for selected node, or all dimmed if none selected)
|
|
164
|
-
if (selectedNodeId) {
|
|
165
|
-
// Draw connected edges prominently
|
|
166
|
-
ctx.lineWidth = 2;
|
|
167
|
-
for (const edge of connectedEdges) {
|
|
168
|
-
const source = nodeMap.get(edge.source);
|
|
169
|
-
const target = nodeMap.get(edge.target);
|
|
170
|
-
if (!source || !target) continue;
|
|
171
|
-
|
|
172
|
-
ctx.strokeStyle = RELATIONSHIP_COLORS[edge.relationship] || '#475569';
|
|
173
|
-
ctx.globalAlpha = 0.8;
|
|
174
|
-
ctx.beginPath();
|
|
175
|
-
ctx.moveTo(source.x, source.y);
|
|
176
|
-
ctx.lineTo(target.x, target.y);
|
|
177
|
-
ctx.stroke();
|
|
178
|
-
|
|
179
|
-
// Draw relationship label at midpoint
|
|
180
|
-
const midX = (source.x + target.x) / 2;
|
|
181
|
-
const midY = (source.y + target.y) / 2;
|
|
182
|
-
ctx.font = '10px system-ui';
|
|
183
|
-
ctx.fillStyle = RELATIONSHIP_COLORS[edge.relationship] || '#94a3b8';
|
|
184
|
-
ctx.globalAlpha = 1;
|
|
185
|
-
ctx.textAlign = 'center';
|
|
186
|
-
ctx.fillText(edge.relationship, midX, midY - 4);
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
// Show all edges very dimmed when nothing selected
|
|
190
|
-
ctx.lineWidth = 1;
|
|
191
|
-
ctx.globalAlpha = 0.15;
|
|
192
|
-
for (const edge of filteredEdges) {
|
|
193
|
-
const source = nodeMap.get(edge.source);
|
|
194
|
-
const target = nodeMap.get(edge.target);
|
|
195
|
-
if (!source || !target) continue;
|
|
196
|
-
|
|
197
|
-
ctx.strokeStyle = RELATIONSHIP_COLORS[edge.relationship] || '#475569';
|
|
198
|
-
ctx.beginPath();
|
|
199
|
-
ctx.moveTo(source.x, source.y);
|
|
200
|
-
ctx.lineTo(target.x, target.y);
|
|
201
|
-
ctx.stroke();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
ctx.globalAlpha = 1;
|
|
205
|
-
|
|
206
|
-
// Draw nodes
|
|
207
|
-
for (const node of nodes) {
|
|
208
|
-
const isSelected = node.id === selectedNodeId;
|
|
209
|
-
const isConnected = connectedIds.has(node.id);
|
|
210
|
-
const isHovered = node.id === hoveredNodeId;
|
|
211
|
-
const isHighlighted = isSelected || isConnected || !selectedNodeId;
|
|
212
|
-
|
|
213
|
-
const baseRadius = 6 + node.salience * 6;
|
|
214
|
-
const radius = isSelected ? baseRadius + 4 : isHovered ? baseRadius + 2 : baseRadius;
|
|
215
|
-
|
|
216
|
-
// Determine opacity
|
|
217
|
-
const opacity = isHighlighted ? 1 : 0.2;
|
|
218
|
-
|
|
219
|
-
// Draw node
|
|
220
|
-
ctx.beginPath();
|
|
221
|
-
ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
|
|
222
|
-
ctx.fillStyle = CATEGORY_COLORS[node.category] || '#64748b';
|
|
223
|
-
ctx.globalAlpha = opacity;
|
|
224
|
-
ctx.fill();
|
|
225
|
-
|
|
226
|
-
// Selection/hover ring
|
|
227
|
-
if (isSelected || isHovered) {
|
|
228
|
-
ctx.strokeStyle = isSelected ? '#f472b6' : '#94a3b8';
|
|
229
|
-
ctx.lineWidth = isSelected ? 3 : 2;
|
|
230
|
-
ctx.globalAlpha = 1;
|
|
231
|
-
ctx.stroke();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Draw label for highlighted nodes
|
|
235
|
-
if (isHighlighted && (isSelected || isConnected || isHovered || node.salience > 0.5)) {
|
|
236
|
-
ctx.globalAlpha = opacity;
|
|
237
|
-
const label = node.title.length > 20 ? node.title.slice(0, 20) + '...' : node.title;
|
|
238
|
-
ctx.font = isSelected ? 'bold 11px system-ui' : '10px system-ui';
|
|
239
|
-
ctx.textAlign = 'center';
|
|
240
|
-
|
|
241
|
-
// Background
|
|
242
|
-
const textWidth = ctx.measureText(label).width;
|
|
243
|
-
ctx.fillStyle = 'rgba(15, 23, 42, 0.9)';
|
|
244
|
-
ctx.fillRect(node.x - textWidth / 2 - 4, node.y + radius + 4, textWidth + 8, 16);
|
|
245
|
-
|
|
246
|
-
// Text
|
|
247
|
-
ctx.fillStyle = isSelected ? '#f472b6' : isConnected ? '#e2e8f0' : '#94a3b8';
|
|
248
|
-
ctx.fillText(label, node.x, node.y + radius + 16);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
ctx.globalAlpha = 1;
|
|
253
|
-
}, [nodes, nodeMap, filteredEdges, selectedNodeId, hoveredNodeId, selectedConnections]);
|
|
254
|
-
|
|
255
|
-
// Mouse handling
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
const canvas = canvasRef.current;
|
|
258
|
-
if (!canvas) return;
|
|
259
|
-
|
|
260
|
-
const getNodeAt = (x: number, y: number): Node | null => {
|
|
261
|
-
for (const node of nodes) {
|
|
262
|
-
const radius = 6 + node.salience * 6 + 4;
|
|
263
|
-
const dx = node.x - x;
|
|
264
|
-
const dy = node.y - y;
|
|
265
|
-
if (dx * dx + dy * dy < radius * radius) {
|
|
266
|
-
return node;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const handleClick = (e: MouseEvent) => {
|
|
273
|
-
const rect = canvas.getBoundingClientRect();
|
|
274
|
-
const x = e.clientX - rect.left;
|
|
275
|
-
const y = e.clientY - rect.top;
|
|
276
|
-
const node = getNodeAt(x, y);
|
|
277
|
-
|
|
278
|
-
if (node) {
|
|
279
|
-
setSelectedNodeId(node.id === selectedNodeId ? null : node.id);
|
|
280
|
-
} else {
|
|
281
|
-
setSelectedNodeId(null);
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
286
|
-
const rect = canvas.getBoundingClientRect();
|
|
287
|
-
const x = e.clientX - rect.left;
|
|
288
|
-
const y = e.clientY - rect.top;
|
|
289
|
-
const node = getNodeAt(x, y);
|
|
290
|
-
setHoveredNodeId(node?.id || null);
|
|
291
|
-
canvas.style.cursor = node ? 'pointer' : 'default';
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
canvas.addEventListener('click', handleClick);
|
|
295
|
-
canvas.addEventListener('mousemove', handleMouseMove);
|
|
296
|
-
|
|
297
|
-
return () => {
|
|
298
|
-
canvas.removeEventListener('click', handleClick);
|
|
299
|
-
canvas.removeEventListener('mousemove', handleMouseMove);
|
|
300
|
-
};
|
|
301
|
-
}, [nodes, selectedNodeId]);
|
|
302
|
-
|
|
303
|
-
return (
|
|
304
|
-
<div className="h-full flex flex-col">
|
|
305
|
-
{/* Controls */}
|
|
306
|
-
<div className="p-2 border-b border-slate-700 flex items-center gap-3">
|
|
307
|
-
<span className="text-xs text-slate-400">Filter:</span>
|
|
308
|
-
<button
|
|
309
|
-
onClick={() => setRelationshipFilter(null)}
|
|
310
|
-
className={`px-2 py-0.5 text-xs rounded ${
|
|
311
|
-
relationshipFilter === null
|
|
312
|
-
? 'bg-slate-600 text-white'
|
|
313
|
-
: 'text-slate-400 hover:text-white'
|
|
314
|
-
}`}
|
|
315
|
-
>
|
|
316
|
-
All
|
|
317
|
-
</button>
|
|
318
|
-
{relationshipTypes.map((type) => (
|
|
319
|
-
<button
|
|
320
|
-
key={type}
|
|
321
|
-
onClick={() => setRelationshipFilter(type)}
|
|
322
|
-
className={`px-2 py-0.5 text-xs rounded ${
|
|
323
|
-
relationshipFilter === type ? 'text-white' : 'text-slate-400 hover:text-white'
|
|
324
|
-
}`}
|
|
325
|
-
style={{
|
|
326
|
-
backgroundColor: relationshipFilter === type ? RELATIONSHIP_COLORS[type] : 'transparent',
|
|
327
|
-
}}
|
|
328
|
-
>
|
|
329
|
-
{type}
|
|
330
|
-
</button>
|
|
331
|
-
))}
|
|
332
|
-
|
|
333
|
-
<div className="flex-1" />
|
|
334
|
-
|
|
335
|
-
{/* Instructions */}
|
|
336
|
-
<span className="text-xs text-slate-500">
|
|
337
|
-
{selectedNodeId ? 'Click elsewhere to deselect' : 'Click a node to focus'}
|
|
338
|
-
</span>
|
|
339
|
-
|
|
340
|
-
{/* Legend */}
|
|
341
|
-
<div className="flex items-center gap-2 text-xs border-l border-slate-700 pl-3">
|
|
342
|
-
{Object.entries(RELATIONSHIP_COLORS).map(([type, color]) => (
|
|
343
|
-
<div key={type} className="flex items-center gap-1">
|
|
344
|
-
<div className="w-3 h-0.5" style={{ backgroundColor: color }} />
|
|
345
|
-
<span className="text-slate-500">{type}</span>
|
|
346
|
-
</div>
|
|
347
|
-
))}
|
|
348
|
-
</div>
|
|
349
|
-
</div>
|
|
350
|
-
|
|
351
|
-
{/* Graph */}
|
|
352
|
-
<div className="flex-1 relative min-h-0">
|
|
353
|
-
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
|
354
|
-
|
|
355
|
-
{/* Selected Node Details */}
|
|
356
|
-
{selectedNode && (
|
|
357
|
-
<div className="absolute top-3 left-3 p-3 bg-slate-800/95 border border-slate-700 rounded-lg max-w-xs">
|
|
358
|
-
<div className="flex items-center gap-2 mb-2">
|
|
359
|
-
<div
|
|
360
|
-
className="w-3 h-3 rounded-full"
|
|
361
|
-
style={{ backgroundColor: CATEGORY_COLORS[selectedNode.category] }}
|
|
362
|
-
/>
|
|
363
|
-
<span className="text-white font-medium text-sm">{selectedNode.title}</span>
|
|
364
|
-
</div>
|
|
365
|
-
<div className="text-xs text-slate-400 space-y-1">
|
|
366
|
-
<div>Category: {selectedNode.category}</div>
|
|
367
|
-
<div>Salience: {(selectedNode.salience * 100).toFixed(0)}%</div>
|
|
368
|
-
<div>Connections: {selectedConnections.connectedIds.size}</div>
|
|
369
|
-
</div>
|
|
370
|
-
{selectedConnections.connectedEdges.length > 0 && (
|
|
371
|
-
<div className="mt-2 pt-2 border-t border-slate-700">
|
|
372
|
-
<div className="text-xs text-slate-500 mb-1">Connected to:</div>
|
|
373
|
-
<div className="space-y-1 max-h-24 overflow-y-auto">
|
|
374
|
-
{selectedConnections.connectedEdges.map((edge, i) => {
|
|
375
|
-
const otherId = edge.source === selectedNodeId ? edge.target : edge.source;
|
|
376
|
-
const other = nodeMap.get(otherId);
|
|
377
|
-
return (
|
|
378
|
-
<div key={i} className="text-xs flex items-center gap-1">
|
|
379
|
-
<span
|
|
380
|
-
className="w-2 h-2 rounded-full shrink-0"
|
|
381
|
-
style={{ backgroundColor: RELATIONSHIP_COLORS[edge.relationship] }}
|
|
382
|
-
/>
|
|
383
|
-
<span className="text-slate-400">{edge.relationship}:</span>
|
|
384
|
-
<span className="text-slate-300 truncate">{other?.title || 'Unknown'}</span>
|
|
385
|
-
</div>
|
|
386
|
-
);
|
|
387
|
-
})}
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
)}
|
|
391
|
-
</div>
|
|
392
|
-
)}
|
|
393
|
-
|
|
394
|
-
{/* Empty state */}
|
|
395
|
-
{nodes.length === 0 && (
|
|
396
|
-
<div className="absolute inset-0 flex items-center justify-center text-slate-500">
|
|
397
|
-
No relationships to display
|
|
398
|
-
</div>
|
|
399
|
-
)}
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
);
|
|
403
|
-
}
|