'use client'; /** * Chip Scene - Circuit Board Style * Clean top-down orthographic view of memory as a PCB */ import { Suspense, useMemo, useState, useCallback, useRef } from 'react'; import { Canvas } from '@react-three/fiber'; import { OrthographicCamera } from '@react-three/drei'; import { EffectComposer, Bloom } from '@react-three/postprocessing'; import { Memory, MemoryLink } from '@/types/memory'; import { useMemoryWebSocket } from '@/lib/websocket'; import * as THREE from 'three'; // Layout constants const CHIP_WIDTH = 24; const CHIP_HEIGHT = 16; const NODE_SPACING = 1.2; const SECTION_GAP = 2; // Category colors const CATEGORY_COLORS: Record = { architecture: '#06b6d4', // cyan pattern: '#22c55e', // green preference: '#eab308', // yellow error: '#ef4444', // red context: '#f97316', // orange learning: '#84cc16', // lime todo: '#a855f7', // purple note: '#3b82f6', // blue relationship: '#6366f1', // indigo custom: '#ec4899', // pink }; interface ChipSceneProps { memories: Memory[]; links?: MemoryLink[]; selectedMemory: Memory | null; onSelectMemory: (memory: Memory | null) => void; } export function ChipScene({ memories = [], links = [], selectedMemory, onSelectMemory, }: ChipSceneProps) { const [hoveredMemory, setHoveredMemory] = useState(null); // Group memories by type const groupedMemories = useMemo(() => { const stm = memories.filter((m) => m.type === 'short_term'); const episodic = memories.filter((m) => m.type === 'episodic'); const ltm = memories.filter((m) => m.type === 'long_term'); return { stm, episodic, ltm }; }, [memories]); return (
onSelectMemory(null)} > {/* Hover tooltip */} {hoveredMemory && (
{hoveredMemory.title}
{hoveredMemory.category} | {Math.round(hoveredMemory.salience * 100)}% salience
)} {/* Legend */}

Memory Banks

