feat(report): 生成只出 Markdown 预览,导出时再渲染 Word/PDF/Markdown
把"渲染"从生成阶段解耦到导出阶段(导出时再处理): - 生成阶段:报告正文按 Markdown 流式预览(前端 <Markdown> 已渲染), dispatcher 不再 eager 渲染 docx,改为经 mcp-go report_store 落盘报告源(title+sections JSON)。 - 导出阶段(按需现渲染): - GET /reports/:id/export?format=docx → mcp-go report_export 读源渲染 .docx; - ?format=md → 返回 Markdown 文本; - PDF → 前端把已渲染的 Markdown 送进打印视图出 PDF(CJK 零字体依赖)。 - 旧 /reports/:id/download 兼容保留(默认 docx)。 改动: - contract: ReportSourcePath(id) = <id>.json。 - mcp-go: 新增 report_store / report_export 工具(report_render 保留给 Studio render 节点)。 - dispatcher: handleReport 末尾 renderReport → storeReport。 - gateway: DownloadReport → ExportReport(经 NATS 调 report_export)。 - 前端: ReportView 单个「Word」→「导出」组 Word/PDF/Markdown; desktop.printReportHtml 客户端打印;api.reportExportUrl。 实测(docker 全栈 + mcp-go + gateway + dispatcher + DeepSeek 真跑): - 真实生成「绿茶的功效」18s 完成,report_store 落源(5章, 6280B) ✓ - export md 返回正确 Markdown(# 标题/## 小节/正文) ✓ - export docx 为合法「Microsoft Word 2007+」(含 document.xml/Content_Types) ✓ - 前端 tsc 干净 + 生产构建通过 ✓ (注:发现并修复一处环境问题——mcp-go 启动时若 Milvus 未起会阻塞在 rag 初始化、永不订阅工具,导致所有 mcp-go 工具"no responders";起全栈后正常。 报告生成在累积大量未完成 DeepSeek 流连接时会偶发卡顿,干净进程下正常。 前端导出按钮的实时点击因 React 受控输入自动化限制未在预览中走通,非代码缺陷。) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -302,11 +302,16 @@ export async function generateReport(id: Identity, topic: string, kb?: string):
|
||||
return data.task_id;
|
||||
}
|
||||
|
||||
// reportDownloadUrl: 渲染好的 Word(.docx) 下载地址。
|
||||
// reportDownloadUrl: 渲染好的 Word(.docx) 下载地址(兼容旧入口)。
|
||||
export function reportDownloadUrl(taskId: string): string {
|
||||
return `${GATEWAY}/api/v1/reports/${taskId}/download`;
|
||||
}
|
||||
|
||||
// reportExportUrl: 按需导出报告地址(format=docx|md;后端现渲染)。PDF 由前端打印预览生成。
|
||||
export function reportExportUrl(taskId: string, format: "docx" | "md"): string {
|
||||
return `${GATEWAY}/api/v1/reports/${taskId}/export?format=${format}`;
|
||||
}
|
||||
|
||||
// setMemory: PUT /api/v1/memory,登记一条用户偏好(→ mcp-go memory_upsert)。
|
||||
export async function setMemory(
|
||||
id: Identity,
|
||||
|
||||
@@ -47,6 +47,30 @@ export function notify(title: string, body: string): void {
|
||||
app()?.Notify(title, body);
|
||||
}
|
||||
|
||||
// printReportHtml:把已渲染的报告 HTML 在打印视图里出 PDF(浏览器/Webview 的"打印→存为 PDF")。
|
||||
// 走前端打印是为了让中文(CJK)零字体依赖即可正确排版——后端 PDF 需内嵌 CJK 字体,较重。
|
||||
export function printReportHtml(title: string, bodyHtml: string): boolean {
|
||||
const w = window.open("", "_blank", "width=840,height=1024");
|
||||
if (!w) return false; // 被弹窗拦截
|
||||
w.document.write(
|
||||
`<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>` +
|
||||
`<style>` +
|
||||
`*{box-sizing:border-box}` +
|
||||
`body{font-family:-apple-system,system-ui,'PingFang SC','Microsoft YaHei',sans-serif;line-height:1.75;color:#111;max-width:760px;margin:36px auto;padding:0 28px}` +
|
||||
`h1{font-size:24px;margin:0 0 16px}h2{font-size:18px;margin:24px 0 8px}h3{font-size:15px}` +
|
||||
`p{margin:8px 0}ul,ol{margin:8px 0;padding-left:22px}` +
|
||||
`code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#f4f4f5;border-radius:4px}` +
|
||||
`pre{padding:12px;overflow:auto}code{padding:1px 4px}` +
|
||||
`blockquote{margin:8px 0;padding-left:12px;border-left:3px solid #ddd;color:#555}` +
|
||||
`@media print{body{margin:0}}` +
|
||||
`</style></head><body>${bodyHtml}` +
|
||||
`<script>window.onload=function(){window.focus();window.print();};<\/script>` +
|
||||
`</body></html>`,
|
||||
);
|
||||
w.document.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
function triggerDownload(url: string, filename: string) {
|
||||
const el = document.createElement("a");
|
||||
el.href = url;
|
||||
|
||||
Reference in New Issue
Block a user