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>
121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
import { useRef, useState } from "react";
|
||
import { Play, Download, FileText } from "lucide-react";
|
||
import { generateReport, streamTokens, streamExec, reportDownloadUrl, type Identity, type ExecEvent } from "../lib/api";
|
||
import { ExecTrace } from "../components/ExecTrace";
|
||
import { Button, Input, Field, Panel, Dot, EmptyState, useToast } from "../ui";
|
||
|
||
type Phase = "idle" | "running" | "done" | "error";
|
||
|
||
// 报告生成:输入主题(+可选知识库) → 触发后端专用编排
|
||
// (规划大纲 → 各章并行检索+撰写 → 渲染 Word),实时看进度与正文,完成后下载 .docx。
|
||
export function ReportView({ identity }: { identity: Identity }) {
|
||
const toast = useToast();
|
||
const [topic, setTopic] = useState("");
|
||
const [kb, setKb] = useState("");
|
||
const [phase, setPhase] = useState<Phase>("idle");
|
||
const [out, setOut] = useState("");
|
||
const [exec, setExec] = useState<ExecEvent[]>([]);
|
||
const [taskId, setTaskId] = useState("");
|
||
const closeRef = useRef<(() => void) | null>(null);
|
||
const execCloseRef = useRef<(() => void) | null>(null);
|
||
|
||
const running = phase === "running";
|
||
|
||
const onGenerate = async () => {
|
||
if (!topic.trim() || running) return;
|
||
closeRef.current?.();
|
||
execCloseRef.current?.();
|
||
setPhase("running");
|
||
setOut("");
|
||
setExec([]);
|
||
setTaskId("");
|
||
try {
|
||
const id = await generateReport(identity, topic.trim(), kb.trim() || undefined);
|
||
setTaskId(id);
|
||
execCloseRef.current = streamExec(
|
||
id,
|
||
(ev) => setExec((xs) => [...xs, ev]),
|
||
() => {},
|
||
() => {},
|
||
);
|
||
closeRef.current = streamTokens(
|
||
id,
|
||
(tok) => setOut((o) => o + tok),
|
||
() => {
|
||
setPhase("done");
|
||
toast.push("success", "报告已生成,可下载 Word");
|
||
},
|
||
() => {
|
||
setPhase("error");
|
||
toast.push("error", "报告流连接中断");
|
||
},
|
||
);
|
||
} catch (e) {
|
||
setPhase("error");
|
||
toast.push("error", (e as Error).message);
|
||
}
|
||
};
|
||
|
||
const tracePhase = running ? "streaming" : phase === "done" ? "done" : phase === "error" ? "error" : "idle";
|
||
|
||
return (
|
||
<div className="flex h-full min-h-0 flex-col gap-4 overflow-hidden p-6">
|
||
<header>
|
||
<h1 className="text-lg font-semibold text-slate-100">报告生成</h1>
|
||
<p className="mt-1 text-xs text-slate-500">
|
||
规划大纲 → 各章并行(知识库检索 + LLM 撰写)→ 汇聚 → 渲染真实 Word(.docx)。依赖已配置对话模型;挂知识库则引用其资料。
|
||
</p>
|
||
</header>
|
||
|
||
<div className="grid grid-cols-[1fr_220px_auto_auto] items-end gap-3 rounded-lg border border-line bg-ink-900 p-4 shadow-card">
|
||
<Field label="报告主题">
|
||
<Input
|
||
value={topic}
|
||
onChange={(e) => setTopic(e.target.value)}
|
||
onKeyDown={(e) => e.key === "Enter" && onGenerate()}
|
||
placeholder="如:2026 年国产大模型产业现状与趋势分析"
|
||
/>
|
||
</Field>
|
||
<Field label="知识库(可选)">
|
||
<Input value={kb} onChange={(e) => setKb(e.target.value)} placeholder="如 docs,留空则不挂检索" />
|
||
</Field>
|
||
<Button variant="primary" icon={Play} onClick={onGenerate} disabled={running || !topic.trim()}>
|
||
{running ? "生成中…" : "生成报告"}
|
||
</Button>
|
||
{phase === "done" && taskId ? (
|
||
<a
|
||
href={reportDownloadUrl(taskId)}
|
||
className="inline-flex h-9 items-center gap-1.5 rounded-md border border-brand/60 px-4 text-sm font-medium text-brand-400 transition hover:bg-brand/10"
|
||
>
|
||
<Download className="h-4 w-4" />
|
||
下载 Word
|
||
</a>
|
||
) : (
|
||
<span className="h-9" />
|
||
)}
|
||
</div>
|
||
|
||
<div className="grid min-h-0 flex-1 grid-cols-[340px_1fr] gap-4">
|
||
<Panel title="执行轨迹" icon={FileText}>
|
||
<ExecTrace events={exec} phase={tracePhase} />
|
||
</Panel>
|
||
|
||
<Panel
|
||
title={
|
||
<span className="flex items-center gap-2">
|
||
<Dot tone={running ? "running" : phase === "done" ? "success" : "neutral"} pulse={running} />
|
||
报告正文 · {taskId || "未开始"}
|
||
</span>
|
||
}
|
||
>
|
||
{out ? (
|
||
<pre className="whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-slate-300">{out}</pre>
|
||
) : (
|
||
<EmptyState icon={FileText} title="尚未生成报告" desc="输入主题并点击「生成报告」,这里将实时显示规划与撰写过程。" />
|
||
)}
|
||
</Panel>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|