STM {groupedMemories.stm.length}
Episodic {groupedMemories.episodic.length}
Long-Term {groupedMemories.ltm.length}
{memories.length} total memories
{/* Status */}
CORTEX PCB v1.0
); } interface ChipContentProps { memories: Memory[]; groupedMemories: { stm: Memory[]; episodic: Memory[]; ltm: Memory[] }; links: MemoryLink[]; selectedMemory: Memory | null; hoveredMemory: Memory | null; onSelectMemory: (memory: Memory | null) => void; onHoverMemory: (memory: Memory | null) => void; } function ChipContent({ memories, groupedMemories, links, selectedMemory, hoveredMemory, onSelectMemory, onHoverMemory, }: ChipContentProps) { // Calculate grid layout for each section const layoutConfig = useMemo(() => { const cols = 12; const stmRows = Math.ceil(groupedMemories.stm.length / cols) || 1; const episodicRows = Math.ceil(groupedMemories.episodic.length / cols) || 1; const ltmRows = Math.ceil(groupedMemories.ltm.length / cols) || 1; const totalHeight = (stmRows + episodicRows + ltmRows) * NODE_SPACING + SECTION_GAP * 2; const startY = totalHeight / 2; return { cols, stm: { startY: startY - NODE_SPACING / 2, rows: stmRows, }, episodic: { startY: startY - stmRows * NODE_SPACING - SECTION_GAP, rows: episodicRows, }, ltm: { startY: startY - stmRows * NODE_SPACING - episodicRows * NODE_SPACING - SECTION_GAP * 2, rows: ltmRows, }, }; }, [groupedMemories]); // Calculate positions for all memories const memoryPositions = useMemo(() => { const positions = new Map(); const gridWidth = layoutConfig.cols * NODE_SPACING; const startX = -gridWidth / 2 + NODE_SPACING / 2; // STM positions groupedMemories.stm.forEach((m, i) => { const col = i % layoutConfig.cols; const row = Math.floor(i / layoutConfig.cols); const x = startX + col * NODE_SPACING; const y = layoutConfig.stm.startY - row * NODE_SPACING; positions.set(m.id, [x, y]); }); // Episodic positions groupedMemories.episodic.forEach((m, i) => { const col = i % layoutConfig.cols; const row = Math.floor(i / layoutConfig.cols); const x = startX + col * NODE_SPACING; const y = layoutConfig.episodic.startY - row * NODE_SPACING; positions.set(m.id, [x, y]); }); // LTM positions groupedMemories.ltm.forEach((m, i) => { const col = i % layoutConfig.cols; const row = Math.floor(i / layoutConfig.cols); const x = startX + col * NODE_SPACING; const y = layoutConfig.ltm.startY - row * NODE_SPACING; positions.set(m.id, [x, y]); }); return positions; }, [groupedMemories, layoutConfig]); return ( <> {/* Subtle ambient light */} {/* PCB Background */} {/* Section dividers and labels */} {/* Memory links (PCB traces) */} {/* Memory nodes */} {memories.map((memory) => { const pos = memoryPositions.get(memory.id); if (!pos) return null; return ( ); })} {/* Post-processing - subtle bloom */} ); } // PCB-style background with grid function PCBBackground({ width, height }: { width: number; height: number }) { const gridGeometry = useMemo(() => { const points: THREE.Vector3[] = []; const spacing = 1; // Vertical lines for (let x = -width / 2; x <= width / 2; x += spacing) { points.push(new THREE.Vector3(x, -height / 2, 0)); points.push(new THREE.Vector3(x, height / 2, 0)); } // Horizontal lines for (let y = -height / 2; y <= height / 2; y += spacing) { points.push(new THREE.Vector3(-width / 2, y, 0)); points.push(new THREE.Vector3(width / 2, y, 0)); } return new THREE.BufferGeometry().setFromPoints(points); }, [width, height]); return ( {/* Dark background */} {/* PCB substrate */} {/* Grid lines */} {/* Border */} ); } // Section dividers and labels function SectionDividers({ layoutConfig, cols, }: { layoutConfig: { stm: { startY: number; rows: number }; episodic: { startY: number; rows: number }; ltm: { startY: number; rows: number }; cols: number }; cols: number; }) { const gridWidth = cols * NODE_SPACING; // Divider line between STM and Episodic const divider1Y = layoutConfig.stm.startY - layoutConfig.stm.rows * NODE_SPACING - SECTION_GAP / 2 + NODE_SPACING / 2; // Divider line between Episodic and LTM const divider2Y = layoutConfig.episodic.startY - layoutConfig.episodic.rows * NODE_SPACING - SECTION_GAP / 2 + NODE_SPACING / 2; return ( {/* STM label */} {/* Divider 1 */} {/* Episodic label */} {/* Divider 2 */} {/* LTM label */} ); } // Section label as 3D text alternative (simple box with color) function SectionLabel({ text, position, color }: { text: string; position: [number, number, number]; color: string }) { return ( ); } // Memory traces (connections between related memories) function MemoryTraces({ links, positions, }: { links: MemoryLink[]; positions: Map; }) { const traceGeometry = useMemo(() => { const points: number[] = []; links.forEach((link) => { const sourcePos = positions.get(link.source_id); const targetPos = positions.get(link.target_id); if (sourcePos && targetPos) { points.push(sourcePos[0], sourcePos[1], 0.1); points.push(targetPos[0], targetPos[1], 0.1); } }); const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3)); return geometry; }, [links, positions]); if (links.length === 0) return null; return ( ); } // Individual memory node function MemoryNode({ memory, position, isSelected, isHovered, onSelect, onHover, }: { memory: Memory; position: [number, number]; isSelected: boolean; isHovered: boolean; onSelect: (memory: Memory | null) => void; onHover: (memory: Memory | null) => void; }) { const color = CATEGORY_COLORS[memory.category] || CATEGORY_COLORS.custom; const isQuantum = memory.salience >= 0.7; // Size based on salience const baseSize = 0.25; const size = baseSize + memory.salience * 0.15; return ( {/* Main node */} { e.stopPropagation(); onSelect(isSelected ? null : memory); }} onPointerOver={(e) => { e.stopPropagation(); onHover(memory); document.body.style.cursor = 'pointer'; }} onPointerOut={() => { onHover(null); document.body.style.cursor = 'auto'; }} > {/* Quantum glow ring */} {isQuantum && ( )} {/* Selection ring */} {isSelected && ( )} {/* Hover highlight */} {isHovered && !isSelected && ( )} ); }