feat(desktop): MVP 驾驶舱外壳 + 类型化节点 Studio + 运行抽屉
按 desktop-ui-plan.md 落 MVP:五区外壳 + 编排 Studio + 底部抽屉 + 健康灯。 - shell: TopBar(垂直切换/健康灯[Gateway/DB 实时,余规划]/身份会话) + LeftNav(BUILD/RUN/MANAGE 分组,未就绪模块灰显) + BottomDrawer(输出/轨迹/工具调用/引用/评测) - studio: 类型化节点目录(输入/检索RAG/Agent/工具/记忆/分支/并行/汇聚/渲染/输出, 按类配色) + 自定义 TypedNode(状态徽标) + Inspector(按类型渲染配置表单) + 校验(孤立节点/必填项) + 运行 - views: MemoryView(复用偏好面板) + Placeholder(规划中模块,露出 IA 与依赖) - lib: run(运行状态机) + health(轮询 billing) + dsl(导出类型化 DSL + validate) - 删旧 AgentCanvas(被 StudioView 取代) 验证: npm run build(tsc+vite)✓; 真实浏览器跑通——加类型化节点→校验(标出孤立)→运行 →SSE 注入画像(老王)+历史 流入抽屉, 健康灯 Gateway/DB 实时绿 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,87 +1,83 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { AgentCanvas } from "./canvas/AgentCanvas";
|
||||
import { MemoryPanel } from "./panels/MemoryPanel";
|
||||
import { TopBar } from "./shell/TopBar";
|
||||
import { LeftNav, type ViewKey } from "./shell/LeftNav";
|
||||
import { BottomDrawer } from "./shell/BottomDrawer";
|
||||
import { StudioView } from "./studio/StudioView";
|
||||
import { MemoryView } from "./views/MemoryView";
|
||||
import { Placeholder } from "./views/Placeholder";
|
||||
import { submitTask, streamTokens, type Identity } from "./lib/api";
|
||||
import type { TaskDsl } from "./lib/dsl";
|
||||
import { emptyRun, type RunState } from "./lib/run";
|
||||
|
||||
const PLACEHOLDERS: Partial<Record<ViewKey, { title: string; desc: string }>> = {
|
||||
home: { title: "工作台", desc: "概览:知识库 / 文档 / 近期运行 / 待办报告 / 配额计费 + 快捷入口。" },
|
||||
kb: { title: "知识库 (RAG)", desc: "入库流水线监控 · 检索调试台(带来源徽标) · 文档/块浏览 · 知识图谱 · 检索评测。依赖 embedding + 入库 worker + 真实混合检索。" },
|
||||
report: { title: "报告生成", desc: "模板库 · 大纲编辑 · 章节并行生成进度 · 实时预览(含引用) · 导出 docx/pdf。依赖 RAG 核心链 + UniOffice。" },
|
||||
runs: { title: "运行 · 观测", desc: "实时执行 · 节点轨迹 · 工具调用 · 运行历史复盘。当前运行结果见底部抽屉。" },
|
||||
market: { title: "市场 · Packs", desc: "垂直包(法律/医疗/金融) · Agent 模板 · 开通向导(建租户→入库→注册模板→应用配置)。依赖多租户 + Pack 格式。" },
|
||||
admin: { title: "管理", desc: "租户/工作区 · 用户计费 · 护栏 · 模型与连接 · 设置。" },
|
||||
};
|
||||
|
||||
// 顶层布局:左侧 React Flow 编排画布 + 右侧 身份 / 偏好记忆 / 运行输出。
|
||||
export default function App() {
|
||||
const [view, setView] = useState<ViewKey>("studio");
|
||||
const [identity, setIdentity] = useState<Identity>({ userId: "wt", sessionId: "sess-ui" });
|
||||
const [output, setOutput] = useState("");
|
||||
const [status, setStatus] = useState("就绪");
|
||||
const [running, setRunning] = useState(false);
|
||||
const [run, setRun] = useState<RunState>(emptyRun);
|
||||
const closeRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const onRun = useCallback(
|
||||
async (dsl: TaskDsl) => {
|
||||
closeRef.current?.();
|
||||
setOutput("");
|
||||
setRunning(true);
|
||||
setStatus("提交任务…");
|
||||
const t0 = Date.now();
|
||||
setRun({ phase: "submitting", output: "", events: [{ t: 0, label: "提交任务" }] });
|
||||
try {
|
||||
const taskId = await submitTask(dsl, identity);
|
||||
setStatus(`流式中 · ${taskId}`);
|
||||
let first = true;
|
||||
setRun((r) => ({
|
||||
...r,
|
||||
phase: "streaming",
|
||||
taskId,
|
||||
events: [...r.events, { t: Date.now() - t0, label: `已发布 ${taskId}` }],
|
||||
}));
|
||||
closeRef.current = streamTokens(
|
||||
taskId,
|
||||
(t) => setOutput((o) => o + t),
|
||||
() => {
|
||||
setStatus("完成 ✓");
|
||||
setRunning(false);
|
||||
},
|
||||
() => {
|
||||
setStatus("连接中断");
|
||||
setRunning(false);
|
||||
},
|
||||
(tok) =>
|
||||
setRun((r) => {
|
||||
const ev = first ? [...r.events, { t: Date.now() - t0, label: "首 token" }] : r.events;
|
||||
first = false;
|
||||
return { ...r, output: r.output + tok, events: ev };
|
||||
}),
|
||||
() =>
|
||||
setRun((r) => ({
|
||||
...r,
|
||||
phase: "done",
|
||||
events: [...r.events, { t: Date.now() - t0, label: "完成" }],
|
||||
})),
|
||||
() => setRun((r) => ({ ...r, phase: "error", error: "连接中断" })),
|
||||
);
|
||||
} catch (e) {
|
||||
setStatus(`✗ ${(e as Error).message}`);
|
||||
setRunning(false);
|
||||
setRun((r) => ({ ...r, phase: "error", error: (e as Error).message }));
|
||||
}
|
||||
},
|
||||
[identity],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen text-gray-900">
|
||||
<main className="flex-1 border-r">
|
||||
<AgentCanvas onRun={onRun} running={running} />
|
||||
</main>
|
||||
<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 className="flex h-screen w-screen flex-col text-gray-900">
|
||||
<TopBar identity={identity} setIdentity={setIdentity} />
|
||||
<div className="flex min-h-0 flex-1">
|
||||
<LeftNav active={view} onSelect={setView} />
|
||||
<main className="min-w-0 flex-1 overflow-hidden">
|
||||
{view === "studio" ? (
|
||||
<StudioView onRun={onRun} phase={run.phase} />
|
||||
) : view === "memory" ? (
|
||||
<MemoryView identity={identity} />
|
||||
) : (
|
||||
<Placeholder {...(PLACEHOLDERS[view] ?? { title: "模块", desc: "规划中。" })} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
<BottomDrawer run={run} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user