ba8c6b3c43
- 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>
57 lines
1.3 KiB
Go
57 lines
1.3 KiB
Go
package office
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRenderReportValidDocx(t *testing.T) {
|
|
data, err := NewRenderer().RenderReport(context.Background(), "测试报告 <X&Y>", []Section{
|
|
{Heading: "第一章 背景", Body: "这是第一段。\n这是第二段,含特殊字符 < > &。"},
|
|
{Heading: "第二章 分析", Body: "结论行。"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
|
if err != nil {
|
|
t.Fatalf("not a valid zip: %v", err)
|
|
}
|
|
want := map[string]bool{"[Content_Types].xml": false, "_rels/.rels": false, "word/document.xml": false}
|
|
var docXML string
|
|
for _, f := range zr.File {
|
|
if _, ok := want[f.Name]; ok {
|
|
want[f.Name] = true
|
|
}
|
|
if f.Name == "word/document.xml" {
|
|
rc, _ := f.Open()
|
|
var b strings.Builder
|
|
buf := make([]byte, 4096)
|
|
for {
|
|
n, e := rc.Read(buf)
|
|
b.Write(buf[:n])
|
|
if e != nil {
|
|
break
|
|
}
|
|
}
|
|
rc.Close()
|
|
docXML = b.String()
|
|
}
|
|
}
|
|
for name, found := range want {
|
|
if !found {
|
|
t.Errorf("missing part %s", name)
|
|
}
|
|
}
|
|
if !strings.Contains(docXML, "< > &") {
|
|
t.Errorf("xml not escaped properly: %s", docXML)
|
|
}
|
|
if !strings.Contains(docXML, "第一章 背景") {
|
|
t.Errorf("heading missing")
|
|
}
|
|
t.Logf("docx ok: %d bytes, %d parts", len(data), len(zr.File))
|
|
}
|