deerflow2/frontend/src/components/workspace/thread-memory-test-panel.tsx

143 lines
4.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { getBackendBaseURL } from "@/core/config";
type ThreadMemoryTestPanelProps = {
threadId?: string;
};
export function ThreadMemoryTestPanel({ threadId }: ThreadMemoryTestPanelProps) {
const [memorySummary, setMemorySummary] = useState("");
const [memoryVersion, setMemoryVersion] = useState<number | null>(null);
const [loadingSummary, setLoadingSummary] = useState(false);
const [savingSummary, setSavingSummary] = useState(false);
const [deletingMemory, setDeletingMemory] = useState(false);
const [open, setOpen] = useState(true);
if (!threadId || threadId === "new") return null;
const handleLoadMemorySummary = async () => {
setLoadingSummary(true);
try {
const res = await fetch(
`${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory-summary`,
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = (await res.json()) as { summary: string; memoryVersion: number };
setMemorySummary(data.summary ?? "");
setMemoryVersion(data.memoryVersion ?? 0);
toast.success("已加载会话记忆");
} catch {
toast.error("加载会话记忆失败");
} finally {
setLoadingSummary(false);
}
};
const handleSaveMemorySummary = async () => {
if (memoryVersion == null) return;
setSavingSummary(true);
try {
const res = await fetch(
`${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory-summary`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ summary: memorySummary, memoryVersion }),
},
);
if (res.status === 409) {
toast.error("记忆已更新,请先重新加载再保存");
return;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = (await res.json()) as { memoryVersion?: number };
if (typeof data.memoryVersion === "number") setMemoryVersion(data.memoryVersion);
toast.success("会话记忆已保存");
} catch {
toast.error("保存会话记忆失败");
} finally {
setSavingSummary(false);
}
};
const handleDeleteMemory = async () => {
setDeletingMemory(true);
try {
const res = await fetch(
`${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory`,
{ method: "DELETE" },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setMemorySummary("");
setMemoryVersion(0);
toast.success("当前会话记忆已删除");
} catch {
toast.error("删除会话记忆失败");
} finally {
setDeletingMemory(false);
}
};
return (
<div className="fixed right-4 bottom-4 z-50 w-[380px] rounded-lg border border-ws-divider bg-ws-surface-elevated p-3 shadow-lg">
<div className="mb-2 flex items-center justify-between">
<div className="text-sm font-semibold">Thread Memory TestPanel</div>
<Button size="sm" variant="ghost" onClick={() => setOpen((v) => !v)}>
{open ? "收起" : "展开"}
</Button>
</div>
{open && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => {
void handleLoadMemorySummary();
}}
disabled={loadingSummary}
>
{loadingSummary ? "加载中..." : "查看记忆"}
</Button>
<Button
size="sm"
onClick={() => {
void handleSaveMemorySummary();
}}
disabled={savingSummary || memoryVersion == null}
>
{savingSummary ? "保存中..." : "保存记忆"}
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => {
void handleDeleteMemory();
}}
disabled={deletingMemory}
>
{deletingMemory ? "删除中..." : "删除记忆"}
</Button>
</div>
<div className="text-xs text-ws-text-subtle-strong">
threadId: {threadId.slice(0, 8)}... | version:{" "}
{memoryVersion == null ? "-" : memoryVersion}
</div>
<Textarea
value={memorySummary}
onChange={(e) => setMemorySummary(e.target.value)}
placeholder="这里显示会话记忆总结,可编辑后保存"
className="min-h-32 bg-white/80"
/>
</div>
)}
</div>
);
}