Files
sundynix-agentix/sundynix-desktop/frontend/src/App.tsx
T
Blizzard 72bd43965f feat(desktop): 工业化升级 A —— 设计系统地基(primitives + lucide + 语义令牌)
把"手搓内联 class + Unicode 字符图标"换成统一组件与真实图标,为后续工业化打底。

- 依赖:装 lucide-react(描线图标,按需 tree-shake)
- 令牌:tailwind.config 加语义色 brand/accent/success/warn/danger + 圆角档位;
  强调色字面量(violet/cyan/emerald…)收敛到令牌,便于整体换肤
- primitives(src/ui,零重依赖自建):Button/Input/Textarea/Select/Field/Card/Panel/
  Badge/Dot/Tabs/Skeleton/EmptyState/Dialog/Toast(+useToast)/cn,桶文件统一引入
- 迁移:TopBar/LeftNav/BottomDrawer + Home/Report/Runs/Kb/Placeholder/ExecTrace/
  MemoryPanel/StudioView 全部换 primitives + lucide 图标;导航/能力卡/按钮告别
  ▤◆▣▦ 等 Unicode 字符;错误改用全局 Toast;空状态用 EmptyState
- App 包 ToastProvider

验证:tsc + vite build 通过;浏览器(Preview)走查工作台/报告页——真实图标、统一卡片/
按钮/输入;跑报告端到端正常(执行轨迹 lucide 状态图标点亮、章节耗时/字数/检索片段、
完成弹 Toast + 下载 Word)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 16:39:42 +08:00

114 lines
4.9 KiB
TypeScript

import { useCallback, useRef, useState } from "react";
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 { KbView } from "./views/KbView";
import { ReportView } from "./views/ReportView";
import { RunsView } from "./views/RunsView";
import { Home } from "./views/Home";
import { Placeholder } from "./views/Placeholder";
import { submitTask, streamTokens, streamExec, type Identity } from "./lib/api";
import type { TaskDsl } from "./lib/dsl";
import { emptyRun, type RunState } from "./lib/run";
import { ToastProvider } from "./ui";
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: "租户/工作区 · 用户计费 · 护栏 · 模型与连接 · 设置。" },
};
export default function App() {
const [view, setView] = useState<ViewKey>("home");
const [identity, setIdentity] = useState<Identity>({ userId: "wt", sessionId: "sess-ui" });
const [run, setRun] = useState<RunState>(emptyRun);
const closeRef = useRef<(() => void) | null>(null);
const execCloseRef = useRef<(() => void) | null>(null);
const onRun = useCallback(
async (dsl: TaskDsl) => {
closeRef.current?.();
execCloseRef.current?.();
const t0 = Date.now();
setRun({ phase: "submitting", output: "", events: [{ t: 0, label: "提交任务" }], exec: [] });
try {
const taskId = await submitTask(dsl, identity);
let first = true;
setRun((r) => ({
...r,
phase: "streaming",
taskId,
events: [...r.events, { t: Date.now() - t0, label: `已发布 ${taskId}` }],
}));
// 执行轨迹(运行·观测):与 token 流并行订阅,逐节点点亮。
execCloseRef.current = streamExec(
taskId,
(ev) => setRun((r) => ({ ...r, exec: [...r.exec, ev] })),
() => {},
() => {},
);
closeRef.current = streamTokens(
taskId,
(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) {
setRun((r) => ({ ...r, phase: "error", error: (e as Error).message }));
}
},
[identity],
);
return (
<ToastProvider>
<div className="relative flex h-screen w-screen flex-col bg-ink-950 text-slate-200">
{/* 顶部柔光,增加纵深 */}
<div
className="pointer-events-none absolute inset-x-0 top-0 h-64 opacity-60"
style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }}
/>
<TopBar identity={identity} setIdentity={setIdentity} />
<div className="relative flex min-h-0 flex-1">
<LeftNav active={view} onSelect={setView} />
<main className="min-w-0 flex-1 overflow-hidden">
{view === "home" ? (
<Home onSelect={setView} />
) : view === "studio" ? (
<StudioView onRun={onRun} phase={run.phase} />
) : view === "kb" ? (
<KbView />
) : view === "report" ? (
<ReportView identity={identity} />
) : view === "runs" ? (
<RunsView run={run} />
) : view === "memory" ? (
<MemoryView identity={identity} />
) : (
<Placeholder {...(PLACEHOLDERS[view] ?? { title: "模块", desc: "规划中。" })} />
)}
</main>
</div>
<BottomDrawer run={run} />
</div>
</ToastProvider>
);
}