'use client'; /** * Activity Log Component * * Real-time event stream showing memory operations. * Uses WebSocket for live updates. */ import { useState, useEffect, useRef } from 'react'; import { useMemoryWebSocket, MemoryEventType } from '@/lib/websocket'; interface LogEntry { id: number; timestamp: Date; type: MemoryEventType; message: string; details?: Record; } const EVENT_COLORS: Record = { memory_created: 'text-green-400', memory_accessed: 'text-blue-400', memory_updated: 'text-yellow-400', memory_deleted: 'text-red-400', consolidation_complete: 'text-purple-400', decay_tick: 'text-slate-500', initial_state: 'text-slate-400', worker_light_tick: 'text-slate-500', worker_medium_tick: 'text-slate-500', link_discovered: 'text-cyan-400', predictive_consolidation: 'text-purple-400', update_started: 'text-blue-400', update_complete: 'text-green-400', update_failed: 'text-red-400', server_restarting: 'text-orange-400', }; const EVENT_ICONS: Record = { memory_created: '+', memory_accessed: '👁', memory_updated: '✏', memory_deleted: '✕', consolidation_complete: '🔄', decay_tick: '⏱', initial_state: '📋', worker_light_tick: '⚡', worker_medium_tick: '🔋', link_discovered: '🔗', predictive_consolidation: '🔮', update_started: '⬆', update_complete: '✓', update_failed: '✗', server_restarting: '🔄', }; export function ActivityLog() { const [logs, setLogs] = useState([]); const [autoScroll, setAutoScroll] = useState(true); const [filters, setFilters] = useState>({ memory_created: true, memory_accessed: true, memory_updated: true, memory_deleted: true, consolidation_complete: true, decay_tick: false, // Off by default (noisy) initial_state: false, worker_light_tick: false, worker_medium_tick: false, link_discovered: true, predictive_consolidation: true, update_started: true, update_complete: true, update_failed: true, server_restarting: true, }); const logContainerRef = useRef(null); const nextIdRef = useRef(1); // Connect to WebSocket const { lastEvent, isConnected } = useMemoryWebSocket(); // Process incoming events useEffect(() => { if (!lastEvent) return; const entry: LogEntry = { id: nextIdRef.current++, timestamp: new Date(lastEvent.timestamp), type: lastEvent.type, message: formatEventMessage(lastEvent), details: lastEvent.data as Record, }; setLogs((prev) => { const updated = [...prev, entry]; // Keep only last 500 entries return updated.slice(-500); }); }, [lastEvent]); // Auto-scroll to bottom useEffect(() => { if (autoScroll && logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [logs, autoScroll]); const filteredLogs = logs.filter((log) => filters[log.type]); const toggleFilter = (type: MemoryEventType) => { setFilters((prev) => ({ ...prev, [type]: !prev[type] })); }; const clearLogs = () => { setLogs([]); }; const exportLogs = () => { const json = JSON.stringify(filteredLogs, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `cortex-activity-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); }; return (
{/* Controls */}
{/* Connection Status */}
{isConnected ? 'Connected' : 'Disconnected'}
{/* Filters */}
{(Object.keys(filters) as MemoryEventType[]).map((type) => ( ))}
{/* Actions */}
{/* Log Entries */}
{filteredLogs.length === 0 ? (
{isConnected ? 'Waiting for events...' : 'Not connected to server'}
) : (
{filteredLogs.map((log) => (
{log.timestamp.toLocaleTimeString()} {EVENT_ICONS[log.type]} {log.message}
))}
)}
); } function formatEventMessage(event: { type: MemoryEventType; data?: unknown }): string { const data = event.data as Record | undefined; switch (event.type) { case 'memory_created': return `Created: "${data?.title || 'Unknown'}" (${data?.type || 'unknown'})`; case 'memory_accessed': return `Accessed: "${data?.title || 'Unknown'}" → salience ${((data?.newSalience as number) * 100).toFixed(0)}%`; case 'memory_updated': return `Updated: "${data?.title || 'Unknown'}"`; case 'memory_deleted': return `Deleted: "${data?.title || 'Unknown'}" (ID: ${data?.memoryId})`; case 'consolidation_complete': return `Consolidation: ${data?.consolidated || 0} promoted, ${data?.decayed || 0} decayed, ${data?.deleted || 0} deleted`; case 'decay_tick': return `Decay tick: ${(data?.updates as unknown[])?.length || 0} memories updated`; case 'initial_state': return 'Connected - received initial state'; case 'worker_light_tick': return 'Worker light tick completed'; case 'worker_medium_tick': return 'Worker medium tick completed'; case 'link_discovered': return `Link discovered: ${data?.sourceTitle || '?'} → ${data?.targetTitle || '?'}`; case 'predictive_consolidation': return `Predictive consolidation: ${data?.promoted || 0} promoted`; default: return `Event: ${event.type}`; } }