import { useState, useEffect, useCallback } from 'react'; import { Brain, Loader2, ChevronDown, ChevronRight, User } from 'lucide-react'; import { listVikingResources, readVikingResource } from '../../lib/viking-client'; import { invoke } from '@tauri-apps/api/core'; interface MemorySectionProps { agentId: string; refreshKey?: number; } interface MemoryEntry { uri: string; name: string; resourceType: string; size?: number; modifiedAt?: string; summary?: string; loading?: boolean; } type MemoryGroup = 'preferences' | 'knowledge' | 'experience' | 'sessions' | 'other'; interface UserProfile { industry?: string; role?: string; expertise_level?: string; communication_style?: string; preferred_language?: string; recent_topics?: string[]; active_pain_points?: string[]; preferred_tools?: string[]; confidence?: number; } const GROUP_LABELS: Record = { preferences: '偏好', knowledge: '知识', experience: '经验', sessions: '会话', other: '其他', }; const GROUP_ORDER: MemoryGroup[] = ['preferences', 'knowledge', 'experience', 'sessions', 'other']; function classifyGroup(resourceType: string): MemoryGroup { if (resourceType in GROUP_LABELS) return resourceType as MemoryGroup; return 'other'; } function formatDate(iso?: string): string { if (!iso) return ''; try { return new Date(iso).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }); } catch { return ''; } } // Fetch user profile from agent_get Tauri command async function fetchUserProfile(agentId: string): Promise { try { const result = await invoke<{ userProfile?: UserProfile } | null>('agent_get', { agentId }); return result?.userProfile ?? null; } catch { return null; } } export function MemorySection({ agentId, refreshKey }: MemorySectionProps) { const [memories, setMemories] = useState([]); const [loading, setLoading] = useState(false); const [expandedGroups, setExpandedGroups] = useState>(new Set(['preferences', 'knowledge'])); const [profile, setProfile] = useState(null); const [_profileLoading, setProfileLoading] = useState(false); const loadMemories = useCallback(async () => { if (!agentId) return; setLoading(true); try { const entries = await listVikingResources(`agent://${agentId}/`); const typed = entries as MemoryEntry[]; // Load L1 summaries in parallel (batched to avoid overwhelming) const enriched = await Promise.all( typed.map(async (entry) => { try { const summary = await readVikingResource(entry.uri, 'L1'); return { ...entry, summary: summary || entry.name }; } catch { return { ...entry, summary: entry.name }; } }) ); setMemories(enriched); } catch { setMemories([]); } finally { setLoading(false); } }, [agentId]); const loadProfile = useCallback(async () => { if (!agentId) return; setProfileLoading(true); try { const p = await fetchUserProfile(agentId); setProfile(p); } catch { setProfile(null); } finally { setProfileLoading(false); } }, [agentId]); useEffect(() => { loadMemories(); loadProfile(); }, [loadMemories, loadProfile, refreshKey]); // Group memories by type const grouped = memories.reduce>((acc, m) => { const group = classifyGroup(m.resourceType); if (!acc[group]) acc[group] = []; acc[group].push(m); return acc; }, {} as Record); const nonEmptyGroups = GROUP_ORDER.filter((g) => (grouped[g]?.length ?? 0) > 0); const totalMemories = memories.length; const toggleGroup = (group: MemoryGroup) => { setExpandedGroups((prev) => { const next = new Set(prev); if (next.has(group)) next.delete(group); else next.add(group); return next; }); }; const hasProfile = profile && ( profile.industry || profile.role || profile.communication_style || (profile.recent_topics && profile.recent_topics.length > 0) || (profile.preferred_tools && profile.preferred_tools.length > 0) ); if (loading && memories.length === 0) { return (
); } if (totalMemories === 0 && !hasProfile) { return (

暂无记忆

管家会在对话中自动积累对您的了解

); } return (
{/* User Profile Card */} {hasProfile && (
用户画像 {profile.confidence !== undefined && profile.confidence > 0 && ( 置信度 {Math.round(profile.confidence * 100)}% )}
{profile.industry && ( )} {profile.role && ( )} {profile.expertise_level && ( )} {profile.communication_style && ( )} {profile.recent_topics && profile.recent_topics.length > 0 && (
近期话题 {profile.recent_topics.slice(0, 8).map((topic) => ( {topic} ))}
)} {profile.preferred_tools && profile.preferred_tools.length > 0 && (
常用工具 {profile.preferred_tools.map((tool) => ( {tool} ))}
)}
)} {/* Memory Groups */} {nonEmptyGroups.map((group) => { const isExpanded = expandedGroups.has(group); const items = grouped[group] ?? []; return (
{isExpanded && (
{items.map((memory) => (
{memory.summary || memory.name}
{memory.name} {memory.modifiedAt && ( {formatDate(memory.modifiedAt)} )}
))}
)}
); })}
); } function ProfileField({ label, value }: { label: string; value: string }) { return (
{label} {value}
); }