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:
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
sharedbus "github.com/sundynix/sundynix-shared/bus"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
|
||||
"github.com/sundynix/sundynix-mcp-go/internal/history"
|
||||
"github.com/sundynix/sundynix-mcp-go/internal/memory"
|
||||
"github.com/sundynix/sundynix-mcp-go/internal/office"
|
||||
"github.com/sundynix/sundynix-mcp-go/internal/rag"
|
||||
"github.com/sundynix/sundynix-mcp-go/internal/search"
|
||||
)
|
||||
@@ -37,7 +40,7 @@ func (g *Gateway) Serve(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = unsub() }()
|
||||
log.Printf("[mcp_go] tools ready on %s (queue=%s): wiki_search, kb_ingest, kb_search, kb_graph, memory_*, history_*, echo",
|
||||
log.Printf("[mcp_go] tools ready on %s (queue=%s): wiki_search, kb_ingest, kb_search, kb_graph, report_render, memory_*, history_*, echo",
|
||||
contract.SubjectToolsGoAll, contract.QueueToolsGo)
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
@@ -55,6 +58,8 @@ func (g *Gateway) dispatch(ctx context.Context, call *contract.ToolCall) *contra
|
||||
return g.kbSearch(ctx, call)
|
||||
case "kb_graph":
|
||||
return g.kbGraph(ctx, call)
|
||||
case "report_render":
|
||||
return g.reportRender(ctx, call)
|
||||
case "memory_get":
|
||||
return g.memoryGet(ctx, call)
|
||||
case "memory_upsert":
|
||||
@@ -174,6 +179,37 @@ func (g *Gateway) kbGraph(ctx context.Context, call *contract.ToolCall) *contrac
|
||||
return &contract.ToolResult{OK: true, Content: string(data)}
|
||||
}
|
||||
|
||||
// reportRender 把结构化报告(title + sections[{heading,body}])渲染为真实 .docx,
|
||||
// 落盘到 contract.ReportPath(task_id),返回绝对路径供 Gateway 提供下载。
|
||||
func (g *Gateway) reportRender(ctx context.Context, call *contract.ToolCall) *contract.ToolResult {
|
||||
title, _ := call.Args["title"].(string)
|
||||
id, _ := call.Args["task_id"].(string)
|
||||
if id == "" {
|
||||
id = call.TaskID
|
||||
}
|
||||
if id == "" {
|
||||
return &contract.ToolResult{OK: false, Error: "report_render: task_id 必填"}
|
||||
}
|
||||
// sections 经 NATS JSON 透传,统一 re-marshal 再解出强类型。
|
||||
var secs []office.Section
|
||||
if raw, err := json.Marshal(call.Args["sections"]); err == nil {
|
||||
_ = json.Unmarshal(raw, &secs)
|
||||
}
|
||||
data, err := office.NewRenderer().RenderReport(ctx, title, secs)
|
||||
if err != nil {
|
||||
return &contract.ToolResult{OK: false, Error: "report_render: " + err.Error()}
|
||||
}
|
||||
path := contract.ReportPath(id)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return &contract.ToolResult{OK: false, Error: "report_render: mkdir " + err.Error()}
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return &contract.ToolResult{OK: false, Error: "report_render: write " + err.Error()}
|
||||
}
|
||||
log.Printf("[mcp_go] report_render 已生成 %s (%d 字节, %d 章节)", path, len(data), len(secs))
|
||||
return &contract.ToolResult{OK: true, Content: path}
|
||||
}
|
||||
|
||||
// kbIngest 把文本入库(切块→embedding→Milvus+Bleve)。
|
||||
// 带 job_id 时逐阶段把进度发到 sundynix.streams.<job_id>,供 UI 实时入库监控。
|
||||
func (g *Gateway) kbIngest(ctx context.Context, call *contract.ToolCall) *contract.ToolResult {
|
||||
|
||||
Reference in New Issue
Block a user