0edfc948ba
读路径(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>
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
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>
|
||
);
|
||
}
|