72bd43965f
把"手搓内联 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>
114 lines
4.9 KiB
TypeScript
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>
|
|
);
|
|
}
|