import { useCallback, useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Brain, Search, Trash2, Download, Star, Tag, Clock, ChevronDown, ChevronUp, } from 'lucide-react'; import { cardHover, defaultTransition } from '../lib/animations'; import { Button, Badge, EmptyState } from './ui'; import { intelligenceClient, type MemoryEntry, type MemoryType, type MemoryStats, } from '../lib/intelligence-client'; import { useConversationStore } from '../store/chat/conversationStore'; const TYPE_LABELS: Record = { fact: { label: '事实', emoji: '📋', color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300' }, preference: { label: '偏好', emoji: '⭐', color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300' }, lesson: { label: '经验', emoji: '💡', color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300' }, context: { label: '上下文', emoji: '📌', color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300' }, task: { label: '任务', emoji: '📝', color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300' }, }; export function MemoryPanel() { const currentAgent = useConversationStore((s) => s.currentAgent); const agentId = currentAgent?.id || 'zclaw-main'; const [memories, setMemories] = useState([]); const [stats, setStats] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [filterType, setFilterType] = useState('all'); const [expandedId, setExpandedId] = useState(null); const [isExporting, setIsExporting] = useState(false); const loadMemories = useCallback(async () => { const typeFilter = filterType !== 'all' ? { type: filterType as MemoryType } : {}; if (searchQuery.trim()) { const results = await intelligenceClient.memory.search({ agentId, query: searchQuery, limit: 50, ...typeFilter, }); setMemories(results); } else { const results = await intelligenceClient.memory.search({ agentId, limit: 50, ...typeFilter, }); setMemories(results); } const s = await intelligenceClient.memory.stats(); setStats(s); }, [agentId, searchQuery, filterType]); useEffect(() => { loadMemories(); }, [loadMemories]); const handleDelete = async (id: string) => { await intelligenceClient.memory.delete(id); loadMemories(); }; const handleExport = async () => { setIsExporting(true); try { const memories = await intelligenceClient.memory.export(); const filtered = memories.filter(m => m.agentId === agentId); const md = filtered.map(m => `## [${m.type}] ${m.content}\n` + `- 重要度: ${m.importance}\n` + `- 标签: ${m.tags.join(', ') || '无'}\n` + `- 创建时间: ${m.createdAt}\n` ).join('\n---\n\n'); const blob = new Blob([md || '# 无记忆数据'], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `zclaw-memory-${agentId}-${new Date().toISOString().slice(0, 10)}.md`; a.click(); URL.revokeObjectURL(url); } finally { setIsExporting(false); } }; const handlePrune = async () => { // Find old, low-importance memories and delete them const memories = await intelligenceClient.memory.search({ agentId, minImportance: 0, limit: 1000, }); const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; const toDelete = memories.filter(m => new Date(m.createdAt).getTime() < thirtyDaysAgo && m.importance < 3 ); for (const m of toDelete) { await intelligenceClient.memory.delete(m.id); } if (toDelete.length > 0) { loadMemories(); } }; return (
{/* Stats */}

Agent 记忆

{stats && (
{stats.totalEntries}
总记忆
{stats.byType['fact'] || 0}
事实
{stats.byType['preference'] || 0}
偏好
)}
{/* Search & Filter */}
setSearchQuery(e.target.value)} placeholder="搜索记忆..." className="w-full text-sm border border-gray-200 dark:border-gray-600 rounded-lg pl-8 pr-3 py-1.5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400" />
setFilterType('all')} /> {(Object.keys(TYPE_LABELS) as MemoryType[]).map((type) => ( setFilterType(type)} /> ))}
{/* Memory List */}
{memories.length > 0 ? ( {memories.map((entry) => ( setExpandedId(expandedId === entry.id ? null : entry.id)} onDelete={() => handleDelete(entry.id)} /> ))} ) : ( } title={searchQuery ? '未找到匹配的记忆' : '暂无记忆'} description={searchQuery ? '尝试不同的搜索词' : '与 Agent 交流后,记忆会自动积累'} className="py-6" /> )}
); } function MemoryCard({ entry, expanded, onToggle, onDelete, }: { entry: MemoryEntry; expanded: boolean; onToggle: () => void; onDelete: () => void; }) { const typeInfo = TYPE_LABELS[entry.type]; const importanceStars = Math.round(entry.importance / 2); const timeAgo = getTimeAgo(entry.createdAt); return (
{typeInfo.emoji} {typeInfo.label}

{entry.content}

{expanded ? ( ) : ( )}
{'★'.repeat(importanceStars)}{'☆'.repeat(5 - importanceStars)} {timeAgo} {entry.tags.length > 0 && ( {entry.tags.slice(0, 2).join(', ')} )}
{expanded && (
重要性 {entry.importance}/10
来源 {entry.source === 'auto' ? '自动' : entry.source === 'user' ? '用户' : '反思'}
访问 {entry.accessCount}次
创建 {new Date(entry.createdAt).toLocaleDateString('zh-CN')}
{entry.tags.length > 0 && (
{entry.tags.map((tag) => ( {tag} ))}
)}
)}
); } function FilterChip({ label, active, onClick, }: { label: string; active: boolean; onClick: () => void; }) { return ( ); } function getTimeAgo(isoDate: string): string { const now = Date.now(); const then = new Date(isoDate).getTime(); const diffMs = now - then; const minutes = Math.floor(diffMs / 60_000); if (minutes < 1) return '刚刚'; if (minutes < 60) return `${minutes}分钟前`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}小时前`; const days = Math.floor(hours / 24); if (days < 30) return `${days}天前`; const months = Math.floor(days / 30); return `${months}个月前`; }