feat(report): 报告生成端到端 — 规划→分章并行检索撰写→渲染真实 Word
- shared: 新增 intent=report 任务约定 + ReportPath(跨进程共享落盘目录,零配置对齐) - dispatcher: handleReport 专用编排(DeepSeek 规划大纲 → 各章并行 RAG 检索+撰写 → 汇聚 → report_render),Pool.Chat 非流式聚合;进度与正文经 Token 流实时回流 - mcp-go: 用标准库 archive/zip + OOXML 拼出真实可打开的 .docx(零额外依赖), report_render 工具落盘到共享目录;附 docx 有效性测试 - gateway: POST /reports 触发;GET /reports/:id/download 下发 Word - desktop: 新增「报告」页(主题→实时编排进度→下载 Word),左导航置为就绪 实测:DeepSeek 生成 5 章报告 → 渲染 5KB docx → file 识别为 Microsoft Word 2007+ → textutil 提取标题/各章正文完整。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { generateReport, streamTokens, reportDownloadUrl, type Identity } from "../lib/api";
|
||||
|
||||
type Phase = "idle" | "running" | "done" | "error";
|
||||
|
||||
// 报告生成:输入主题(+可选知识库) → 触发后端专用编排
|
||||
// (规划大纲 → 各章并行检索+撰写 → 渲染 Word),实时看进度与正文,完成后下载 .docx。
|
||||
export function ReportView({ identity }: { identity: Identity }) {
|
||||
const [topic, setTopic] = useState("");
|
||||
const [kb, setKb] = useState("");
|
||||
const [phase, setPhase] = useState<Phase>("idle");
|
||||
const [out, setOut] = useState("");
|
||||
const [taskId, setTaskId] = useState("");
|
||||
const [err, setErr] = useState("");
|
||||
const closeRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const running = phase === "running";
|
||||
|
||||
const onGenerate = async () => {
|
||||
if (!topic.trim() || running) return;
|
||||
closeRef.current?.();
|
||||
setPhase("running");
|
||||
setOut("");
|
||||
setErr("");
|
||||
setTaskId("");
|
||||
try {
|
||||
const id = await generateReport(identity, topic.trim(), kb.trim() || undefined);
|
||||
setTaskId(id);
|
||||
closeRef.current = streamTokens(
|
||||
id,
|
||||
(tok) => setOut((o) => o + tok),
|
||||
() => setPhase("done"),
|
||||
() => {
|
||||
setErr("连接中断");
|
||||
setPhase("error");
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
setErr((e as Error).message);
|
||||
setPhase("error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col gap-4 overflow-y-auto 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] gap-3 rounded-xl border border-line bg-ink-900 p-4 shadow-card">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[11px] font-medium text-slate-400">报告主题</label>
|
||||
<input
|
||||
value={topic}
|
||||
onChange={(e) => setTopic(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && onGenerate()}
|
||||
placeholder="如:2026 年国产大模型产业现状与趋势分析"
|
||||
className="rounded-lg border border-line bg-ink-950 px-3 py-2 text-sm text-slate-200 outline-none placeholder:text-slate-600 focus:border-violet-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[11px] font-medium text-slate-400">知识库(可选)</label>
|
||||
<input
|
||||
value={kb}
|
||||
onChange={(e) => setKb(e.target.value)}
|
||||
placeholder="如 docs,留空则不挂检索"
|
||||
className="rounded-lg border border-line bg-ink-950 px-3 py-2 text-sm text-slate-200 outline-none placeholder:text-slate-600 focus:border-violet-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center gap-3">
|
||||
<button
|
||||
onClick={onGenerate}
|
||||
disabled={running || !topic.trim()}
|
||||
className="rounded-lg bg-gradient-to-r from-violet-500 to-cyan-500 px-4 py-2 text-sm font-medium text-white shadow-glow transition disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
{running ? "生成中…" : "生成报告"}
|
||||
</button>
|
||||
{phase === "done" && taskId && (
|
||||
<a
|
||||
href={reportDownloadUrl(taskId)}
|
||||
className="rounded-lg border border-violet-500/60 px-4 py-2 text-sm font-medium text-violet-300 transition hover:bg-violet-500/10"
|
||||
>
|
||||
⬇ 下载 Word
|
||||
</a>
|
||||
)}
|
||||
<span className="text-xs text-slate-500">
|
||||
{running && "正在编排,实时进度见下方…"}
|
||||
{phase === "done" && "已完成"}
|
||||
{phase === "error" && <span className="text-rose-400">出错:{err}</span>}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 实时进度 / 正文 */}
|
||||
<div className="flex min-h-0 flex-1 flex-col rounded-xl border border-line bg-ink-900 shadow-card">
|
||||
<div className="flex items-center gap-2 border-b border-line px-4 py-2.5 text-[11px] text-slate-500">
|
||||
<span className={`h-2 w-2 rounded-full ${running ? "animate-pulse bg-cyan-400" : phase === "done" ? "bg-emerald-400" : "bg-slate-600"}`} />
|
||||
实时编排 · {taskId || "未开始"}
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-4">
|
||||
{out ? (
|
||||
<pre className="whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-slate-300">{out}</pre>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-sm text-slate-600">
|
||||
输入主题并点击「生成报告」,这里将实时显示规划与撰写过程。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user