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,177 @@
1
+ /**
2
+ * Position Algorithm
3
+ * Calculates 3D positions for memories in a brain-like visualization
4
+ *
5
+ * Brain anatomy mapping:
6
+ * - Frontal lobe (front, +Z): Short-term memories, decisions, planning
7
+ * - Temporal lobes (sides, ±X): Episodic memories, language, emotion
8
+ * - Parietal lobe (top, +Y): Spatial awareness, context
9
+ * - Occipital lobe (back, -Z): Long-term storage, patterns
10
+ * - Hippocampus (center): Learning, memory formation
11
+ */
12
+
13
+ import { Memory, Memory3DPosition, MemoryCategory } from '@/types/memory';
14
+
15
+ // Brain region definitions - where each category lives
16
+ const BRAIN_REGIONS: Record<MemoryCategory, {
17
+ basePosition: { x: number; y: number; z: number };
18
+ spread: number;
19
+ }> = {
20
+ // Prefrontal cortex - planning and architecture decisions
21
+ architecture: {
22
+ basePosition: { x: 0, y: 1.2, z: 2.2 },
23
+ spread: 1.0,
24
+ },
25
+ // Visual/pattern recognition area - occipital lobe
26
+ pattern: {
27
+ basePosition: { x: 0, y: 0.3, z: -2.2 },
28
+ spread: 0.9,
29
+ },
30
+ // Limbic system - preferences and emotions
31
+ preference: {
32
+ basePosition: { x: 1.6, y: -0.3, z: 0 },
33
+ spread: 0.7,
34
+ },
35
+ // Amygdala area - error/threat detection
36
+ error: {
37
+ basePosition: { x: -1.6, y: -0.3, z: 0.3 },
38
+ spread: 0.8,
39
+ },
40
+ // Parietal lobe - context and spatial awareness
41
+ context: {
42
+ basePosition: { x: 0, y: 1.8, z: 0 },
43
+ spread: 1.2,
44
+ },
45
+ // Hippocampus - learning and memory formation (central)
46
+ learning: {
47
+ basePosition: { x: 0, y: -0.3, z: 0 },
48
+ spread: 0.8,
49
+ },
50
+ // Frontal lobe - task planning
51
+ todo: {
52
+ basePosition: { x: 0.6, y: 0.8, z: 1.8 },
53
+ spread: 0.7,
54
+ },
55
+ // Temporal lobe - general notes and language
56
+ note: {
57
+ basePosition: { x: 1.8, y: 0, z: -0.3 },
58
+ spread: 1.0,
59
+ },
60
+ // Social brain network - relationships
61
+ relationship: {
62
+ basePosition: { x: -1.8, y: 0.3, z: -0.3 },
63
+ spread: 0.9,
64
+ },
65
+ // Distributed - custom memories
66
+ custom: {
67
+ basePosition: { x: 0, y: 0, z: 0 },
68
+ spread: 1.5,
69
+ },
70
+ };
71
+
72
+ // Memory type influences depth (surface vs inner brain)
73
+ const TYPE_DEPTH: Record<string, number> = {
74
+ short_term: 0.6, // Closer to surface (outer brain)
75
+ episodic: 0, // Middle layer
76
+ long_term: -0.4, // Deeper (inner brain, more permanent)
77
+ };
78
+
79
+ /**
80
+ * Seeded random for consistent positions based on memory ID
81
+ */
82
+ function seededRandom(seed: number): number {
83
+ const x = Math.sin(seed * 12.9898) * 43758.5453;
84
+ return x - Math.floor(x);
85
+ }
86
+
87
+ /**
88
+ * Calculate 3D position for a memory within brain anatomy
89
+ */
90
+ export function calculateMemoryPosition(memory: Memory): Memory3DPosition {
91
+ const region = BRAIN_REGIONS[memory.category] || BRAIN_REGIONS.custom;
92
+ const typeDepth = TYPE_DEPTH[memory.type] || 0;
93
+
94
+ // Use memory ID for consistent random offset
95
+ const rand1 = seededRandom(memory.id);
96
+ const rand2 = seededRandom(memory.id + 1000);
97
+ const rand3 = seededRandom(memory.id + 2000);
98
+
99
+ // Calculate position within brain region using spherical distribution
100
+ const spread = region.spread;
101
+ const theta = rand1 * Math.PI * 2; // Angle around Y axis
102
+ const phi = (rand2 - 0.5) * Math.PI; // Angle from horizontal
103
+ const r = rand3 * spread * 0.7 + spread * 0.3; // Distance from region center
104
+
105
+ // Convert to cartesian
106
+ const offsetX = r * Math.cos(phi) * Math.cos(theta);
107
+ const offsetY = r * Math.sin(phi) * 0.6; // Flatten vertically
108
+ const offsetZ = r * Math.cos(phi) * Math.sin(theta);
109
+
110
+ // Base position from region + offset + type depth
111
+ let x = region.basePosition.x + offsetX;
112
+ let y = region.basePosition.y + offsetY;
113
+ let z = region.basePosition.z + offsetZ + typeDepth;
114
+
115
+ // Salience affects prominence (higher = closer to surface, more visible)
116
+ const salienceBoost = memory.salience * 0.4;
117
+ const distanceFromCenter = Math.sqrt(x * x + y * y + z * z);
118
+ if (distanceFromCenter > 0.1) {
119
+ const targetDistance = distanceFromCenter + salienceBoost;
120
+ const scale = targetDistance / distanceFromCenter;
121
+ x *= scale;
122
+ y *= scale;
123
+ z *= scale;
124
+ }
125
+
126
+ // Constrain to brain-like ellipsoid shape
127
+ const brainWidth = 3.2; // X axis
128
+ const brainHeight = 2.5; // Y axis
129
+ const brainDepth = 3.5; // Z axis (front to back)
130
+
131
+ // Keep within brain bounds
132
+ const normalizedDist = Math.sqrt(
133
+ (x / brainWidth) ** 2 +
134
+ (y / brainHeight) ** 2 +
135
+ (z / brainDepth) ** 2
136
+ );
137
+
138
+ if (normalizedDist > 0.95) {
139
+ const scale = 0.95 / normalizedDist;
140
+ x *= scale;
141
+ y *= scale;
142
+ z *= scale;
143
+ }
144
+
145
+ return { x, y, z };
146
+ }
147
+
148
+ /**
149
+ * Calculate decay factor for visual effects
150
+ */
151
+ export function calculateDecayFactor(memory: Memory): number {
152
+ if (!memory.lastAccessed) return 1;
153
+
154
+ const now = Date.now();
155
+ const lastAccessed = new Date(memory.lastAccessed).getTime();
156
+ const hoursSinceAccess = (now - lastAccessed) / (1000 * 60 * 60);
157
+
158
+ const decayRates: Record<string, number> = {
159
+ short_term: 0.995,
160
+ long_term: 0.9995,
161
+ episodic: 0.998,
162
+ };
163
+
164
+ const rate = decayRates[memory.type] || 0.995;
165
+ return Math.pow(rate, hoursSinceAccess);
166
+ }
167
+
168
+ /**
169
+ * Get region bounds for each memory type
170
+ */
171
+ export function getRegionBounds() {
172
+ return {
173
+ short_term: { minZ: 1.0, maxZ: 3.0, color: '#F97316' },
174
+ episodic: { minZ: -1.0, maxZ: 1.0, color: '#8B5CF6' },
175
+ long_term: { minZ: -3.0, maxZ: -1.0, color: '#3B82F6' },
176
+ };
177
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Simplex Noise Implementation
3
+ * Used for procedural brain mesh generation with organic cortex folds
4
+ *
5
+ * Based on Stefan Gustavson's simplex noise algorithm
6
+ */
7
+
8
+ // Gradient vectors for 3D
9
+ const grad3 = [
10
+ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
11
+ [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
12
+ [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1],
13
+ ];
14
+
15
+ // Permutation table
16
+ const perm = new Uint8Array(512);
17
+ const permMod12 = new Uint8Array(512);
18
+
19
+ // Initialize with seed
20
+ function initPermutation(seed: number = 0): void {
21
+ const p = new Uint8Array(256);
22
+ for (let i = 0; i < 256; i++) {
23
+ p[i] = i;
24
+ }
25
+
26
+ // Shuffle using seed
27
+ let n = seed;
28
+ for (let i = 255; i > 0; i--) {
29
+ n = (n * 1103515245 + 12345) & 0x7fffffff;
30
+ const j = n % (i + 1);
31
+ [p[i], p[j]] = [p[j], p[i]];
32
+ }
33
+
34
+ for (let i = 0; i < 512; i++) {
35
+ perm[i] = p[i & 255];
36
+ permMod12[i] = perm[i] % 12;
37
+ }
38
+ }
39
+
40
+ // Initialize with default seed
41
+ initPermutation(42);
42
+
43
+ // Skewing factors for 3D
44
+ const F3 = 1 / 3;
45
+ const G3 = 1 / 6;
46
+
47
+ function dot3(g: number[], x: number, y: number, z: number): number {
48
+ return g[0] * x + g[1] * y + g[2] * z;
49
+ }
50
+
51
+ /**
52
+ * 3D Simplex Noise
53
+ * Returns value in range [-1, 1]
54
+ */
55
+ export function simplex3D(x: number, y: number, z: number): number {
56
+ // Skew the input space
57
+ const s = (x + y + z) * F3;
58
+ const i = Math.floor(x + s);
59
+ const j = Math.floor(y + s);
60
+ const k = Math.floor(z + s);
61
+
62
+ const t = (i + j + k) * G3;
63
+ const X0 = i - t;
64
+ const Y0 = j - t;
65
+ const Z0 = k - t;
66
+
67
+ const x0 = x - X0;
68
+ const y0 = y - Y0;
69
+ const z0 = z - Z0;
70
+
71
+ // Determine simplex
72
+ let i1: number, j1: number, k1: number;
73
+ let i2: number, j2: number, k2: number;
74
+
75
+ if (x0 >= y0) {
76
+ if (y0 >= z0) {
77
+ i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
78
+ } else if (x0 >= z0) {
79
+ i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1;
80
+ } else {
81
+ i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1;
82
+ }
83
+ } else {
84
+ if (y0 < z0) {
85
+ i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1;
86
+ } else if (x0 < z0) {
87
+ i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1;
88
+ } else {
89
+ i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
90
+ }
91
+ }
92
+
93
+ const x1 = x0 - i1 + G3;
94
+ const y1 = y0 - j1 + G3;
95
+ const z1 = z0 - k1 + G3;
96
+ const x2 = x0 - i2 + 2 * G3;
97
+ const y2 = y0 - j2 + 2 * G3;
98
+ const z2 = z0 - k2 + 2 * G3;
99
+ const x3 = x0 - 1 + 3 * G3;
100
+ const y3 = y0 - 1 + 3 * G3;
101
+ const z3 = z0 - 1 + 3 * G3;
102
+
103
+ const ii = i & 255;
104
+ const jj = j & 255;
105
+ const kk = k & 255;
106
+
107
+ let n0 = 0, n1 = 0, n2 = 0, n3 = 0;
108
+
109
+ let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
110
+ if (t0 >= 0) {
111
+ const gi0 = permMod12[ii + perm[jj + perm[kk]]];
112
+ t0 *= t0;
113
+ n0 = t0 * t0 * dot3(grad3[gi0], x0, y0, z0);
114
+ }
115
+
116
+ let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
117
+ if (t1 >= 0) {
118
+ const gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]];
119
+ t1 *= t1;
120
+ n1 = t1 * t1 * dot3(grad3[gi1], x1, y1, z1);
121
+ }
122
+
123
+ let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
124
+ if (t2 >= 0) {
125
+ const gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]];
126
+ t2 *= t2;
127
+ n2 = t2 * t2 * dot3(grad3[gi2], x2, y2, z2);
128
+ }
129
+
130
+ let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
131
+ if (t3 >= 0) {
132
+ const gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]];
133
+ t3 *= t3;
134
+ n3 = t3 * t3 * dot3(grad3[gi3], x3, y3, z3);
135
+ }
136
+
137
+ return 32 * (n0 + n1 + n2 + n3);
138
+ }
139
+
140
+ /**
141
+ * Fractal Brownian Motion (fBm)
142
+ * Adds multiple octaves of noise for more detail
143
+ */
144
+ export function fbm3D(
145
+ x: number,
146
+ y: number,
147
+ z: number,
148
+ octaves: number = 4,
149
+ lacunarity: number = 2,
150
+ gain: number = 0.5
151
+ ): number {
152
+ let value = 0;
153
+ let amplitude = 1;
154
+ let frequency = 1;
155
+ let maxValue = 0;
156
+
157
+ for (let i = 0; i < octaves; i++) {
158
+ value += amplitude * simplex3D(x * frequency, y * frequency, z * frequency);
159
+ maxValue += amplitude;
160
+ amplitude *= gain;
161
+ frequency *= lacunarity;
162
+ }
163
+
164
+ return value / maxValue;
165
+ }
166
+
167
+ /**
168
+ * Ridged noise - creates sharp ridges
169
+ * Useful for sulci (brain folds)
170
+ */
171
+ export function ridged3D(
172
+ x: number,
173
+ y: number,
174
+ z: number,
175
+ octaves: number = 4,
176
+ lacunarity: number = 2,
177
+ gain: number = 0.5
178
+ ): number {
179
+ let value = 0;
180
+ let amplitude = 1;
181
+ let frequency = 1;
182
+ let weight = 1;
183
+
184
+ for (let i = 0; i < octaves; i++) {
185
+ let signal = simplex3D(x * frequency, y * frequency, z * frequency);
186
+ signal = 1 - Math.abs(signal);
187
+ signal *= signal * weight;
188
+ weight = Math.min(1, Math.max(0, signal * 2));
189
+ value += signal * amplitude;
190
+ amplitude *= gain;
191
+ frequency *= lacunarity;
192
+ }
193
+
194
+ return value;
195
+ }
196
+
197
+ /**
198
+ * Turbulence - absolute value of noise
199
+ */
200
+ export function turbulence3D(
201
+ x: number,
202
+ y: number,
203
+ z: number,
204
+ octaves: number = 4
205
+ ): number {
206
+ let value = 0;
207
+ let amplitude = 1;
208
+ let frequency = 1;
209
+
210
+ for (let i = 0; i < octaves; i++) {
211
+ value += amplitude * Math.abs(simplex3D(x * frequency, y * frequency, z * frequency));
212
+ amplitude *= 0.5;
213
+ frequency *= 2;
214
+ }
215
+
216
+ return value;
217
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * UI State Store
5
+ * Zustand store for dashboard UI state
6
+ */
7
+
8
+ import { create } from 'zustand';
9
+ import { Memory, MemoryEvent } from '@/types/memory';
10
+
11
+ interface DashboardState {
12
+ // Selected memory
13
+ selectedMemory: Memory | null;
14
+ setSelectedMemory: (memory: Memory | null) => void;
15
+
16
+ // View mode
17
+ viewMode: '3d' | 'list' | 'graph';
18
+ setViewMode: (mode: '3d' | 'list' | 'graph') => void;
19
+
20
+ // Filters
21
+ typeFilter: string | null;
22
+ categoryFilter: string | null;
23
+ projectFilter: string | null;
24
+ setTypeFilter: (type: string | null) => void;
25
+ setCategoryFilter: (category: string | null) => void;
26
+ setProjectFilter: (project: string | null) => void;
27
+
28
+ // Recent events (for activity feed)
29
+ recentEvents: MemoryEvent[];
30
+ addEvent: (event: MemoryEvent) => void;
31
+ clearEvents: () => void;
32
+
33
+ // 3D camera state
34
+ cameraPosition: [number, number, number];
35
+ setCameraPosition: (pos: [number, number, number]) => void;
36
+
37
+ // Sidebar visibility
38
+ showLeftSidebar: boolean;
39
+ showRightSidebar: boolean;
40
+ toggleLeftSidebar: () => void;
41
+ toggleRightSidebar: () => void;
42
+
43
+ // Search query
44
+ searchQuery: string;
45
+ setSearchQuery: (query: string) => void;
46
+ }
47
+
48
+ export const useDashboardStore = create<DashboardState>((set) => ({
49
+ // Selected memory
50
+ selectedMemory: null,
51
+ setSelectedMemory: (memory) => set({ selectedMemory: memory }),
52
+
53
+ // View mode
54
+ viewMode: '3d',
55
+ setViewMode: (mode) => set({ viewMode: mode }),
56
+
57
+ // Filters
58
+ typeFilter: null,
59
+ categoryFilter: null,
60
+ projectFilter: null,
61
+ setTypeFilter: (type) => set({ typeFilter: type }),
62
+ setCategoryFilter: (category) => set({ categoryFilter: category }),
63
+ setProjectFilter: (project) => set({ projectFilter: project }),
64
+
65
+ // Recent events
66
+ recentEvents: [],
67
+ addEvent: (event) =>
68
+ set((state) => ({
69
+ recentEvents: [event, ...state.recentEvents].slice(0, 50),
70
+ })),
71
+ clearEvents: () => set({ recentEvents: [] }),
72
+
73
+ // 3D camera
74
+ cameraPosition: [0, 0, 12],
75
+ setCameraPosition: (pos) => set({ cameraPosition: pos }),
76
+
77
+ // Sidebars
78
+ showLeftSidebar: true,
79
+ showRightSidebar: true,
80
+ toggleLeftSidebar: () =>
81
+ set((state) => ({ showLeftSidebar: !state.showLeftSidebar })),
82
+ toggleRightSidebar: () =>
83
+ set((state) => ({ showRightSidebar: !state.showRightSidebar })),
84
+
85
+ // Search
86
+ searchQuery: '',
87
+ setSearchQuery: (query) => set({ searchQuery: query }),
88
+ }));
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * WebSocket client for real-time memory updates
3
+ *
4
+ * Connects to the visualization server's WebSocket endpoint
5
+ * and dispatches events to React Query for cache invalidation.
6
+ */
7
+
8
+ import { useEffect, useRef, useCallback, useState } from 'react';
9
+ import { useQueryClient } from '@tanstack/react-query';
10
+
11
+ const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3001/ws/events';
12
+
13
+ export type WebSocketEventType =
14
+ | 'initial_state'
15
+ | 'memory_created'
16
+ | 'memory_accessed'
17
+ | 'memory_updated'
18
+ | 'memory_deleted'
19
+ | 'consolidation_complete'
20
+ | 'decay_tick'
21
+ // Phase 4: Worker events
22
+ | 'worker_light_tick'
23
+ | 'worker_medium_tick'
24
+ | 'link_discovered'
25
+ | 'predictive_consolidation';
26
+
27
+ // Alias for backwards compatibility
28
+ export type MemoryEventType = WebSocketEventType;
29
+
30
+ interface WebSocketMessage {
31
+ type: WebSocketEventType;
32
+ data?: unknown;
33
+ }
34
+
35
+ interface UseMemoryWebSocketOptions {
36
+ enabled?: boolean;
37
+ onMessage?: (event: WebSocketMessage) => void;
38
+ }
39
+
40
+ // Reconnection configuration
41
+ const INITIAL_RECONNECT_DELAY = 1000; // 1 second
42
+ const MAX_RECONNECT_DELAY = 30000; // 30 seconds max
43
+ const MAX_RECONNECT_ATTEMPTS = 10;
44
+
45
+ /**
46
+ * Hook to connect to memory WebSocket and handle real-time updates
47
+ */
48
+ export function useMemoryWebSocket(options: UseMemoryWebSocketOptions = {}) {
49
+ const { enabled = true, onMessage } = options;
50
+ const queryClient = useQueryClient();
51
+ const wsRef = useRef<WebSocket | null>(null);
52
+ const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
53
+ const reconnectAttemptsRef = useRef(0);
54
+ const reconnectDelayRef = useRef(INITIAL_RECONNECT_DELAY);
55
+ const [isConnected, setIsConnected] = useState(false);
56
+ const [lastEvent, setLastEvent] = useState<{
57
+ type: WebSocketEventType;
58
+ data?: unknown;
59
+ timestamp: string;
60
+ } | null>(null);
61
+
62
+ const connect = useCallback(() => {
63
+ if (!enabled || wsRef.current?.readyState === WebSocket.OPEN) return;
64
+
65
+ // Clear any pending reconnect timeout
66
+ if (reconnectTimeoutRef.current) {
67
+ clearTimeout(reconnectTimeoutRef.current);
68
+ reconnectTimeoutRef.current = null;
69
+ }
70
+
71
+ try {
72
+ const ws = new WebSocket(WS_URL);
73
+ wsRef.current = ws;
74
+
75
+ ws.onopen = () => {
76
+ setIsConnected(true);
77
+ // Reset reconnect state on successful connection
78
+ reconnectAttemptsRef.current = 0;
79
+ reconnectDelayRef.current = INITIAL_RECONNECT_DELAY;
80
+ console.log('[WebSocket] Connected to memory server');
81
+ };
82
+
83
+ ws.onmessage = (event) => {
84
+ try {
85
+ const message = JSON.parse(event.data) as WebSocketMessage & { timestamp?: string };
86
+ setLastEvent({
87
+ type: message.type,
88
+ data: message.data,
89
+ timestamp: message.timestamp || new Date().toISOString(),
90
+ });
91
+
92
+ // Notify external handler
93
+ onMessage?.(message);
94
+
95
+ // Invalidate relevant queries based on event type
96
+ switch (message.type) {
97
+ case 'initial_state':
98
+ // Full state received, refresh everything
99
+ queryClient.invalidateQueries({ queryKey: ['memories'] });
100
+ queryClient.invalidateQueries({ queryKey: ['stats'] });
101
+ queryClient.invalidateQueries({ queryKey: ['links'] });
102
+ break;
103
+
104
+ case 'memory_created':
105
+ case 'memory_updated':
106
+ case 'memory_deleted':
107
+ // Memory changed, refresh memories list
108
+ queryClient.invalidateQueries({ queryKey: ['memories'] });
109
+ queryClient.invalidateQueries({ queryKey: ['stats'] });
110
+ break;
111
+
112
+ case 'consolidation_complete':
113
+ // Major changes, refresh everything
114
+ queryClient.invalidateQueries({ queryKey: ['memories'] });
115
+ queryClient.invalidateQueries({ queryKey: ['stats'] });
116
+ queryClient.invalidateQueries({ queryKey: ['links'] });
117
+ break;
118
+
119
+ case 'decay_tick':
120
+ // Just decay scores updated, soft refresh
121
+ // We don't invalidate here to avoid constant refetches
122
+ // The dashboard can handle this via the onMessage callback
123
+ break;
124
+
125
+ // Phase 4: Worker events
126
+ case 'link_discovered':
127
+ // New link created, refresh links
128
+ queryClient.invalidateQueries({ queryKey: ['links'] });
129
+ break;
130
+
131
+ case 'predictive_consolidation':
132
+ // Predictive consolidation ran, refresh everything
133
+ queryClient.invalidateQueries({ queryKey: ['memories'] });
134
+ queryClient.invalidateQueries({ queryKey: ['stats'] });
135
+ queryClient.invalidateQueries({ queryKey: ['links'] });
136
+ break;
137
+
138
+ case 'worker_light_tick':
139
+ case 'worker_medium_tick':
140
+ // Worker ticks don't require cache invalidation
141
+ // Dashboard can track via onMessage callback if needed
142
+ break;
143
+ }
144
+ } catch (err) {
145
+ console.error('[WebSocket] Failed to parse message:', err);
146
+ }
147
+ };
148
+
149
+ ws.onerror = () => {
150
+ // Use warn instead of error to avoid Next.js error overlay in dev mode
151
+ // WebSocket connection failures are expected when API server isn't running
152
+ console.warn('[WebSocket] Connection failed - is the API server running?');
153
+ };
154
+
155
+ ws.onclose = () => {
156
+ setIsConnected(false);
157
+ console.log('[WebSocket] Disconnected');
158
+
159
+ // Attempt to reconnect with exponential backoff
160
+ if (enabled && reconnectAttemptsRef.current < MAX_RECONNECT_ATTEMPTS) {
161
+ const delay = reconnectDelayRef.current;
162
+ reconnectAttemptsRef.current++;
163
+
164
+ // Exponential backoff: double the delay each time, up to max
165
+ reconnectDelayRef.current = Math.min(
166
+ reconnectDelayRef.current * 2,
167
+ MAX_RECONNECT_DELAY
168
+ );
169
+
170
+ console.log(
171
+ `[WebSocket] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS})...`
172
+ );
173
+
174
+ reconnectTimeoutRef.current = setTimeout(() => {
175
+ connect();
176
+ }, delay);
177
+ } else if (reconnectAttemptsRef.current >= MAX_RECONNECT_ATTEMPTS) {
178
+ console.error('[WebSocket] Max reconnection attempts reached. Use reconnect() to try again.');
179
+ }
180
+ };
181
+ } catch (err) {
182
+ console.error('[WebSocket] Failed to connect:', err);
183
+ }
184
+ }, [enabled, queryClient, onMessage]);
185
+
186
+ // Connect on mount
187
+ useEffect(() => {
188
+ if (enabled) {
189
+ connect();
190
+ }
191
+
192
+ return () => {
193
+ if (reconnectTimeoutRef.current) {
194
+ clearTimeout(reconnectTimeoutRef.current);
195
+ }
196
+ if (wsRef.current) {
197
+ wsRef.current.close();
198
+ wsRef.current = null;
199
+ }
200
+ };
201
+ }, [enabled, connect]);
202
+
203
+ // Manual reconnect that resets backoff state
204
+ const manualReconnect = useCallback(() => {
205
+ reconnectAttemptsRef.current = 0;
206
+ reconnectDelayRef.current = INITIAL_RECONNECT_DELAY;
207
+ connect();
208
+ }, [connect]);
209
+
210
+ return {
211
+ isConnected,
212
+ lastEvent,
213
+ reconnect: manualReconnect,
214
+ reconnectAttempts: reconnectAttemptsRef.current,
215
+ };
216
+ }