claude-cortex 1.0.0 → 1.1.0

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.
Files changed (79) hide show
  1. package/dashboard/README.md +36 -0
  2. package/dashboard/components.json +22 -0
  3. package/dashboard/eslint.config.mjs +18 -0
  4. package/dashboard/next.config.ts +7 -0
  5. package/dashboard/package-lock.json +7784 -0
  6. package/dashboard/package.json +42 -0
  7. package/dashboard/postcss.config.mjs +7 -0
  8. package/dashboard/public/file.svg +1 -0
  9. package/dashboard/public/globe.svg +1 -0
  10. package/dashboard/public/next.svg +1 -0
  11. package/dashboard/public/vercel.svg +1 -0
  12. package/dashboard/public/window.svg +1 -0
  13. package/dashboard/src/app/favicon.ico +0 -0
  14. package/dashboard/src/app/globals.css +125 -0
  15. package/dashboard/src/app/layout.tsx +35 -0
  16. package/dashboard/src/app/page.tsx +338 -0
  17. package/dashboard/src/components/Providers.tsx +27 -0
  18. package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
  19. package/dashboard/src/components/brain/BrainMesh.tsx +118 -0
  20. package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
  21. package/dashboard/src/components/brain/BrainScene.tsx +255 -0
  22. package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
  23. package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
  24. package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
  25. package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
  26. package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
  27. package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
  28. package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
  29. package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
  30. package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
  31. package/dashboard/src/components/brain/SynapseNodes.tsx +306 -0
  32. package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
  33. package/dashboard/src/components/chip/ChipScene.tsx +497 -0
  34. package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
  35. package/dashboard/src/components/chip/CortexCore.tsx +210 -0
  36. package/dashboard/src/components/chip/DataBus.tsx +416 -0
  37. package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
  38. package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
  39. package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
  40. package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
  41. package/dashboard/src/components/chip/index.ts +14 -0
  42. package/dashboard/src/components/controls/ControlPanel.tsx +100 -0
  43. package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
  44. package/dashboard/src/components/debug/ActivityLog.tsx +238 -0
  45. package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
  46. package/dashboard/src/components/debug/QueryTester.tsx +192 -0
  47. package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
  48. package/dashboard/src/components/debug/SqlConsole.tsx +313 -0
  49. package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
  50. package/dashboard/src/components/ui/button.tsx +62 -0
  51. package/dashboard/src/components/ui/card.tsx +92 -0
  52. package/dashboard/src/components/ui/input.tsx +21 -0
  53. package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
  54. package/dashboard/src/hooks/useMemories.ts +276 -0
  55. package/dashboard/src/hooks/useSuggestions.ts +46 -0
  56. package/dashboard/src/lib/category-colors.ts +84 -0
  57. package/dashboard/src/lib/position-algorithm.ts +177 -0
  58. package/dashboard/src/lib/simplex-noise.ts +217 -0
  59. package/dashboard/src/lib/store.ts +88 -0
  60. package/dashboard/src/lib/utils.ts +6 -0
  61. package/dashboard/src/lib/websocket.ts +216 -0
  62. package/dashboard/src/types/memory.ts +73 -0
  63. package/dashboard/tsconfig.json +34 -0
  64. package/dist/api/control.d.ts +27 -0
  65. package/dist/api/control.d.ts.map +1 -0
  66. package/dist/api/control.js +60 -0
  67. package/dist/api/control.js.map +1 -0
  68. package/dist/api/visualization-server.d.ts.map +1 -1
  69. package/dist/api/visualization-server.js +109 -2
  70. package/dist/api/visualization-server.js.map +1 -1
  71. package/dist/index.d.ts +2 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +80 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/memory/store.d.ts +6 -0
  76. package/dist/memory/store.d.ts.map +1 -1
  77. package/dist/memory/store.js +14 -0
  78. package/dist/memory/store.js.map +1 -1
  79. package/package.json +7 -3
