feat: 前端跑通 React Flow 画布→DSL→提交→SSE + 偏好记忆面板 (④)
桌面端前端从骨架变为可用:画布编排 Agent → 导出 DSL → 提交 Gateway → 逐 token 流式展示;偏好记忆面板让用户登记画像(→ memory_upsert)。 - lib/api.ts: submitTask(POST) / streamTokens(EventSource SSE token/done) / setMemory(PUT) - canvas/AgentCanvas: 加节点(提示词)/连线/运行(导出DSL交上层), React Flow 工具栏 - panels/MemoryPanel: 登记偏好(key/value)→PUT /api/v1/memory - App: 身份(user/session)+记忆面板+SSE 输出面板,串起提交→流式 - postcss.config + vite-env.d.ts(import.meta.env) 补齐构建;删 WikiPanel(stale Qdrant) - Gateway: 加 CORS 中间件(放开跨源 + X-User-ID/X-Session-ID 头 + OPTIONS) - Makefile: web 目标(Vite dev); .claude/launch.json(preview 配置), 忽略 settings.local 验证: npm run build(tsc+vite)✓; 真实浏览器跑通——加节点→运行→SSE 流出含 注入画像(称呼:老王)的回答, 第2轮 UI 显示'已有1轮历史'(短期历史经 Eino 图回灌) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { useState } from "react";
|
||||
import { setMemory, type Identity } from "../lib/api";
|
||||
|
||||
// 偏好记忆面板 —— 让用户显式登记/纠正模型对自己的记忆(→ PUT /api/v1/memory)。
|
||||
export function MemoryPanel({ identity }: { identity: Identity }) {
|
||||
const [key, setKey] = useState("回答偏好");
|
||||
const [value, setValue] = useState("简洁、中文、多给要点");
|
||||
const [saved, setSaved] = useState<Array<{ key: string; value: string }>>([]);
|
||||
const [msg, setMsg] = useState("");
|
||||
|
||||
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())]);
|
||||
setMsg(m);
|
||||
} catch (e) {
|
||||
setMsg(`✗ ${(e as Error).message}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="border-b p-4">
|
||||
<h2 className="mb-2 text-sm font-semibold text-gray-700">偏好记忆(让模型知道是我)</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
<input
|
||||
className="rounded border px-2 py-1 text-sm"
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
placeholder="键,如 称呼 / 回答偏好"
|
||||
/>
|
||||
<textarea
|
||||
className="h-16 resize-none rounded border px-2 py-1 text-sm"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="值"
|
||||
/>
|
||||
<button onClick={save} className="self-end rounded bg-emerald-600 px-3 py-1 text-sm text-white">
|
||||
记住
|
||||
</button>
|
||||
</div>
|
||||
{msg && <p className="mt-2 text-xs text-emerald-700">{msg}</p>}
|
||||
{saved.length > 0 && (
|
||||
<ul className="mt-3 space-y-1">
|
||||
{saved.map((s) => (
|
||||
<li key={s.key} className="rounded bg-gray-50 px-2 py-1 text-xs text-gray-600">
|
||||
<span className="font-medium">{s.key}</span>:{s.value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user