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,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
|
+
}
|