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.
- package/dashboard/README.md +36 -0
- package/dashboard/components.json +22 -0
- package/dashboard/eslint.config.mjs +18 -0
- package/dashboard/next.config.ts +7 -0
- package/dashboard/package-lock.json +7784 -0
- package/dashboard/package.json +42 -0
- package/dashboard/postcss.config.mjs +7 -0
- package/dashboard/public/file.svg +1 -0
- package/dashboard/public/globe.svg +1 -0
- package/dashboard/public/next.svg +1 -0
- package/dashboard/public/vercel.svg +1 -0
- package/dashboard/public/window.svg +1 -0
- package/dashboard/src/app/favicon.ico +0 -0
- package/dashboard/src/app/globals.css +125 -0
- package/dashboard/src/app/layout.tsx +35 -0
- package/dashboard/src/app/page.tsx +338 -0
- package/dashboard/src/components/Providers.tsx +27 -0
- package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
- package/dashboard/src/components/brain/BrainMesh.tsx +118 -0
- package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
- package/dashboard/src/components/brain/BrainScene.tsx +255 -0
- package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
- package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
- package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
- package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
- package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
- package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
- package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
- package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
- package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
- package/dashboard/src/components/brain/SynapseNodes.tsx +306 -0
- package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
- package/dashboard/src/components/chip/ChipScene.tsx +497 -0
- package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
- package/dashboard/src/components/chip/CortexCore.tsx +210 -0
- package/dashboard/src/components/chip/DataBus.tsx +416 -0
- package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
- package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
- package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
- package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
- package/dashboard/src/components/chip/index.ts +14 -0
- package/dashboard/src/components/controls/ControlPanel.tsx +100 -0
- package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
- package/dashboard/src/components/debug/ActivityLog.tsx +238 -0
- package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
- package/dashboard/src/components/debug/QueryTester.tsx +192 -0
- package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
- package/dashboard/src/components/debug/SqlConsole.tsx +313 -0
- package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
- package/dashboard/src/components/ui/button.tsx +62 -0
- package/dashboard/src/components/ui/card.tsx +92 -0
- package/dashboard/src/components/ui/input.tsx +21 -0
- package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
- package/dashboard/src/hooks/useMemories.ts +276 -0
- package/dashboard/src/hooks/useSuggestions.ts +46 -0
- package/dashboard/src/lib/category-colors.ts +84 -0
- package/dashboard/src/lib/position-algorithm.ts +177 -0
- package/dashboard/src/lib/simplex-noise.ts +217 -0
- package/dashboard/src/lib/store.ts +88 -0
- package/dashboard/src/lib/utils.ts +6 -0
- package/dashboard/src/lib/websocket.ts +216 -0
- package/dashboard/src/types/memory.ts +73 -0
- package/dashboard/tsconfig.json +34 -0
- package/dist/api/control.d.ts +27 -0
- package/dist/api/control.d.ts.map +1 -0
- package/dist/api/control.js +60 -0
- package/dist/api/control.js.map +1 -0
- package/dist/api/visualization-server.d.ts.map +1 -1
- package/dist/api/visualization-server.js +109 -2
- package/dist/api/visualization-server.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -4
- package/dist/index.js.map +1 -1
- package/dist/memory/store.d.ts +6 -0
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +14 -0
- package/dist/memory/store.js.map +1 -1
- 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,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
|
+
}
|