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:
@@ -1,15 +1,86 @@
|
||||
import { AgentCanvas } from "./canvas/AgentCanvas";
|
||||
import { WikiPanel } from "./wiki/WikiPanel";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
// UI Representation Layer —— 顶层布局:左侧编排画布 + 右侧 Wiki 面板。
|
||||
import { AgentCanvas } from "./canvas/AgentCanvas";
|
||||
import { MemoryPanel } from "./panels/MemoryPanel";
|
||||
import { submitTask, streamTokens, type Identity } from "./lib/api";
|
||||
import type { TaskDsl } from "./lib/dsl";
|
||||
|
||||
// 顶层布局:左侧 React Flow 编排画布 + 右侧 身份 / 偏好记忆 / 运行输出。
|
||||
export default function App() {
|
||||
const [identity, setIdentity] = useState<Identity>({ userId: "wt", sessionId: "sess-ui" });
|
||||
const [output, setOutput] = useState("");
|
||||
const [status, setStatus] = useState("就绪");
|
||||
const [running, setRunning] = useState(false);
|
||||
const closeRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const onRun = useCallback(
|
||||
async (dsl: TaskDsl) => {
|
||||
closeRef.current?.();
|
||||
setOutput("");
|
||||
setRunning(true);
|
||||
setStatus("提交任务…");
|
||||
try {
|
||||
const taskId = await submitTask(dsl, identity);
|
||||
setStatus(`流式中 · ${taskId}`);
|
||||
closeRef.current = streamTokens(
|
||||
taskId,
|
||||
(t) => setOutput((o) => o + t),
|
||||
() => {
|
||||
setStatus("完成 ✓");
|
||||
setRunning(false);
|
||||
},
|
||||
() => {
|
||||
setStatus("连接中断");
|
||||
setRunning(false);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
setStatus(`✗ ${(e as Error).message}`);
|
||||
setRunning(false);
|
||||
}
|
||||
},
|
||||
[identity],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="flex h-screen w-screen text-gray-900">
|
||||
<main className="flex-1 border-r">
|
||||
<AgentCanvas />
|
||||
<AgentCanvas onRun={onRun} running={running} />
|
||||
</main>
|
||||
<aside className="w-96 overflow-auto">
|
||||
<WikiPanel />
|
||||
<aside className="flex w-[26rem] flex-col overflow-auto">
|
||||
<section className="border-b p-4">
|
||||
<h2 className="mb-2 text-sm font-semibold text-gray-700">身份 / 会话</h2>
|
||||
<div className="flex gap-2">
|
||||
<label className="flex-1 text-xs text-gray-500">
|
||||
用户
|
||||
<input
|
||||
className="mt-1 w-full rounded border px-2 py-1 text-sm text-gray-900"
|
||||
value={identity.userId}
|
||||
onChange={(e) => setIdentity((id) => ({ ...id, userId: e.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex-1 text-xs text-gray-500">
|
||||
会话
|
||||
<input
|
||||
className="mt-1 w-full rounded border px-2 py-1 text-sm text-gray-900"
|
||||
value={identity.sessionId}
|
||||
onChange={(e) => setIdentity((id) => ({ ...id, sessionId: e.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MemoryPanel identity={identity} />
|
||||
|
||||
<section className="flex flex-1 flex-col p-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-sm font-semibold text-gray-700">运行输出(SSE Token 流)</h2>
|
||||
<span className="text-xs text-gray-400">{status}</span>
|
||||
</div>
|
||||
<pre className="flex-1 whitespace-pre-wrap rounded bg-gray-900 p-3 text-xs leading-relaxed text-emerald-300">
|
||||
{output || "在左侧加节点 → 运行,模型会注入你的偏好与历史后流式作答。"}
|
||||
</pre>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user