"use client"; import { FormEvent, useEffect, useMemo, useState } from "react"; import { Activity, Archive, BrainCircuit, ChevronRight, Database, History, Lock, RefreshCw, Send, Settings2, ShieldCheck, Sparkles, Trash2, Wrench } from "lucide-react"; function apiUrl(path: string): string { if (typeof window === "undefined") return path; const configured = process.env.NEXT_PUBLIC_API_URL?.trim().replace(/\/$/, ""); const runtimeBase = `${window.location.protocol}//${window.location.hostname}:8000`; return `${configured || runtimeBase}${path}`; } async function apiFetch(path: string, init?: RequestInit, timeoutMs = 10000): Promise { const controller = new AbortController(); const timeout = window.setTimeout(() => controller.abort(), timeoutMs); try { return await fetch(apiUrl(path), { ...init, signal: controller.signal }); } finally { window.clearTimeout(timeout); } } type SystemId = "text2mem" | "mem0" | "letta" | "reme" | "memu"; type SystemInfo = { id: SystemId; name: string; paradigm: string; description: string; available: boolean; mode: string; status: string; package?: string; setup_hint?: string }; type Memory = { id: string; system: string; content: string; memory_type: string; source: string; confidence: number; created_at: string; updated_at: string; metadata: Record }; type Audit = { id: string; system: string; operation: string; status: string; target?: string; details: Record; created_at: string }; type Message = { role: "user" | "assistant" | "system"; content: string; mode?: string; events?: string[] }; const fallbackSystems: SystemInfo[] = [ { id: "text2mem", name: "Text2Mem", paradigm: "IR 操作契约", description: "显式定义记忆操作、授权与生命周期。", available: true, mode: "native", status: "ready" }, { id: "mem0", name: "Mem0", paradigm: "自动提取", description: "自动提取事实,处理更新与冲突。", available: false, mode: "unavailable", status: "not-configured" }, { id: "letta", name: "Letta", paradigm: "有状态 Agent", description: "Core / Archival / Recall 分层工作记忆。", available: false, mode: "unavailable", status: "not-installed" }, { id: "reme", name: "ReMe", paradigm: "文件即记忆", description: "Markdown 文件、日志和混合检索。", available: false, mode: "unavailable", status: "not-configured" }, { id: "memu", name: "memU", paradigm: "主动式管线", description: "后台整理、候选记忆和主动检索。", available: false, mode: "unavailable", status: "not-configured" }, ]; const icons: Record = { text2mem: ShieldCheck, mem0: BrainCircuit, letta: Database, reme: Archive, memu: Sparkles }; function initialMessages(): Record { return { text2mem: [{ role: "assistant", content: "你好,我是 Text2Mem Agent。告诉我你的技术偏好、项目进展或希望我记住的工程规则。" }], mem0: [{ role: "assistant", content: "Mem0 适配器当前未启用;完成记忆投影、删除、重置和隔离闭环后再开放对话。" }], letta: [{ role: "assistant", content: "Letta 适配器当前未启用;完成 Agent ID 恢复和分层记忆投影后再开放对话。" }], reme: [{ role: "assistant", content: "你好,我是 ReMe Agent。当前对话只会使用 ReMe 的文件记忆。" }], memu: [{ role: "assistant", content: "memU 适配器当前未启用;完成队列、预算、审批和停止开关后再开放对话。" }], }; } function setupSummary(system: SystemInfo): string { if (system.setup_hint) return system.setup_hint; if (system.id === "text2mem") return "本项目内置教学实现,无需额外框架服务。"; if (system.id === "reme") return "已连接 ReMe 真实 SDK;右侧每一项代表一个 Markdown 记忆文件。"; return "当前为未启用的适配器骨架。"; } export default function Home() { const [systems, setSystems] = useState(fallbackSystems); const [selected, setSelected] = useState("text2mem"); const [messagesBySystem, setMessagesBySystem] = useState>(initialMessages); const [input, setInput] = useState(""); const [memories, setMemories] = useState([]); const [audit, setAudit] = useState([]); const [loading, setLoading] = useState(false); const [resetting, setResetting] = useState(false); const [deletingId, setDeletingId] = useState(null); const [notice, setNotice] = useState(""); const current = useMemo(() => systems.find((item) => item.id === selected) ?? fallbackSystems[0], [systems, selected]); const messages = messagesBySystem[selected]; const memoryNoun = selected === "reme" ? "记忆文件" : "记忆"; async function loadSystems() { try { const response = await apiFetch("/api/systems"); if (!response.ok) throw new Error("API unavailable"); const data = await response.json(); setSystems(data.systems); setNotice(`已连接:${apiUrl("")} · 存储:${data.storage}`); } catch (error) { const reason = error instanceof Error ? error.message : "未知错误"; setNotice(`连接失败:${apiUrl("")} · ${reason}`); } } async function loadDetails(system: SystemId = selected) { try { const [memoryResponse, auditResponse] = await Promise.all([ apiFetch(`/api/memories?system=${system}`), apiFetch(`/api/audit?system=${system}`), ]); if (memoryResponse.ok) setMemories((await memoryResponse.json()).memories); if (auditResponse.ok) setAudit((await auditResponse.json()).events); } catch { setMemories([]); setAudit([]); } } useEffect(() => { void loadSystems(); void loadDetails(); }, []); useEffect(() => { void loadDetails(selected); }, [selected]); async function submit(event: FormEvent) { event.preventDefault(); const content = input.trim(); if (!content || loading) return; const system = selected; setInput(""); setMessagesBySystem((all) => ({ ...all, [system]: [...all[system], { role: "user", content }] })); setLoading(true); try { const response = await apiFetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ system, message: content }) }, 120000); const data = await response.json(); if (!response.ok) throw new Error(data.detail ?? "执行失败"); setMessagesBySystem((all) => ({ ...all, [system]: [...all[system], { role: "assistant", content: data.answer, mode: data.mode, events: data.memory_events?.map((item: { event?: string }) => item.event).filter(Boolean) }] })); await loadDetails(system); } catch (error) { setMessagesBySystem((all) => ({ ...all, [system]: [...all[system], { role: "system", content: error instanceof Error ? error.message : "执行失败" }] })); } finally { setLoading(false); } } async function reset() { if (!window.confirm(`确定清空 ${current.name} 的本地记忆和审计记录吗?`)) return; const system = selected; setResetting(true); try { const response = await apiFetch("/api/reset", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ system }) }, 60000); const data = await response.json(); if (!response.ok) throw new Error(data.detail ?? "清空失败"); setMessagesBySystem((all) => ({ ...all, [system]: [...initialMessages()[system], { role: "system", content: `${current.name} 的记忆已清空。` }] })); await loadDetails(system); } catch (error) { setNotice(error instanceof Error ? error.message : "清空失败"); } finally { setResetting(false); } } async function deleteMemory(memory: Memory) { const warning = selected === "reme" ? "该文件可能包含多条事实,删除后会重建 ReMe 索引。\n\n" : ""; if (!window.confirm(`确定删除这个${current.name}${memoryNoun}吗?\n\n${warning}${memory.content.slice(0, 120)}`)) return; const system = selected; setDeletingId(memory.id); try { const response = await apiFetch(`/api/memories/${encodeURIComponent(memory.id)}?system=${system}`, { method: "DELETE" }, 60000); const data = await response.json(); if (!response.ok) throw new Error(data.detail ?? "删除失败"); await loadDetails(system); } catch (error) { setNotice(error instanceof Error ? error.message : "删除失败"); } finally { setDeletingId(null); } } const Icon = icons[selected]; return (
Memory Agents Lab
五种记忆范式 · 同一场景 · 独立证据
local-workspace

AI PROGRAMMING ASSISTANT

把记忆从黑箱,变成可观察的系统。

五种记忆范式使用同一套工程场景;当前可实测 Text2Mem 与 ReMe,其余适配器保留为后续扩展。

系统状态
{notice || "正在连接后端…"}模型 API、数据库和框架均可按需配置
ACTIVE AGENT

{current.name}{current.mode}

{current.description} · {setupSummary(current)}
{messages.map((message, index) =>
{message.role === "user" ? "你" : message.role === "system" ? "!" : "M"}
{message.role === "user" ? "USER" : message.role === "system" ? "SYSTEM" : current.name.toUpperCase()}{message.mode && {message.mode}}
{message.content}
{message.events && message.events.length > 0 &&
{message.events.map((event) => {event})}
}
)}