1→import { useState, useEffect } from 'react' 2→import { Brain, Globe, Server, Plus, Search, Trash2, X, Pencil } from 'lucide-react' 3→import * as Dialog from '@radix-ui/react-dialog' 4→import * as AlertDialog from '@radix-ui/react-alert-dialog' 5→import { useProjectStore } from '../stores/projectStore' 6→ 7→type MemoryTab = 'project' | 'global' | 'infrastructure' 8→ 9→interface ProjectMemoryItem { 10→ id: number 11→ project_id: number 12→ type: string 13→ content: string 14→ metadata: Record | null 15→ created_at: string 16→} 17→ 18→interface GlobalMemoryItem { 19→ id: number 20→ type: string 21→ content: string 22→ metadata: Record | null 23→ usage_count: number 24→ created_at: string 25→} 26→ 27→interface InfrastructureItem { 28→ id: number 29→ name: string 30→ type: string 31→ config: Record 32→ projects: number[] | null 33→ created_at: string 34→ updated_at: string 35→} 36→ 37→const API_BASE = 'http://localhost:8000/api/memory' 38→ 39→export default function MemoryManager() { 40→ const { currentProject } = useProjectStore() 41→ const [activeTab, setActiveTab] = useState('project') 42→ const [searchQuery, setSearchQuery] = useState('') 43→ 44→ // Data states 45→ const [projectMemory, setProjectMemory] = useState([]) 46→ const [globalMemory, setGlobalMemory] = useState([]) 47→ const [infrastructure, setInfrastructure] = useState([]) 48→ const [isLoading, setIsLoading] = useState(false) 49→ 50→ // Dialog states 51→ const [showAddDialog, setShowAddDialog] = useState(false) 52→ const [newItemType, setNewItemType] = useState('') 53→ const [newItemContent, setNewItemContent] = useState('') 54→ const [newItemName, setNewItemName] = useState('') 55→ const [isSaving, setIsSaving] = useState(false) 56→ 57→ // Delete dialog states 58→ const [showDeleteDialog, setShowDeleteDialog] = useState(false) 59→ const [itemToDelete, setItemToDelete] = useState<{ id: number; type: MemoryTab } | null>(null) 60→ const [isDeleting, setIsDeleting] = useState(false) 61→ 62→ // Edit dialog states 63→ const [showEditDialog, setShowEditDialog] = useState(false) 64→ const [editItem, setEditItem] = useState<{ id: number; type: string; content: string; memoryType: MemoryTab } | null>(null) 65→ const [editItemType, setEditItemType] = useState('') 66→ const [editItemContent, setEditItemContent] = useState('') 67→ const [isEditing, setIsEditing] = useState(false) 68→ 69→ const tabs = [ 70→ { id: 'project' as const, label: 'Project Memory', icon: Brain }, 71→ { id: 'global' as const, label: 'Global Memory', icon: Globe }, 72→ { id: 'infrastructure' as const, label: 'Infrastructure', icon: Server }, 73→ ] 74→ 75→ // Fetch data based on active tab 76→ useEffect(() => { 77→ const fetchData = async () => { 78→ setIsLoading(true) 79→ try { 80→ if (activeTab === 'project' && currentProject) { 81→ const response = await fetch(`${API_BASE}/project/${currentProject.id}`) 82→ if (response.ok) { 83→ const data = await response.json() 84→ setProjectMemory(data) 85→ } 86→ } else if (activeTab === 'global') { 87→ const response = await fetch(`${API_BASE}/global`) 88→ if (response.ok) { 89→ const data = await response.json() 90→ setGlobalMemory(data) 91→ } 92→ } else if (activeTab === 'infrastructure') { 93→ const response = await fetch(`${API_BASE}/infrastructure`) 94→ if (response.ok) { 95→ const data = await response.json() 96→ setInfrastructure(data) 97→ } 98→ } 99→ } catch (error) { 100→ console.error('Failed to fetch memory items:', error) 101→ } finally { 102→ setIsLoading(false) 103→ } 104→ } 105→ fetchData() 106→ }, [activeTab, currentProject]) 107→ 108→ const getTypeOptions = () => { 109→ switch (activeTab) { 110→ case 'project': 111→ return ['insight', 'pattern', 'gotcha', 'codebase_map'] 112→ case 'global': 113→ return ['preference', 'pattern', 'solution'] 114→ case 'infrastructure': 115→ return ['server', 'docker', 'database', 'deployment'] 116→ default: 117→ return [] 118→ } 119→ } 120→ 121→ const handleOpenAddDialog = () => { 122→ setNewItemType(getTypeOptions()[0] || '') 123→ setNewItemContent('') 124→ setNewItemName('') 125→ setShowAddDialog(true) 126→ } 127→ 128→ const handleAddItem = async () => { 129→ if (!newItemType || (!newItemContent.trim() && activeTab !== 'infrastructure')) return 130→ if (activeTab === 'infrastructure' && !newItemName.trim()) return 131→ 132→ setIsSaving(true) 133→ try { 134→ let response: Response 135→ 136→ if (activeTab === 'project' && currentProject) { 137→ response = await fetch(`${API_BASE}/project/${currentProject.id}`, { 138→ method: 'POST', 139→ headers: { 'Content-Type': 'application/json' }, 140→ body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }), 141→ }) 142→ if (response.ok) { 143→ const newItem = await response.json() 144→ setProjectMemory([...projectMemory, newItem]) 145→ } 146→ } else if (activeTab === 'global') { 147→ response = await fetch(`${API_BASE}/global`, { 148→ method: 'POST', 149→ headers: { 'Content-Type': 'application/json' }, 150→ body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }), 151→ }) 152→ if (response.ok) { 153→ const newItem = await response.json() 154→ setGlobalMemory([...globalMemory, newItem]) 155→ } 156→ } else if (activeTab === 'infrastructure') { 157→ response = await fetch(`${API_BASE}/infrastructure`, { 158→ method: 'POST', 159→ headers: { 'Content-Type': 'application/json' }, 160→ body: JSON.stringify({ 161→ name: newItemName.trim(), 162→ type: newItemType, 163→ config: { content: newItemContent.trim() } 164→ }), 165→ }) 166→ if (response.ok) { 167→ const newItem = await response.json() 168→ setInfrastructure([...infrastructure, newItem]) 169→ } 170→ } 171→ 172→ setShowAddDialog(false) 173→ } catch (error) { 174→ console.error('Failed to add memory item:', error) 175→ } finally { 176→ setIsSaving(false) 177→ } 178→ } 179→ 180→ const handleDeleteClick = (id: number, type: MemoryTab) => { 181→ setItemToDelete({ id, type }) 182→ setShowDeleteDialog(true) 183→ } 184→ 185→ const handleDeleteConfirm = async () => { 186→ if (!itemToDelete) return 187→ 188→ setIsDeleting(true) 189→ try { 190→ let endpoint: string 191→ 192→ if (itemToDelete.type === 'project' && currentProject) { 193→ endpoint = `${API_BASE}/project/${currentProject.id}/${itemToDelete.id}` 194→ } else if (itemToDelete.type === 'global') { 195→ endpoint = `${API_BASE}/global/${itemToDelete.id}` 196→ } else { 197→ endpoint = `${API_BASE}/infrastructure/${itemToDelete.id}` 198→ } 199→ 200→ const response = await fetch(endpoint, { method: 'DELETE' }) 201→ 202→ if (response.ok || response.status === 204) { 203→ if (itemToDelete.type === 'project') { 204→ setProjectMemory(projectMemory.filter(item => item.id !== itemToDelete.id)) 205→ } else if (itemToDelete.type === 'global') { 206→ setGlobalMemory(globalMemory.filter(item => item.id !== itemToDelete.id)) 207→ } else { 208→ setInfrastructure(infrastructure.filter(item => item.id !== itemToDelete.id)) 209→ } 210→ } 211→ 212→ setShowDeleteDialog(false) 213→ setItemToDelete(null) 214→ } catch (error) { 215→ console.error('Failed to delete memory item:', error) 216→ } finally { 217→ setIsDeleting(false) 218→ } 219→ } 220→ 221→ const handleEditClick = (id: number, type: string, content: string, memoryType: MemoryTab) => { 222→ setEditItem({ id, type, content, memoryType }) 223→ setEditItemType(type) 224→ setEditItemContent(content) 225→ setShowEditDialog(true) 226→ } 227→ 228→ const handleEditSave = async () => { 229→ if (!editItem || !editItemContent.trim()) return 230→ 231→ setIsEditing(true) 232→ try { 233→ let endpoint: string 234→ let body: Record 235→ 236→ if (editItem.memoryType === 'project' && currentProject) { 237→ endpoint = `${API_BASE}/project/${currentProject.id}/${editItem.id}` 238→ body = { type: editItemType, content: editItemContent.trim() } 239→ } else if (editItem.memoryType === 'global') { 240→ endpoint = `${API_BASE}/global/${editItem.id}` 241→ body = { type: editItemType, content: editItemContent.trim() } 242→ } else { 243→ // Infrastructure uses different update endpoint 244→ setShowEditDialog(false) 245→ setEditItem(null) 246→ return 247→ } 248→ 249→ const response = await fetch(endpoint, { 250→ method: 'PUT', 251→ headers: { 'Content-Type': 'application/json' }, 252→ body: JSON.stringify(body), 253→ }) 254→ 255→ if (response.ok) { 256→ const updatedItem = await response.json() 257→ if (editItem.memoryType === 'project') { 258→ setProjectMemory(projectMemory.map(item => 259→ item.id === editItem.id ? updatedItem : item 260→ )) 261→ } else if (editItem.memoryType === 'global') { 262→ setGlobalMemory(globalMemory.map(item => 263→ item.id === editItem.id ? updatedItem : item 264→ )) 265→ } 266→ } 267→ 268→ setShowEditDialog(false) 269→ setEditItem(null) 270→ } catch (error) { 271→ console.error('Failed to update memory item:', error) 272→ } finally { 273→ setIsEditing(false) 274→ } 275→ } 276→ 277→ const filterItems = (items: T[]) => { 278→ if (!searchQuery) return items 279→ const query = searchQuery.toLowerCase() 280→ return items.filter(item => 281→ item.content?.toLowerCase().includes(query) || 282→ item.name?.toLowerCase().includes(query) || 283→ item.type.toLowerCase().includes(query) 284→ ) 285→ } 286→ 287→ const filteredProjectMemory = filterItems(projectMemory) 288→ const filteredGlobalMemory = filterItems(globalMemory) 289→ const filteredInfrastructure = filterItems(infrastructure) 290→ 291→ return ( 292→
293→ {/* Header */} 294→
295→

Memory Manager

296→ 303→
304→ 305→ {/* Tabs */} 306→
307→ {tabs.map((tab) => { 308→ const Icon = tab.icon 309→ return ( 310→ 322→ ) 323→ })} 324→
325→ 326→ {/* Search */} 327→
328→ 332→ setSearchQuery(e.target.value)} 336→ placeholder="Search memory items..." 337→ className="w-full pl-10 pr-4 py-2 bg-slate-800 border border-slate-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent" 338→ /> 339→
340→ 341→ {/* Content */} 342→
343→ {isLoading ? ( 344→
Loading...
345→ ) : ( 346→ <> 347→ {activeTab === 'project' && ( 348→
349→ {filteredProjectMemory.length === 0 ? ( 350→ 354→ ) : ( 355→ filteredProjectMemory.map(item => ( 356→ handleDeleteClick(item.id, 'project')} 363→ onEdit={() => handleEditClick(item.id, item.type, item.content, 'project')} 364→ /> 365→ )) 366→ )} 367→
368→ )} 369→ 370→ {activeTab === 'global' && ( 371→
372→ {filteredGlobalMemory.length === 0 ? ( 373→ 377→ ) : ( 378→ filteredGlobalMemory.map(item => ( 379→ handleDeleteClick(item.id, 'global')} 387→ onEdit={() => handleEditClick(item.id, item.type, item.content, 'global')} 388→ /> 389→ )) 390→ )} 391→
392→ )} 393→ 394→ {activeTab === 'infrastructure' && ( 395→
396→ {filteredInfrastructure.length === 0 ? ( 397→ 401→ ) : ( 402→ filteredInfrastructure.map(item => ( 403→ handleDeleteClick(item.id, 'infrastructure')} 411→ /> 412→ )) 413→ )} 414→
415→ )} 416→ 417→ )} 418→
419→ 420→ {/* Add Memory Dialog */} 421→ 422→ 423→ 424→ 425→
426→ 427→ Add {activeTab === 'project' ? 'Project' : activeTab === 'global' ? 'Global' : 'Infrastructure'} Memory 428→ 429→ 430→ 433→ 434→
435→ 436→
437→ {activeTab === 'infrastructure' && ( 438→
439→ 440→ setNewItemName(e.target.value)} 444→ className="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-accent" 445→ placeholder="Enter config name" 446→ /> 447→
448→ )} 449→ 450→
451→ 452→ 461→
462→ 463→
464→ 465→