Files
sundynix-agentix/sundynix-desktop/frontend/src/panels/MemoryPanel.tsx
T
Blizzard 0edfc948ba feat(memory): P2 读路径打分 + 桌面端记忆面板(看/改/删)
读路径(Generative Agents 公式的 Recency+Importance 两项,Relevance 待 P3):
- memory.Get 改为 rankProfiles:Score=0.4·Recency+0.6·Importance,按分降序、截断 top-30。
  Recency=0.98^天 指数衰减;未评分行用兜底 importance=5(不被不公平遗忘)。纯函数 + 单测。
- 新增 Store.List(结构化、不截断)+ memory_list 工具。

桌面端记忆面板:
- gateway GET /memory(列表) + DELETE /memory?key=(软删),受保护组。
- api listMemory/deleteMemory;MemoryView 右侧从占位 → 真列表:按分排序展示
  key/value/重要度/最近时间,可内联编辑(PUT)与删除(软删);左侧登记后自动刷新。

实测:PUT 两条 → GET 返回带 importance/last_seen 的有序列表(较新者靠前)→ DELETE 一条
(软删)→ 再 GET 已消失。rank/recency/兜底 importance 单测过;前端 tsc+构建通过。

至此记忆:召回(打分) + 历史 + 自动对账(P1) + 用户可管控(面板) 闭环。Relevance(Milvus) 留 P3。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:48:11 +08:00

51 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { Check } from "lucide-react";
import { setMemory, type Identity } from "../lib/api";
import { Button, Input, Textarea, Field, useToast } from "../ui";
// 偏好记忆面板 —— 让用户显式登记/纠正模型对自己的记忆(→ PUT /api/v1/memory)。
export function MemoryPanel({ identity, onSaved }: { identity: Identity; onSaved?: () => void }) {
const toast = useToast();
const [key, setKey] = useState("回答偏好");
const [value, setValue] = useState("简洁、中文、多给要点");
const [saved, setSaved] = useState<Array<{ key: string; value: string }>>([]);
const save = async () => {
if (!key.trim()) return;
try {
const m = await setMemory(identity, key.trim(), value.trim());
setSaved((s) => [{ key: key.trim(), value: value.trim() }, ...s.filter((x) => x.key !== key.trim())]);
toast.push("success", m);
onSaved?.();
} catch (e) {
toast.push("error", (e as Error).message);
}
};
return (
<section className="border-b border-line p-4">
<h2 className="mb-3 text-sm font-semibold text-slate-300"></h2>
<div className="flex flex-col gap-3">
<Field label="键">
<Input value={key} onChange={(e) => setKey(e.target.value)} placeholder="如 称呼 / 回答偏好" />
</Field>
<Field label="值">
<Textarea className="h-16 resize-none" value={value} onChange={(e) => setValue(e.target.value)} placeholder="值" />
</Field>
<Button variant="primary" size="sm" icon={Check} className="self-end" onClick={save}>
</Button>
</div>
{saved.length > 0 && (
<ul className="mt-3 space-y-1">
{saved.map((s) => (
<li key={s.key} className="rounded-md bg-ink-800 px-2 py-1.5 text-xs text-slate-400">
<span className="font-medium text-slate-200">{s.key}</span>{s.value}
</li>
))}
</ul>
)}
</section>
);
}