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,64 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"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})
|
||||
}
|
||||
|
||||
// DownloadReport: GET /api/v1/reports/:id/download —— 下载已渲染的 Word 文档。
|
||||
func (h *Handler) DownloadReport(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
path := contract.ReportPath(id)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "报告尚未生成或已过期"})
|
||||
return
|
||||
}
|
||||
c.Header("Content-Disposition", `attachment; filename="`+id+`.docx"`)
|
||||
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|
||||
c.File(path)
|
||||
}
|
||||
|
||||
func newReportID() string {
|
||||
var b [8]byte
|
||||
_, _ = rand.Read(b[:])
|
||||
return "report_" + hex.EncodeToString(b[:])
|
||||
}
|
||||
@@ -28,6 +28,9 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus) *gin.Engine {
|
||||
api.GET("/kb/ingest/:id/stream", h.KbIngestStream) // 入库进度 SSE(实时监控)
|
||||
api.POST("/kb/search", h.KbSearch) // 知识库检索台(→ mcp-go kb_search)
|
||||
api.GET("/kb/graph", h.KbGraph) // 知识图谱三元组(→ mcp-go kb_graph,Neo4j)
|
||||
|
||||
api.POST("/reports", h.GenerateReport) // 报告生成(intent=report 任务 → Dispatcher 专用编排)
|
||||
api.GET("/reports/:id/download", h.DownloadReport) // 下载渲染好的 Word(.docx)
|
||||
api.GET("/billing", h.Billing)
|
||||
|
||||
// 运维控制面:LLM 模型配置(独立运维控制台调用)。
|
||||
|
||||
Reference in New Issue
Block a user