import React, { useState, useEffect, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Brain, Trash2, RefreshCw, ChevronDown, ChevronUp, AlertCircle, Sparkles, Database, Plus, X, Search, Info, } from 'lucide-react'; import { API_BASE_URL } from '../config/api'; import './MemoryDashboard.css'; // Types matching backend schemas interface MemoryFact { id: string; memory: string; created_at: string | null; metadata: Record; } interface MemoryListResponse { facts: MemoryFact[]; total_count: number; available: boolean; message: string | null; } interface MemoryDashboardProps { authToken?: string; apiBaseUrl?: string; defaultCollapsed?: boolean; } const MemoryDashboard: React.FC = ({ authToken, apiBaseUrl = API_BASE_URL, defaultCollapsed = true, }) => { const [memories, setMemories] = useState([]); const [loading, setLoading] = useState(false); const [deleting, setDeleting] = useState(null); const [error, setError] = useState(null); const [collapsed, setCollapsed] = useState(defaultCollapsed); const [available, setAvailable] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [newMemory, setNewMemory] = useState(''); const [adding, setAdding] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const fetchMemories = useCallback(async () => { if (!authToken) { setError('Authentication required'); return; } setLoading(true); setError(null); try { const response = await fetch(`${apiBaseUrl}/api/memory/facts`, { headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to fetch memories: ${response.status}`); } const data: MemoryListResponse = await response.json(); setMemories(data.facts); setAvailable(data.available); if (data.message) { setError(data.message); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load memories'); } finally { setLoading(false); } }, [authToken, apiBaseUrl]); useEffect(() => { if (!collapsed && authToken) { fetchMemories(); } }, [collapsed, authToken, fetchMemories]); const handleDelete = async (memoryId: string) => { if (!authToken) return; setDeleting(memoryId); try { const response = await fetch(`${apiBaseUrl}/api/memory/facts/${memoryId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json', }, }); if (response.ok) { setMemories(prev => prev.filter(m => m.id !== memoryId)); } else { throw new Error('Failed to delete memory'); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete memory'); } finally { setDeleting(null); } }; const handleAdd = async () => { if (!authToken || !newMemory.trim()) return; setAdding(true); try { const response = await fetch(`${apiBaseUrl}/api/memory/facts`, { method: 'POST', headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ content: newMemory.trim() }), }); if (response.ok) { setNewMemory(''); setShowAddModal(false); await fetchMemories(); } else { const data = await response.json(); throw new Error(data.message || 'Failed to add memory'); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to add memory'); } finally { setAdding(false); } }; const handleSearch = async () => { if (!authToken || !searchQuery.trim()) { await fetchMemories(); return; } setLoading(true); try { const response = await fetch(`${apiBaseUrl}/api/memory/search`, { method: 'POST', headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: searchQuery, limit: 20 }), }); if (response.ok) { const data: MemoryListResponse = await response.json(); setMemories(data.facts); } } catch (err) { setError(err instanceof Error ? err.message : 'Search failed'); } finally { setLoading(false); } }; const formatDate = (dateStr: string | null) => { if (!dateStr) return ''; const date = new Date(dateStr); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }; return (
setCollapsed(!collapsed)} role="button" tabIndex={0} onKeyDown={(e) => e.key === 'Enter' && setCollapsed(!collapsed)} >

Health Memory

{available ? ( <> {memories.length} ) : ( <> Offline )}
{collapsed ? : }
{!collapsed && ( {!available ? (

Memory service is not available

Recommendations will still work, but without personalization from your preferences.
) : ( <>
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} /> {searchQuery && ( )}
{error && (
{error}
)} {loading ? (

Loading memories...

) : memories.length === 0 ? (

No memories yet

Your health preferences and facts will appear here as you interact with the assistant.
) : (
{memories.map((memory, index) => (

{memory.memory}

{memory.created_at && ( {formatDate(memory.created_at)} )}
))}
)}
Memories help personalize your health recommendations.
)}
)}
{/* Add Memory Modal */} {showAddModal && ( setShowAddModal(false)} > e.stopPropagation()} >

Add a Preference