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,113 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Section Label
5
+ * Labels for memory bank sections (STM, Episodic, LTM)
6
+ * Includes count display
7
+ */
8
+
9
+ import { Html } from '@react-three/drei';
10
+ import { MemoryType } from '@/types/memory';
11
+
12
+ interface SectionLabelProps {
13
+ type: MemoryType;
14
+ count: number;
15
+ maxCount: number;
16
+ position: [number, number, number];
17
+ }
18
+
19
+ const SECTION_CONFIG: Record<MemoryType, { label: string; color: string; bgColor: string }> = {
20
+ short_term: {
21
+ label: 'STM BANK',
22
+ color: '#f97316', // orange
23
+ bgColor: 'rgba(249, 115, 22, 0.1)',
24
+ },
25
+ episodic: {
26
+ label: 'EPISODIC BANK',
27
+ color: '#a855f7', // purple
28
+ bgColor: 'rgba(168, 85, 247, 0.1)',
29
+ },
30
+ long_term: {
31
+ label: 'LONG-TERM BANK',
32
+ color: '#3b82f6', // blue
33
+ bgColor: 'rgba(59, 130, 246, 0.1)',
34
+ },
35
+ };
36
+
37
+ export function SectionLabel({ type, count, maxCount, position }: SectionLabelProps) {
38
+ const config = SECTION_CONFIG[type];
39
+
40
+ return (
41
+ <Html
42
+ position={position}
43
+ center
44
+ style={{
45
+ pointerEvents: 'none',
46
+ userSelect: 'none',
47
+ }}
48
+ >
49
+ <div
50
+ className="flex items-center gap-3 px-3 py-1.5 rounded border font-mono text-xs tracking-wider"
51
+ style={{
52
+ backgroundColor: config.bgColor,
53
+ borderColor: `${config.color}40`,
54
+ }}
55
+ >
56
+ <span style={{ color: config.color }}>{config.label}</span>
57
+ <span className="text-slate-500">|</span>
58
+ <span className="text-slate-400">
59
+ {count}/{maxCount}
60
+ </span>
61
+ </div>
62
+ </Html>
63
+ );
64
+ }
65
+
66
+ // All section labels together
67
+ interface AllSectionLabelsProps {
68
+ stmCount: number;
69
+ episodicCount: number;
70
+ ltmCount: number;
71
+ chipWidth: number;
72
+ chipHeight: number;
73
+ }
74
+
75
+ export function AllSectionLabels({
76
+ stmCount,
77
+ episodicCount,
78
+ ltmCount,
79
+ chipWidth,
80
+ chipHeight,
81
+ }: AllSectionLabelsProps) {
82
+ const sectionHeight = chipHeight / 3;
83
+ const halfChipHeight = chipHeight / 2;
84
+ const halfChipWidth = chipWidth / 2;
85
+
86
+ return (
87
+ <group>
88
+ {/* STM label (top) */}
89
+ <SectionLabel
90
+ type="short_term"
91
+ count={stmCount}
92
+ maxCount={100}
93
+ position={[-halfChipWidth + 1.5, halfChipHeight - 0.3, 0.3]}
94
+ />
95
+
96
+ {/* Episodic label (middle left) */}
97
+ <SectionLabel
98
+ type="episodic"
99
+ count={episodicCount}
100
+ maxCount={500}
101
+ position={[-halfChipWidth + 1.5, 0, 0.3]}
102
+ />
103
+
104
+ {/* LTM label (bottom) */}
105
+ <SectionLabel
106
+ type="long_term"
107
+ count={ltmCount}
108
+ maxCount={1000}
109
+ position={[-halfChipWidth + 1.5, -halfChipHeight + sectionHeight - 0.3, 0.3]}
110
+ />
111
+ </group>
112
+ );
113
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Chip Components
3
+ * Export all chip visualization components
4
+ */
5
+
6
+ export { ChipScene } from './ChipScene';
7
+ export { ChipSubstrate } from './ChipSubstrate';
8
+ export { CortexCore } from './CortexCore';
9
+ export { DataBus, DataBusPulse } from './DataBus';
10
+ export type { AccessPulseData } from './DataBus';
11
+ export { MemoryCell, MemoryCellFlash } from './MemoryCell';
12
+ export { QuantumCell } from './QuantumCell';
13
+ export { MemoryGrid, EpisodicMemoryGrid, getMemoryGridPosition } from './MemoryGrid';
14
+ export { SectionLabel, AllSectionLabels } from './SectionLabel';
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Control Panel Component
5
+ *
6
+ * Provides controls for pausing/resuming memory creation
7
+ * and displays server status including uptime.
8
+ */
9
+
10
+ import { useControlStatus, usePauseMemory, useResumeMemory, useConsolidate } from '@/hooks/useMemories';
11
+ import { Button } from '@/components/ui/button';
12
+
13
+ export function ControlPanel() {
14
+ const { data: status, isLoading } = useControlStatus();
15
+ const pauseMutation = usePauseMemory();
16
+ const resumeMutation = useResumeMemory();
17
+ const consolidateMutation = useConsolidate();
18
+
19
+ const isPaused = status?.paused ?? false;
20
+ const isToggling = pauseMutation.isPending || resumeMutation.isPending;
21
+
22
+ const handleTogglePause = () => {
23
+ if (isPaused) {
24
+ resumeMutation.mutate();
25
+ } else {
26
+ pauseMutation.mutate();
27
+ }
28
+ };
29
+
30
+ const handleConsolidate = () => {
31
+ consolidateMutation.mutate();
32
+ };
33
+
34
+ if (isLoading) {
35
+ return (
36
+ <div className="p-3 rounded-lg bg-slate-800/50 border border-slate-700 animate-pulse">
37
+ <div className="h-4 bg-slate-700 rounded w-24 mb-2"></div>
38
+ <div className="h-8 bg-slate-700 rounded w-full"></div>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ return (
44
+ <div className="space-y-3">
45
+ {/* Status Banner (only when paused) */}
46
+ {isPaused && (
47
+ <div className="px-3 py-2 rounded-lg bg-orange-500/20 border border-orange-500/50 text-orange-300 text-sm flex items-center gap-2">
48
+ <span className="text-lg">⏸</span>
49
+ <span>Memory creation paused</span>
50
+ </div>
51
+ )}
52
+
53
+ {/* Server Status */}
54
+ <div className="p-3 rounded-lg bg-slate-800/50 border border-slate-700">
55
+ <div className="flex items-center justify-between mb-3">
56
+ <span className="text-sm text-slate-400">Server Status</span>
57
+ <div className="flex items-center gap-2">
58
+ <span
59
+ className={`w-2 h-2 rounded-full ${isPaused ? 'bg-orange-500' : 'bg-green-500'}`}
60
+ />
61
+ <span className="text-xs text-slate-300">
62
+ {isPaused ? 'Paused' : 'Active'}
63
+ </span>
64
+ </div>
65
+ </div>
66
+
67
+ <div className="text-xs text-slate-500 mb-3">
68
+ Uptime: {status?.uptimeFormatted || '—'}
69
+ </div>
70
+
71
+ {/* Control Buttons */}
72
+ <div className="flex gap-2">
73
+ <Button
74
+ variant="outline"
75
+ size="sm"
76
+ onClick={handleTogglePause}
77
+ disabled={isToggling}
78
+ className={`flex-1 ${
79
+ isPaused
80
+ ? 'border-green-600 text-green-400 hover:bg-green-600/20 hover:text-green-300'
81
+ : 'border-orange-600 text-orange-400 hover:bg-orange-600/20 hover:text-orange-300'
82
+ }`}
83
+ >
84
+ {isToggling ? '...' : isPaused ? '▶ Resume' : '⏸ Pause'}
85
+ </Button>
86
+
87
+ <Button
88
+ variant="outline"
89
+ size="sm"
90
+ onClick={handleConsolidate}
91
+ disabled={consolidateMutation.isPending}
92
+ className="flex-1 border-slate-600 text-slate-300 hover:bg-slate-600/20"
93
+ >
94
+ {consolidateMutation.isPending ? '...' : '🔄 Consolidate'}
95
+ </Button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Stats Panel
5
+ * Displays memory statistics and health metrics
6
+ */
7
+
8
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
9
+ import { MemoryStats } from '@/types/memory';
10
+ import { CATEGORY_COLORS } from '@/lib/category-colors';
11
+
12
+ interface StatsPanelProps {
13
+ stats: MemoryStats | undefined;
14
+ isLoading: boolean;
15
+ }
16
+
17
+ export function StatsPanel({ stats, isLoading }: StatsPanelProps) {
18
+ if (isLoading) {
19
+ return (
20
+ <div className="space-y-4 animate-pulse">
21
+ <div className="h-32 bg-slate-800 rounded-lg" />
22
+ <div className="h-48 bg-slate-800 rounded-lg" />
23
+ </div>
24
+ );
25
+ }
26
+
27
+ if (!stats) {
28
+ return (
29
+ <Card className="bg-slate-900 border-slate-700">
30
+ <CardContent className="p-4 text-slate-400 text-center">
31
+ No data available
32
+ </CardContent>
33
+ </Card>
34
+ );
35
+ }
36
+
37
+ const healthPercentage = stats.decayDistribution
38
+ ? Math.round(
39
+ (stats.decayDistribution.healthy /
40
+ (stats.decayDistribution.healthy +
41
+ stats.decayDistribution.fading +
42
+ stats.decayDistribution.critical)) *
43
+ 100
44
+ ) || 0
45
+ : 0;
46
+
47
+ return (
48
+ <div className="space-y-4">
49
+ {/* Health Indicator */}
50
+ <Card className="bg-slate-900 border-slate-700">
51
+ <CardHeader className="pb-2">
52
+ <CardTitle className="text-sm font-medium text-slate-300">
53
+ System Health
54
+ </CardTitle>
55
+ </CardHeader>
56
+ <CardContent className="space-y-3">
57
+ <div className="flex items-center gap-3">
58
+ <div
59
+ className={`w-3 h-3 rounded-full ${
60
+ healthPercentage > 70
61
+ ? 'bg-green-500'
62
+ : healthPercentage > 40
63
+ ? 'bg-yellow-500'
64
+ : 'bg-red-500'
65
+ }`}
66
+ />
67
+ <span className="text-white font-medium">{healthPercentage}% Healthy</span>
68
+ </div>
69
+ {stats.decayDistribution && (
70
+ <div className="flex gap-1 h-2 rounded-full overflow-hidden">
71
+ <div
72
+ className="bg-green-500 transition-all"
73
+ style={{
74
+ width: `${
75
+ (stats.decayDistribution.healthy / stats.total) * 100
76
+ }%`,
77
+ }}
78
+ />
79
+ <div
80
+ className="bg-yellow-500 transition-all"
81
+ style={{
82
+ width: `${
83
+ (stats.decayDistribution.fading / stats.total) * 100
84
+ }%`,
85
+ }}
86
+ />
87
+ <div
88
+ className="bg-red-500 transition-all"
89
+ style={{
90
+ width: `${
91
+ (stats.decayDistribution.critical / stats.total) * 100
92
+ }%`,
93
+ }}
94
+ />
95
+ </div>
96
+ )}
97
+ <div className="text-xs text-slate-400">
98
+ Avg Salience: {(stats.averageSalience * 100).toFixed(0)}%
99
+ </div>
100
+ </CardContent>
101
+ </Card>
102
+
103
+ {/* Memory Counts */}
104
+ <Card className="bg-slate-900 border-slate-700">
105
+ <CardHeader className="pb-2">
106
+ <CardTitle className="text-sm font-medium text-slate-300">
107
+ Memory Stats
108
+ </CardTitle>
109
+ </CardHeader>
110
+ <CardContent className="space-y-2">
111
+ <div className="flex justify-between items-center">
112
+ <span className="text-slate-400">Total</span>
113
+ <span className="text-white font-mono">{stats.total}</span>
114
+ </div>
115
+ <div className="flex justify-between items-center">
116
+ <span className="text-orange-400">Short-Term</span>
117
+ <span className="text-white font-mono">{stats.shortTerm}</span>
118
+ </div>
119
+ <div className="flex justify-between items-center">
120
+ <span className="text-blue-400">Long-Term</span>
121
+ <span className="text-white font-mono">{stats.longTerm}</span>
122
+ </div>
123
+ <div className="flex justify-between items-center">
124
+ <span className="text-purple-400">Episodic</span>
125
+ <span className="text-white font-mono">{stats.episodic}</span>
126
+ </div>
127
+ </CardContent>
128
+ </Card>
129
+
130
+ {/* Categories */}
131
+ <Card className="bg-slate-900 border-slate-700">
132
+ <CardHeader className="pb-2">
133
+ <CardTitle className="text-sm font-medium text-slate-300">
134
+ By Category
135
+ </CardTitle>
136
+ </CardHeader>
137
+ <CardContent className="space-y-2">
138
+ {Object.entries(stats.byCategory)
139
+ .sort(([, a], [, b]) => b - a)
140
+ .slice(0, 6)
141
+ .map(([category, count]) => (
142
+ <div key={category} className="space-y-1">
143
+ <div className="flex justify-between items-center text-xs">
144
+ <span className="text-slate-400 capitalize">{category}</span>
145
+ <span className="text-white font-mono">{count}</span>
146
+ </div>
147
+ <div className="h-1.5 bg-slate-800 rounded-full overflow-hidden">
148
+ <div
149
+ className="h-full rounded-full transition-all"
150
+ style={{
151
+ width: `${(count / stats.total) * 100}%`,
152
+ backgroundColor:
153
+ CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS] ||
154
+ '#6B7280',
155
+ }}
156
+ />
157
+ </div>
158
+ </div>
159
+ ))}
160
+ </CardContent>
161
+ </Card>
162
+ </div>
163
+ );
164
+ }
@@ -0,0 +1,238 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Activity Log Component
5
+ *
6
+ * Real-time event stream showing memory operations.
7
+ * Uses WebSocket for live updates.
8
+ */
9
+
10
+ import { useState, useEffect, useRef } from 'react';
11
+ import { useMemoryWebSocket, MemoryEventType } from '@/lib/websocket';
12
+
13
+ interface LogEntry {
14
+ id: number;
15
+ timestamp: Date;
16
+ type: MemoryEventType;
17
+ message: string;
18
+ details?: Record<string, unknown>;
19
+ }
20
+
21
+ const EVENT_COLORS: Record<MemoryEventType, string> = {
22
+ memory_created: 'text-green-400',
23
+ memory_accessed: 'text-blue-400',
24
+ memory_updated: 'text-yellow-400',
25
+ memory_deleted: 'text-red-400',
26
+ consolidation_complete: 'text-purple-400',
27
+ decay_tick: 'text-slate-500',
28
+ initial_state: 'text-slate-400',
29
+ worker_light_tick: 'text-slate-500',
30
+ worker_medium_tick: 'text-slate-500',
31
+ link_discovered: 'text-cyan-400',
32
+ predictive_consolidation: 'text-purple-400',
33
+ };
34
+
35
+ const EVENT_ICONS: Record<MemoryEventType, string> = {
36
+ memory_created: '+',
37
+ memory_accessed: '👁',
38
+ memory_updated: '✏',
39
+ memory_deleted: '✕',
40
+ consolidation_complete: '🔄',
41
+ decay_tick: '⏱',
42
+ initial_state: '📋',
43
+ worker_light_tick: '⚡',
44
+ worker_medium_tick: '🔋',
45
+ link_discovered: '🔗',
46
+ predictive_consolidation: '🔮',
47
+ };
48
+
49
+ export function ActivityLog() {
50
+ const [logs, setLogs] = useState<LogEntry[]>([]);
51
+ const [autoScroll, setAutoScroll] = useState(true);
52
+ const [filters, setFilters] = useState<Record<MemoryEventType, boolean>>({
53
+ memory_created: true,
54
+ memory_accessed: true,
55
+ memory_updated: true,
56
+ memory_deleted: true,
57
+ consolidation_complete: true,
58
+ decay_tick: false, // Off by default (noisy)
59
+ initial_state: false,
60
+ worker_light_tick: false,
61
+ worker_medium_tick: false,
62
+ link_discovered: true,
63
+ predictive_consolidation: true,
64
+ });
65
+
66
+ const logContainerRef = useRef<HTMLDivElement>(null);
67
+ const nextIdRef = useRef(1);
68
+
69
+ // Connect to WebSocket
70
+ const { lastEvent, isConnected } = useMemoryWebSocket();
71
+
72
+ // Process incoming events
73
+ useEffect(() => {
74
+ if (!lastEvent) return;
75
+
76
+ const entry: LogEntry = {
77
+ id: nextIdRef.current++,
78
+ timestamp: new Date(lastEvent.timestamp),
79
+ type: lastEvent.type,
80
+ message: formatEventMessage(lastEvent),
81
+ details: lastEvent.data as Record<string, unknown>,
82
+ };
83
+
84
+ setLogs((prev) => {
85
+ const updated = [...prev, entry];
86
+ // Keep only last 500 entries
87
+ return updated.slice(-500);
88
+ });
89
+ }, [lastEvent]);
90
+
91
+ // Auto-scroll to bottom
92
+ useEffect(() => {
93
+ if (autoScroll && logContainerRef.current) {
94
+ logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
95
+ }
96
+ }, [logs, autoScroll]);
97
+
98
+ const filteredLogs = logs.filter((log) => filters[log.type]);
99
+
100
+ const toggleFilter = (type: MemoryEventType) => {
101
+ setFilters((prev) => ({ ...prev, [type]: !prev[type] }));
102
+ };
103
+
104
+ const clearLogs = () => {
105
+ setLogs([]);
106
+ };
107
+
108
+ const exportLogs = () => {
109
+ const json = JSON.stringify(filteredLogs, null, 2);
110
+ const blob = new Blob([json], { type: 'application/json' });
111
+ const url = URL.createObjectURL(blob);
112
+ const a = document.createElement('a');
113
+ a.href = url;
114
+ a.download = `cortex-activity-${new Date().toISOString().split('T')[0]}.json`;
115
+ a.click();
116
+ URL.revokeObjectURL(url);
117
+ };
118
+
119
+ return (
120
+ <div className="h-full flex flex-col">
121
+ {/* Controls */}
122
+ <div className="p-3 border-b border-slate-700 flex items-center gap-3 flex-wrap">
123
+ {/* Connection Status */}
124
+ <div className="flex items-center gap-2 text-xs">
125
+ <span
126
+ className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`}
127
+ />
128
+ <span className="text-slate-400">
129
+ {isConnected ? 'Connected' : 'Disconnected'}
130
+ </span>
131
+ </div>
132
+
133
+ <div className="w-px h-4 bg-slate-700" />
134
+
135
+ {/* Filters */}
136
+ <div className="flex items-center gap-1 flex-wrap">
137
+ {(Object.keys(filters) as MemoryEventType[]).map((type) => (
138
+ <button
139
+ key={type}
140
+ onClick={() => toggleFilter(type)}
141
+ className={`px-2 py-0.5 text-xs rounded transition-colors ${
142
+ filters[type]
143
+ ? `${EVENT_COLORS[type]} bg-slate-700`
144
+ : 'text-slate-600 bg-slate-800'
145
+ }`}
146
+ >
147
+ {type.replace(/_/g, ' ').replace('memory ', '')}
148
+ </button>
149
+ ))}
150
+ </div>
151
+
152
+ <div className="flex-1" />
153
+
154
+ {/* Actions */}
155
+ <label className="flex items-center gap-1 text-xs text-slate-400 cursor-pointer">
156
+ <input
157
+ type="checkbox"
158
+ checked={autoScroll}
159
+ onChange={(e) => setAutoScroll(e.target.checked)}
160
+ className="rounded border-slate-600 bg-slate-800"
161
+ />
162
+ Auto-scroll
163
+ </label>
164
+
165
+ <button
166
+ onClick={exportLogs}
167
+ className="text-xs text-slate-400 hover:text-white"
168
+ >
169
+ Export
170
+ </button>
171
+
172
+ <button
173
+ onClick={clearLogs}
174
+ className="text-xs text-red-400 hover:text-red-300"
175
+ >
176
+ Clear
177
+ </button>
178
+ </div>
179
+
180
+ {/* Log Entries */}
181
+ <div
182
+ ref={logContainerRef}
183
+ className="flex-1 overflow-auto p-3 font-mono text-xs"
184
+ >
185
+ {filteredLogs.length === 0 ? (
186
+ <div className="text-slate-500 text-center py-8">
187
+ {isConnected ? 'Waiting for events...' : 'Not connected to server'}
188
+ </div>
189
+ ) : (
190
+ <div className="space-y-1">
191
+ {filteredLogs.map((log) => (
192
+ <div key={log.id} className="flex gap-2 hover:bg-slate-800/50 px-1 rounded">
193
+ <span className="text-slate-500 shrink-0">
194
+ {log.timestamp.toLocaleTimeString()}
195
+ </span>
196
+ <span className={`shrink-0 w-4 ${EVENT_COLORS[log.type]}`}>
197
+ {EVENT_ICONS[log.type]}
198
+ </span>
199
+ <span className={EVENT_COLORS[log.type]}>{log.message}</span>
200
+ </div>
201
+ ))}
202
+ </div>
203
+ )}
204
+ </div>
205
+ </div>
206
+ );
207
+ }
208
+
209
+ function formatEventMessage(event: { type: MemoryEventType; data?: unknown }): string {
210
+ const data = event.data as Record<string, unknown> | undefined;
211
+
212
+ switch (event.type) {
213
+ case 'memory_created':
214
+ return `Created: "${data?.title || 'Unknown'}" (${data?.type || 'unknown'})`;
215
+ case 'memory_accessed':
216
+ return `Accessed: "${data?.title || 'Unknown'}" → salience ${((data?.newSalience as number) * 100).toFixed(0)}%`;
217
+ case 'memory_updated':
218
+ return `Updated: "${data?.title || 'Unknown'}"`;
219
+ case 'memory_deleted':
220
+ return `Deleted: "${data?.title || 'Unknown'}" (ID: ${data?.memoryId})`;
221
+ case 'consolidation_complete':
222
+ return `Consolidation: ${data?.consolidated || 0} promoted, ${data?.decayed || 0} decayed, ${data?.deleted || 0} deleted`;
223
+ case 'decay_tick':
224
+ return `Decay tick: ${(data?.updates as unknown[])?.length || 0} memories updated`;
225
+ case 'initial_state':
226
+ return 'Connected - received initial state';
227
+ case 'worker_light_tick':
228
+ return 'Worker light tick completed';
229
+ case 'worker_medium_tick':
230
+ return 'Worker medium tick completed';
231
+ case 'link_discovered':
232
+ return `Link discovered: ${data?.sourceTitle || '?'} → ${data?.targetTitle || '?'}`;
233
+ case 'predictive_consolidation':
234
+ return `Predictive consolidation: ${data?.promoted || 0} promoted`;
235
+ default:
236
+ return `Event: ${event.type}`;
237
+ }
238
+ }