1dd6b0cce3
把"渲染"从生成阶段解耦到导出阶段(导出时再处理): - 生成阶段:报告正文按 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>
78 lines
2.7 KiB
Go
78 lines
2.7 KiB
Go
package handler
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"github.com/sundynix/sundynix-shared/contract"
|
||
)
|
||
|
||
// GenerateReport: POST /api/v1/reports —— 触发报告生成。
|
||
// 组装一个 intent=report 的任务发到 NATS,Dispatcher 走专用编排(规划→分章并行→渲染 docx)。
|
||
// 返回 task_id;客户端用 GET /tasks/:id/stream 看实时进度,完成后用 /reports/:id/download 取 Word。
|
||
func (h *Handler) GenerateReport(c *gin.Context) {
|
||
var body struct {
|
||
Topic string `json:"topic"`
|
||
KB string `json:"kb"`
|
||
}
|
||
if err := c.ShouldBindJSON(&body); err != nil || body.Topic == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "topic required"})
|
||
return
|
||
}
|
||
id := newReportID()
|
||
graph, _ := json.Marshal(map[string]any{"topic": body.Topic}) // 占位 DSL,报告编排实际读 Meta
|
||
task := &contract.Task{
|
||
ID: id,
|
||
Graph: graph,
|
||
Meta: map[string]any{
|
||
contract.MetaIntent: contract.IntentReport,
|
||
contract.MetaTopic: body.Topic,
|
||
contract.MetaKB: body.KB,
|
||
contract.MetaUserID: userID(c),
|
||
contract.MetaSessionID: sessionID(c),
|
||
},
|
||
}
|
||
if err := h.bus.PublishTask(c.Request.Context(), task); err != nil {
|
||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusAccepted, gin.H{"task_id": id})
|
||
}
|
||
|
||
// ExportReport: GET /api/v1/reports/:id/export?format=docx|md —— 按需把报告源渲染为指定格式并下载。
|
||
// 生成阶段只存源;此处经 mcp-go report_export 现渲染("导出时再处理")。PDF 由前端打印预览生成。
|
||
func (h *Handler) ExportReport(c *gin.Context) {
|
||
id := c.Param("id")
|
||
format := c.DefaultQuery("format", "docx")
|
||
res, err := h.bus.CallTool(c.Request.Context(), contract.ToolSubjectGo("report_export"),
|
||
&contract.ToolCall{Tool: "report_export", Args: map[string]any{"task_id": id, "format": format}})
|
||
if err != nil || res == nil || !res.OK {
|
||
msg := "报告尚未生成或已过期"
|
||
if res != nil && res.Error != "" {
|
||
msg = res.Error
|
||
}
|
||
c.JSON(http.StatusNotFound, gin.H{"error": msg})
|
||
return
|
||
}
|
||
switch format {
|
||
case "md", "markdown":
|
||
c.Header("Content-Disposition", `attachment; filename="`+id+`.md"`)
|
||
c.Header("Content-Type", "text/markdown; charset=utf-8")
|
||
c.String(http.StatusOK, res.Content)
|
||
default: // docx:res.Content 为 mcp-go 落盘的 .docx 路径
|
||
c.Header("Content-Disposition", `attachment; filename="`+id+`.docx"`)
|
||
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|
||
c.File(res.Content)
|
||
}
|
||
}
|
||
|
||
func newReportID() string {
|
||
var b [8]byte
|
||
_, _ = rand.Read(b[:])
|
||
return "report_" + hex.EncodeToString(b[:])
|
||
}
|