Files
sundynix-agentix/sundynix-gateway/internal/handler/report.go
T
Blizzard 1dd6b0cce3 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>
2026-06-17 14:04:06 +08:00

78 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 的任务发到 NATSDispatcher 走专用编排(规划→分章并行→渲染 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: // docxres.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[:])
}