import React, { useState, useEffect } from 'react'; import { Box, Text, Newline, useInput } from 'ink'; import Spinner from 'ink-spinner'; import { getMemoryManager } from '../../core/global-initialization.ts'; interface MemoryEntry { id: string; key: string; value: string; type: string; tags: string[]; createdAt: Date; updatedAt: Date; size: number; } interface MemoryBrowserProps { onBack: () => void; } export const MemoryBrowser: React.FC = ({ onBack }) => { const [memories, setMemories] = useState([]); const [selectedMemory, setSelectedMemory] = useState(0); const [isLoading, setIsLoading] = useState(true); const [showDetails, setShowDetails] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [filterType, setFilterType] = useState<'all' | 'user' | 'system' | 'context' | 'task'>('all'); const [memoryStats, setMemoryStats] = useState(null); // Load memory data useEffect(() => { const loadMemories = async () => { try { const memoryManager = await getMemoryManager(); // Get memory health status const healthStatus = await memoryManager.getHealthStatus(); setMemoryStats(healthStatus.metrics || {}); // Get memory entries (simulate a query for all entries) const queryResult = await memoryManager.query({ search: searchTerm || undefined, type: filterType !== 'all' && ['observation', 'insight', 'decision', 'artifact', 'error'].includes(filterType) ? filterType as 'observation' | 'insight' | 'decision' | 'artifact' | 'error' : undefined, limit: 50 }); // Transform memory entries const memoryList: MemoryEntry[] = queryResult.map((entry: any) => ({ id: entry.id, key: entry.context?.key || entry.id || 'unnamed', value: entry.content || '', type: entry.type || 'user', tags: entry.tags || [], createdAt: new Date(entry.timestamp || Date.now()), updatedAt: new Date(entry.timestamp || Date.now()), size: JSON.stringify(entry).length })); setMemories(memoryList); setIsLoading(false); } catch (error) { console.error('Error loading memories:', error); setMemories([]); setIsLoading(false); } }; loadMemories(); const interval = setInterval(loadMemories, 5000); // Update every 5 seconds return () => clearInterval(interval); }, [searchTerm, filterType]); // Handle keyboard input useInput((input, key) => { if (key.escape || input === 'q') { onBack(); } else if (key.upArrow && selectedMemory > 0) { setSelectedMemory(selectedMemory - 1); } else if (key.downArrow && selectedMemory < memories.length - 1) { setSelectedMemory(selectedMemory + 1); } else if (key.return) { setShowDetails(!showDetails); } else if (input === 's') { storeNewMemory(); } else if (input === 'd' && memories[selectedMemory]) { deleteMemory(memories[selectedMemory].id); } else if (input === 'c') { clearSearch(); } else if (input === '1') { setFilterType('all'); } else if (input === '2') { setFilterType('user'); } else if (input === '3') { setFilterType('system'); } else if (input === '4') { setFilterType('context'); } else if (input === '5') { setFilterType('task'); } else if (input === '/') { // Toggle search mode (simplified for demo) setSearchTerm(searchTerm ? '' : 'search'); } }); const storeNewMemory = async () => { try { const memoryManager = await getMemoryManager(); const timestamp = new Date().toLocaleTimeString(); const memoryEntry = { id: `ui-memory-${Date.now()}`, agentId: 'ui-user', sessionId: 'ui-session', type: 'artifact' as const, content: `Memory created from UI at ${timestamp}`, context: { source: 'memory-browser', timestamp: Date.now() }, timestamp: new Date(), tags: ['ui-created', 'demo'], version: 1, metadata: { source: 'memory-browser' } }; await memoryManager.store(memoryEntry); // Add to local state immediately for responsiveness const newMemory: MemoryEntry = { id: memoryEntry.id, key: memoryEntry.id, value: memoryEntry.content, type: 'user', tags: ['ui-created', 'demo'], createdAt: new Date(), updatedAt: new Date(), size: 100 }; setMemories(prev => [newMemory, ...prev]); } catch (error) { console.error('Error storing memory:', error); } }; const deleteMemory = async (memoryId: string) => { try { const memoryManager = await getMemoryManager(); await memoryManager.delete(memoryId); setMemories(prev => prev.filter(memory => memory.id !== memoryId)); if (selectedMemory >= memories.length - 1) { setSelectedMemory(Math.max(0, memories.length - 2)); } } catch (error) { console.error('Error deleting memory:', error); } }; const clearSearch = () => { setSearchTerm(''); setFilterType('all'); }; if (isLoading) { return ( Loading memory bank... ); } return ( {/* Header */} 🧠 Memory Bank Browser • {memories.length} entries • Use ↑↓ to select, Enter for details, 's' to store, 'd' to delete, 'q' to quit {/* Filter and Search */} Filter: [1]All [2]User [3]System [4]Context [5]Task {searchTerm && • Search: "{searchTerm}"} [c]Clear [/]Search {/* Memory Statistics */} {memoryStats && ( Statistics Entries: {memoryStats.overview.totalEntries} • Size: {(memoryStats.overview.totalSize / 1024).toFixed(1)}KB • Cache Hit: {(memoryStats.performance.cacheHitRatio * 100).toFixed(1)}% )} {/* Memory List */} {/* Left Panel - Memory List */} Memory Entries ({filterType}) {memories.length === 0 ? ( No memories found. Press 's' to store a new memory. ) : ( memories.map((memory, index) => ( {memory.key.length > 35 ? memory.key.slice(0, 32) + '...' : memory.key} ({memory.size}B) {index === selectedMemory && ( <> └─ Type: {memory.type} {memory.tags.length > 0 && ( └─ Tags: {memory.tags.join(', ')} )} └─ {memory.value.length > 50 ? memory.value.slice(0, 47) + '...' : memory.value} )} )) )} {/* Right Panel - Memory Details */} Memory Details {memories[selectedMemory] ? ( ) : ( Select a memory to view details )} {/* Footer */} [s] Store Memory [d] Delete Memory [c] Clear Filters [/] Search [Enter] Toggle Details [q] Back ); }; // Memory Details Component const MemoryDetails: React.FC<{ memory: MemoryEntry; showDetails: boolean }> = ({ memory, showDetails }) => { const formatDate = (date: Date) => { return date.toLocaleString(); }; const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes}B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`; return `${(bytes / 1024 / 1024).toFixed(1)}MB`; }; return ( {memory.key} ID: {memory.id.slice(-12)} Type: {memory.type} Size: {formatSize(memory.size)} Content: {showDetails ? memory.value : memory.value.length > 100 ? memory.value.slice(0, 97) + '...' : memory.value} Tags: {memory.tags.length > 0 ? ( memory.tags.map((tag, i) => ( #{tag} )) ) : ( No tags )} Timeline: Created: {formatDate(memory.createdAt)} Updated: {formatDate(memory.updatedAt)} {showDetails && ( <> Raw Data: {JSON.stringify({ id: memory.id, key: memory.key, type: memory.type, tags: memory.tags, size: memory.size }, null, 2)} )} ); }; // Helper function to get type color function getTypeColor(type: string): string { switch (type) { case 'user': return 'green'; case 'system': return 'blue'; case 'context': return 'yellow'; case 'task': return 'magenta'; default: return 'white'; } } export default MemoryBrowser;