@@ -0,0 +1,271 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Memory Links - Neural Synapse Visualization
5
+ * Renders organic, neuron-like connections between related memories
6
+ * with flowing electrical signals and synapse endpoints
7
+ */
8
+
9
+ import { useMemo, useState, useRef, useCallback } from 'react';
10
+ import { useFrame } from '@react-three/fiber';
11
+ import { Html, CatmullRomLine } from '@react-three/drei';
12
+ import * as THREE from 'three';
13
+ import { Memory, MemoryLink } from '@/types/memory';
14
+
15
+ interface MemoryLinksProps {
16
+ memories: Memory[];
17
+ links: MemoryLink[];
18
+ memoryPositions: Map<number, { x: number; y: number; z: number }>;
19
+ onLinkClick?: (link: MemoryLink) => void;
20
+ }
21
+
22
+ // Default connection color (light gray) and relationship colors for hover
23
+ const DEFAULT_LINE_COLOR = '#cccccc';
24
+ const RELATIONSHIP_STYLES: Record<string, { color: string; label: string }> = {
25
+ references: { color: '#00d4ff', label: 'References' }, // Cyan - information flow
26
+ extends: { color: '#00ff88', label: 'Extends' }, // Green - growth
27
+ contradicts: { color: '#ff6b6b', label: 'Contradicts' }, // Red - conflict
28
+ related: { color: '#b388ff', label: 'Related' }, // Purple - association
29
+ };
30
+
31
+ // Bright signal pulse that travels along the neural fiber
32
+ function NeuralSignal({
33
+ curve,
34
+ speed = 1,
35
+ delay = 0,
36
+ }: {
37
+ curve: THREE.CatmullRomCurve3;
38
+ speed?: number;
39
+ delay?: number;
40
+ }) {
41
+ const meshRef = useRef<THREE.Mesh>(null);
42
+ const trailRef = useRef<THREE.Mesh>(null);
43
+ const progressRef = useRef(delay);
44
+
45
+ useFrame((_, delta) => {
46
+ if (!meshRef.current) return;
47
+
48
+ progressRef.current += delta * speed * 0.6; // Fast signal speed
49
+ if (progressRef.current > 1) {
50
+ progressRef.current = 0;
51
+ }
52
+
53
+ // Get position along curve
54
+ const point = curve.getPoint(progressRef.current);
55
+ meshRef.current.position.copy(point);
56
+
57
+ // Bright throughout, slight fade at ends
58
+ const fadeIn = Math.min(progressRef.current * 5, 1);
59
+ const fadeOut = Math.min((1 - progressRef.current) * 5, 1);
60
+ const opacity = fadeIn * fadeOut;
61
+ (meshRef.current.material as THREE.MeshBasicMaterial).opacity = opacity;
62
+
63
+ // Trail effect
64
+ if (trailRef.current) {
65
+ const trailT = Math.max(0, progressRef.current - 0.08);
66
+ const trailPoint = curve.getPoint(trailT);
67
+ trailRef.current.position.copy(trailPoint);
68
+ (trailRef.current.material as THREE.MeshBasicMaterial).opacity = opacity * 0.5;
69
+ }
70
+ });
71
+
72
+ return (
73
+ <>
74
+ {/* Main signal - bright white */}
75
+ <mesh ref={meshRef}>
76
+ <sphereGeometry args={[0.15, 12, 12]} />
77
+ <meshBasicMaterial color="#ffffff" transparent opacity={1} />
78
+ </mesh>
79
+ {/* Glow trail */}
80
+ <mesh ref={trailRef}>
81
+ <sphereGeometry args={[0.1, 8, 8]} />
82
+ <meshBasicMaterial color="#ffffff" transparent opacity={0.6} />
83
+ </mesh>
84
+ </>
85
+ );
86
+ }
87
+
88
+ // Single neural connection with organic curve
89
+ function NeuralConnection({
90
+ link,
91
+ sourcePos,
92
+ targetPos,
93
+ isHovered,
94
+ onHover,
95
+ onUnhover,
96
+ }: {
97
+ link: MemoryLink;
98
+ sourcePos: { x: number; y: number; z: number };
99
+ targetPos: { x: number; y: number; z: number };
100
+ isHovered: boolean;
101
+ onHover: () => void;
102
+ onUnhover: () => void;
103
+ }) {
104
+ const style = RELATIONSHIP_STYLES[link.relationship] || RELATIONSHIP_STYLES.related;
105
+
106
+ // Create organic curved path (like an axon)
107
+ const { curve, points } = useMemo(() => {
108
+ const start = new THREE.Vector3(sourcePos.x, sourcePos.y, sourcePos.z);
109
+ const end = new THREE.Vector3(targetPos.x, targetPos.y, targetPos.z);
110
+
111
+ // Calculate control points for organic curve
112
+ const mid = new THREE.Vector3().lerpVectors(start, end, 0.5);
113
+ const direction = new THREE.Vector3().subVectors(end, start);
114
+ const length = direction.length();
115
+
116
+ // Add perpendicular offset for curve (organic look)
117
+ const perpendicular = new THREE.Vector3(-direction.y, direction.x, direction.z * 0.5).normalize();
118
+ const curveAmount = length * 0.15 * (link.strength + 0.5);
119
+
120
+ // Create control points
121
+ const cp1 = new THREE.Vector3().lerpVectors(start, mid, 0.33);
122
+ cp1.add(perpendicular.clone().multiplyScalar(curveAmount));
123
+
124
+ const cp2 = new THREE.Vector3().lerpVectors(start, mid, 0.66);
125
+ cp2.add(perpendicular.clone().multiplyScalar(curveAmount * 0.5));
126
+
127
+ const cp3 = new THREE.Vector3().lerpVectors(mid, end, 0.33);
128
+ cp3.add(perpendicular.clone().multiplyScalar(-curveAmount * 0.5));
129
+
130
+ const cp4 = new THREE.Vector3().lerpVectors(mid, end, 0.66);
131
+ cp4.add(perpendicular.clone().multiplyScalar(-curveAmount));
132
+
133
+ const curvePoints = [start, cp1, cp2, mid, cp3, cp4, end];
134
+ const curve = new THREE.CatmullRomCurve3(curvePoints);
135
+ const points = curve.getPoints(32);
136
+
137
+ return { curve, points };
138
+ }, [sourcePos, targetPos, link.strength]);
139
+
140
+ // Gray by default, relationship color on hover
141
+ const lineColor = isHovered ? style.color : DEFAULT_LINE_COLOR;
142
+ const lineWidth = isHovered ? 4 : 2 + link.strength * 1.5;
143
+
144
+ return (
145
+ <group>
146
+ {/* Neural fiber - gray by default, colored on hover */}
147
+ <CatmullRomLine
148
+ points={points}
149
+ color={lineColor}
150
+ lineWidth={lineWidth}
151
+ transparent
152
+ opacity={isHovered ? 1 : 0.8}
153
+ />
154
+
155
+ {/* Bright white signal pulses traveling along fiber */}
156
+ <NeuralSignal curve={curve} speed={1.2 + link.strength} delay={0} />
157
+ <NeuralSignal curve={curve} speed={1.2 + link.strength} delay={0.5} />
158
+ {link.strength > 0.3 && (
159
+ <NeuralSignal curve={curve} speed={1.5 + link.strength} delay={0.25} />
160
+ )}
161
+ {link.strength > 0.6 && (
162
+ <NeuralSignal curve={curve} speed={1.8 + link.strength} delay={0.75} />
163
+ )}
164
+
165
+ {/* Invisible hit area for hover */}
166
+ <mesh
167
+ position={[
168
+ (sourcePos.x + targetPos.x) / 2,
169
+ (sourcePos.y + targetPos.y) / 2,
170
+ (sourcePos.z + targetPos.z) / 2,
171
+ ]}
172
+ onPointerEnter={onHover}
173
+ onPointerLeave={onUnhover}
174
+ >
175
+ <sphereGeometry args={[0.5, 8, 8]} />
176
+ <meshBasicMaterial visible={false} />
177
+ </mesh>
178
+
179
+ {/* Hover tooltip */}
180
+ {isHovered && (
181
+ <Html
182
+ position={[
183
+ (sourcePos.x + targetPos.x) / 2,
184
+ (sourcePos.y + targetPos.y) / 2 + 0.5,
185
+ (sourcePos.z + targetPos.z) / 2,
186
+ ]}
187
+ center
188
+ style={{ pointerEvents: 'none' }}
189
+ >
190
+ <div
191
+ className="px-3 py-2 rounded-lg shadow-xl text-xs whitespace-nowrap backdrop-blur-sm"
192
+ style={{
193
+ backgroundColor: 'rgba(15, 23, 42, 0.95)',
194
+ border: `2px solid ${style.color}`,
195
+ color: 'white',
196
+ }}
197
+ >
198
+ <div className="flex items-center gap-2 mb-1">
199
+ <span
200
+ className="w-2 h-2 rounded-full animate-pulse"
201
+ style={{ backgroundColor: style.color }}
202
+ />
203
+ <span className="font-semibold" style={{ color: style.color }}>
204
+ {style.label}
205
+ </span>
206
+ </div>
207
+ <div className="text-slate-300 text-[10px] space-y-0.5">
208
+ <div className="truncate max-w-[180px]">
209
+ {link.source_title || `Memory #${link.source_id}`}
210
+ </div>
211
+ <div className="text-slate-500">↓</div>
212
+ <div className="truncate max-w-[180px]">
213
+ {link.target_title || `Memory #${link.target_id}`}
214
+ </div>
215
+ <div className="mt-1 pt-1 border-t border-slate-700">
216
+ Strength: <span style={{ color: style.color }}>{(link.strength * 100).toFixed(0)}%</span>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </Html>
221
+ )}
222
+ </group>
223
+ );
224
+ }
225
+
226
+ export function MemoryLinks({ memories, links, memoryPositions }: MemoryLinksProps) {
227
+ const [hoveredLink, setHoveredLink] = useState<string | null>(null);
228
+
229
+ // Filter to only links where both memories exist and have positions
230
+ const validLinks = useMemo(() => {
231
+ const memoryIds = new Set(memories.map(m => m.id));
232
+ return links.filter(link =>
233
+ memoryIds.has(link.source_id) &&
234
+ memoryIds.has(link.target_id) &&
235
+ memoryPositions.has(link.source_id) &&
236
+ memoryPositions.has(link.target_id)
237
+ );
238
+ }, [memories, links, memoryPositions]);
239
+
240
+ const handleHover = useCallback((linkId: string) => {
241
+ setHoveredLink(linkId);
242
+ }, []);
243
+
244
+ const handleUnhover = useCallback(() => {
245
+ setHoveredLink(null);
246
+ }, []);
247
+
248
+ if (validLinks.length === 0) return null;
249
+
250
+ return (
251
+ <group name="neural-connections">
252
+ {validLinks.map((link) => {
253
+ const sourcePos = memoryPositions.get(link.source_id)!;
254
+ const targetPos = memoryPositions.get(link.target_id)!;
255
+ const linkId = `${link.source_id}-${link.target_id}`;
256
+
257
+ return (
258
+ <NeuralConnection
259
+ key={linkId}
260
+ link={link}
261
+ sourcePos={sourcePos}
262
+ targetPos={targetPos}
263
+ isHovered={hoveredLink === linkId}
264
+ onHover={() => handleHover(linkId)}
265
+ onUnhover={handleUnhover}
266
+ />
267
+ );
268
+ })}
269
+ </group>
270
+ );
271
+ }
@@ -0,0 +1,245 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Memory Node
5
+ * Individual memory rendered as a glowing neuron in 3D space
6
+ *
7
+ * Performance optimizations:
8
+ * - Reduced polygon counts on spheres (8 segments for glow, 12 for main)
9
+ * - Memoized geometries and materials to prevent recreation
10
+ * - Lazy-loaded HTML tooltips (only on hover)
11
+ * - Selection ring uses fewer segments
12
+ */
13
+
14
+ import { useRef, useState, useMemo, memo } from 'react';
15
+ import { useFrame, useThree } from '@react-three/fiber';
16
+ import { Html } from '@react-three/drei';
17
+ import * as THREE from 'three';
18
+ import { Memory } from '@/types/memory';
19
+ import { getCategoryColor } from '@/lib/category-colors';
20
+ import { calculateDecayFactor } from '@/lib/position-algorithm';
21
+ import { getAgeColor } from './TimelineControls';
22
+
23
+ // Holographic color palette (for holographic color mode)
24
+ const JARVIS_GOLD = '#FFD700';
25
+ const JARVIS_AMBER = '#FFB347';
26
+ const JARVIS_ORANGE = '#FF8C00';
27
+
28
+ interface MemoryNodeProps {
29
+ memory: Memory;
30
+ position: [number, number, number];
31
+ onSelect: (memory: Memory) => void;
32
+ isSelected: boolean;
33
+ colorMode?: 'category' | 'health' | 'age' | 'holographic'; // category = by type, health = decay heat map, age = time-based, holographic = Jarvis-style golden
34
+ }
35
+
36
+ /**
37
+ * Format memory age for display
38
+ */
39
+ function formatAge(createdAt: string | Date): string {
40
+ const age = Date.now() - new Date(createdAt).getTime();
41
+ const hours = age / (60 * 60 * 1000);
42
+
43
+ if (hours < 1) return `${Math.round(hours * 60)}m ago`;
44
+ if (hours < 24) return `${Math.round(hours)}h ago`;
45
+ if (hours < 24 * 7) return `${Math.round(hours / 24)}d ago`;
46
+ if (hours < 24 * 30) return `${Math.round(hours / (24 * 7))}w ago`;
47
+ return `${Math.round(hours / (24 * 30))}mo ago`;
48
+ }
49
+
50
+ /**
51
+ * Calculate holographic color based on salience - Jarvis-style golden
52
+ * High salience = bright gold, low salience = deep orange
53
+ */
54
+ function getHolographicColor(salience: number): string {
55
+ if (salience > 0.7) return JARVIS_GOLD; // High salience - bright gold
56
+ if (salience > 0.4) return JARVIS_AMBER; // Medium salience - warm gold
57
+ return JARVIS_ORANGE; // Low salience - deep orange
58
+ }
59
+
60
+ /**
61
+ * Calculate health color based on salience and decay
62
+ * Green (healthy) → Yellow (moderate) → Red (at risk)
63
+ */
64
+ function getHealthColor(salience: number, decayFactor: number): string {
65
+ const health = salience * decayFactor;
66
+
67
+ if (health > 0.6) {
68
+ // Green - healthy
69
+ return '#22c55e';
70
+ } else if (health > 0.35) {
71
+ // Yellow - moderate
72
+ const t = (health - 0.35) / 0.25; // 0 to 1 within yellow range
73
+ // Interpolate from orange to yellow
74
+ const r = Math.round(245 - t * 11); // 245 to 234
75
+ const g = Math.round(158 + t * 21); // 158 to 179
76
+ return `rgb(${r}, ${g}, 66)`;
77
+ } else {
78
+ // Red/Orange - at risk
79
+ const t = health / 0.35; // 0 to 1 within red range
80
+ const r = Math.round(239); // red
81
+ const g = Math.round(68 + t * 90); // 68 to 158
82
+ return `rgb(${r}, ${g}, 68)`;
83
+ }
84
+ }
85
+
86
+ // Shared geometries (created once, reused by all nodes)
87
+ const NODE_GEOMETRY = new THREE.SphereGeometry(1, 16, 16);
88
+ const RING_GEOMETRY = new THREE.RingGeometry(1, 1.15, 24);
89
+
90
+ function MemoryNodeInner({
91
+ memory,
92
+ position,
93
+ onSelect,
94
+ isSelected,
95
+ colorMode = 'category',
96
+ }: MemoryNodeProps) {
97
+ const meshRef = useRef<THREE.Mesh>(null);
98
+ const [hovered, setHovered] = useState(false);
99
+ const { camera } = useThree();
100
+
101
+ // Calculate visual properties (memoized)
102
+ const decayFactor = useMemo(() => calculateDecayFactor(memory), [memory]);
103
+ const categoryColor = useMemo(() => getCategoryColor(memory.category), [memory.category]);
104
+ const healthColor = useMemo(
105
+ () => getHealthColor(memory.salience, decayFactor),
106
+ [memory.salience, decayFactor]
107
+ );
108
+ const ageColor = useMemo(() => getAgeColor(memory.createdAt), [memory.createdAt]);
109
+ const holographicColor = useMemo(() => getHolographicColor(memory.salience), [memory.salience]);
110
+
111
+ // Select color based on mode
112
+ const baseColor = useMemo(() => {
113
+ switch (colorMode) {
114
+ case 'health': return healthColor;
115
+ case 'age': return ageColor;
116
+ case 'holographic': return holographicColor;
117
+ default: return categoryColor;
118
+ }
119
+ }, [colorMode, healthColor, ageColor, holographicColor, categoryColor]);
120
+
121
+ // Node size based on salience (0.2 to 0.4) - larger for better visibility
122
+ const size = useMemo(() => 0.2 + memory.salience * 0.2, [memory.salience]);
123
+
124
+ // Solid node material - no transparency for clarity
125
+ const nodeMaterial = useMemo(
126
+ () =>
127
+ new THREE.MeshStandardMaterial({
128
+ color: baseColor,
129
+ emissive: baseColor,
130
+ emissiveIntensity: 0.3,
131
+ metalness: 0.2,
132
+ roughness: 0.5,
133
+ }),
134
+ [baseColor]
135
+ );
136
+
137
+ // Subtle animation - increase emissive on hover
138
+ useFrame(() => {
139
+ if (!meshRef.current) return;
140
+
141
+ // Update emissive intensity on hover for highlight effect
142
+ (meshRef.current.material as THREE.MeshStandardMaterial).emissiveIntensity = hovered ? 0.8 : 0.3;
143
+ });
144
+
145
+ return (
146
+ <group position={position}>
147
+ {/* Main node - solid colored sphere */}
148
+ <mesh
149
+ ref={meshRef}
150
+ geometry={NODE_GEOMETRY}
151
+ material={nodeMaterial}
152
+ scale={size}
153
+ onClick={(e) => {
154
+ e.stopPropagation();
155
+ onSelect(memory);
156
+ }}
157
+ onPointerOver={(e) => {
158
+ e.stopPropagation();
159
+ setHovered(true);
160
+ document.body.style.cursor = 'pointer';
161
+ }}
162
+ onPointerOut={() => {
163
+ setHovered(false);
164
+ document.body.style.cursor = 'default';
165
+ }}
166
+ />
167
+
168
+ {/* Selection ring - only rendered when selected */}
169
+ {isSelected && (
170
+ <mesh geometry={RING_GEOMETRY} rotation={[Math.PI / 2, 0, 0]} scale={size + 0.15}>
171
+ <meshBasicMaterial
172
+ color="#ffffff"
173
+ transparent
174
+ opacity={0.9}
175
+ side={THREE.DoubleSide}
176
+ />
177
+ </mesh>
178
+ )}
179
+
180
+ {/* Hover tooltip - lazy loaded only on hover */}
181
+ {hovered && !isSelected && (
182
+ <Html
183
+ distanceFactor={8}
184
+ style={{
185
+ pointerEvents: 'none',
186
+ transform: 'translate(-50%, -120%)',
187
+ }}
188
+ >
189
+ <div className="bg-slate-900/95 border border-slate-700 px-3 py-2 rounded-lg shadow-xl backdrop-blur-sm whitespace-nowrap">
190
+ <div className="text-white font-medium text-sm">{memory.title}</div>
191
+ <div className="flex items-center gap-2 mt-1 text-xs">
192
+ <span
193
+ className="px-1.5 py-0.5 rounded"
194
+ style={{ backgroundColor: categoryColor + '30', color: categoryColor }}
195
+ >
196
+ {memory.category}
197
+ </span>
198
+ <span className="text-slate-400">
199
+ {(memory.salience * 100).toFixed(0)}%
200
+ </span>
201
+ {colorMode === 'health' && (
202
+ <span
203
+ className="px-1.5 py-0.5 rounded"
204
+ style={{ backgroundColor: healthColor + '30', color: healthColor }}
205
+ >
206
+ {memory.salience * decayFactor > 0.6 ? 'Healthy' : memory.salience * decayFactor > 0.35 ? 'Moderate' : 'At Risk'}
207
+ </span>
208
+ )}
209
+ {colorMode === 'age' && (
210
+ <span
211
+ className="px-1.5 py-0.5 rounded"
212
+ style={{ backgroundColor: ageColor + '30', color: ageColor }}
213
+ >
214
+ {formatAge(memory.createdAt)}
215
+ </span>
216
+ )}
217
+ {colorMode === 'holographic' && (
218
+ <span
219
+ className="px-1.5 py-0.5 rounded"
220
+ style={{ backgroundColor: holographicColor + '30', color: holographicColor }}
221
+ >
222
+ {memory.salience > 0.7 ? 'High' : memory.salience > 0.4 ? 'Medium' : 'Low'}
223
+ </span>
224
+ )}
225
+ </div>
226
+ </div>
227
+ </Html>
228
+ )}
229
+ </group>
230
+ );
231
+ }
232
+
233
+ // Memoize the component to prevent re-renders when other nodes change
234
+ export const MemoryNode = memo(MemoryNodeInner, (prev, next) => {
235
+ return (
236
+ prev.memory.id === next.memory.id &&
237
+ prev.memory.salience === next.memory.salience &&
238
+ prev.memory.category === next.memory.category &&
239
+ prev.isSelected === next.isSelected &&
240
+ prev.colorMode === next.colorMode &&
241
+ prev.position[0] === next.position[0] &&
242
+ prev.position[1] === next.position[1] &&
243
+ prev.position[2] === next.position[2]
244
+ );
245
+ });