refactor: 后端仓库层收口(A1:课程/产品/素材)+ 收进工作区既有对象化重构
本提交含两部分。第一部分是本轮工作;第二部分是此前一直留在工作区、
从未提交的对象化重构,与第一部分在文件上互相咬合(internal/repository
整个包都是未跟踪状态,且 api 层已有文件引用它),无法拆成两个可编译的提交。
一、仓库层收口 A1 批(本轮工作)
把 api 层手写的 store.DB 查询收进具名仓库方法,只给真正获益的对象做方法,
不机械包裹全量。本批迁移 22 处裸查询(courses.go 9 / media.go 12 / products.go 1),
新增方法:
- MediaFileRepo.ListByBind / ListForAudit / MarkExtracted
- KnowledgeChunkRepo.CountByMediaFile
- ProductRepo.GetVisibleByID
两条业务口径改由仓库单点持有,避免各处手写漂移:
「只有 approved 素材出现在课程详情」与「已停用产品不在课程详情露出」。
修掉两个真实缺陷:
- ProductRepo.GetByID 缺 Where 条件。此前 GET /api/products/{id} 对任意 id 都返回
第一条产品、对不存在的 id 返回 200,且 PUT /api/products/{id} 会覆盖第一条产品
—— 数据损坏级。全仓扫描确认这是唯一一处同型写法。
- ProductRepo.Delete 写 status="deleted",而 DELETE 处理器文档与回包都声称
"inactive",接口在说谎;管理员用 status=all 拉列表会看到前端不认识的状态。
已对齐为 inactive(与 CourseRepo.Delete 一致)。
删除 8 个零调用且列名不存在的死方法(一调即 SQL 报错):
- media_file 上的 file_path / file_type / approval_status 三列并不存在,
GetByPath / ListByType / UpdateStatus 全废
- knowledge_chunk 上的 space_id 列不存在(模型早已改为 knowledge_space_key),
List / Total / ListBySpaceIDs / DeleteBySpace / SearchByVector 全废
取舍边界:能对当前 schema 跑通的死方法保留,跑不通的删或修。
CourseRepo.List 补齐 status=all 档(此前传给它会当作 status='all' 过滤出空列表)。
该方法此前零调用,现与产品列表语义对齐。
验证:go build ./... 与 go test ./... 全绿;另用真实 HTTP 请求验证 34 项
(课程 17 / 产品 3 / 素材 14),跑在数据库副本与独立 KB_DATA_DIR 上,
含 multipart 真上传 → 审批 → pdftotext 提取 → 分片入库的完整链路。
二、此前未提交的对象化重构(非本轮工作)
- 新增 internal/repository 仓库层、connectors、skills、specialists、xapps、jsonutil,
model/task_record|task_run|task_artifact、api/task_runtime|action_definition|chat_message
- 删除 api/app_definition、connectors、my_app_center、notification、office_skill、
export_docx|pptx|xlsx、official_account_* 等,随 XApp/Skill/Specialist/Connector
可插拔打包方向(AR10/AR11)调整
- 资产目录归位:backend-go/knowledge_source → assets/knowledge/source、
training_materials → assets/training/materials;README 内相对路径同步加深两级;
deploy env 补 ASSET_ROOT_DIR 并改 KNOWLEDGE_SOURCE_DIR / TRAINING_MATERIALS_DIR
- 前端新增 skills/ specialists/ connectors/ xapps/ 目录与对应页面
验证:前端 npm run build 通过(7.26s)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package audiotranscribe
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "audio-transcribe",
|
||||
Label: "语音转写",
|
||||
OwnedDefinitionKeys: []string{"skill.audio-transcribe.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.audio-transcribe"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package batchextract
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "batch-extract",
|
||||
Label: "批量提取",
|
||||
OwnedDefinitionKeys: []string{"skill.batch-extract.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.batch-extract"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package contractbrief
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "contract-brief",
|
||||
Label: "合同要点提炼",
|
||||
OwnedDefinitionKeys: []string{"skill.contract-brief.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.contract-brief"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package contractbrief
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "contract-brief",
|
||||
Label: "合同要点提炼",
|
||||
PlanTitle: "合同要点提炼流程",
|
||||
PlanSteps: []string{"识别合同结构", "提炼核心条款", "标记风险事项", "输出摘要版本"},
|
||||
ResultTitle: "合同要点摘要",
|
||||
Summary: "已提炼出合同核心条款、主要风险和待确认事项,方便快速预读和继续审查。",
|
||||
Highlights: []string{"适合采购、合作、服务类合同", "自动压缩核心条款与风险点", "默认沉淀摘要、风险和待确认项"},
|
||||
ActionType: "summary",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package contractreview
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "contract-review",
|
||||
Label: "合同审查",
|
||||
OwnedDefinitionKeys: []string{"skill.contract-review.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.contract-review"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package copyproofreading
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "copy-proofreading",
|
||||
Label: "文案校对",
|
||||
OwnedDefinitionKeys: []string{"skill.copy-proofreading.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.copy-proofreading"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package documenttranslate
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "document-translate",
|
||||
Label: "文档翻译",
|
||||
OwnedDefinitionKeys: []string{"skill.document-translate.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.document-translate"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package emaildrafting
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "email-drafting",
|
||||
Label: "邮件起草",
|
||||
OwnedDefinitionKeys: []string{"skill.email-drafting.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.email-drafting"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package emaildrafting
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "email-drafting",
|
||||
Label: "邮件起草",
|
||||
PlanTitle: "邮件起草流程",
|
||||
PlanSteps: []string{"识别沟通目的", "整理背景与重点", "生成正式邮件", "补充简短跟进版本"},
|
||||
ResultTitle: "邮件草稿",
|
||||
Summary: "已生成一版可直接发送的邮件正文,并补了简短跟进版本和要点摘要。",
|
||||
Highlights: []string{"适合汇报、催办、确认和通知", "兼顾正式语气与清晰表达", "默认沉淀长邮件和短消息版本"},
|
||||
ActionType: "communication",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package interviewsummary
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "interview-summary",
|
||||
Label: "访谈整理",
|
||||
OwnedDefinitionKeys: []string{"skill.interview-summary.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.interview-summary"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package interviewsummary
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "interview-summary",
|
||||
Label: "访谈整理",
|
||||
PlanTitle: "访谈整理流程",
|
||||
PlanSteps: []string{"归并访谈内容", "提炼主题观点", "总结关键洞察", "输出整理版本"},
|
||||
ResultTitle: "访谈整理稿",
|
||||
Summary: "已把访谈内容收成主题摘要、洞察结论和后续建议,方便继续分析或汇报。",
|
||||
Highlights: []string{"适合用户访谈、需求调研、深度沟通", "自动提炼主题与洞察", "默认沉淀摘要、洞察和行动建议"},
|
||||
ActionType: "document",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package longformwriting
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "longform-writing",
|
||||
Label: "长文创作",
|
||||
OwnedDefinitionKeys: []string{"skill.longform-writing.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.longform-writing"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package longformwriting
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "longform-writing",
|
||||
Label: "长文创作",
|
||||
PlanTitle: "长文创作流程",
|
||||
PlanSteps: []string{"明确文体与受众", "规划章节大纲", "生成正文骨架", "整理摘要与交付稿"},
|
||||
ResultTitle: "长文草稿",
|
||||
Summary: "已输出一版长文大纲和正文骨架,方便继续扩写为正式稿。",
|
||||
Highlights: []string{"先给大纲再给正文草稿", "适合方案、制度和长文章", "结果可下载继续改写"},
|
||||
ActionType: "document",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package meetingminutes
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "meeting-minutes",
|
||||
Label: "会议纪要",
|
||||
OwnedDefinitionKeys: []string{"skill.meeting-minutes.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.meeting-minutes"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package meetingminutes
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "meeting-minutes",
|
||||
Label: "会议纪要",
|
||||
PlanTitle: "会议纪要整理流程",
|
||||
PlanSteps: []string{"识别会议主题", "提炼关键结论", "整理行动项", "输出同步版本"},
|
||||
ResultTitle: "会议纪要草稿",
|
||||
Summary: "已按会议纪要结构整理出主题、结论和待办,方便直接同步给参会人。",
|
||||
Highlights: []string{"自动提炼结论与待办", "适合例会、评审会和复盘会", "默认沉淀纪要正文和行动项清单"},
|
||||
ActionType: "document",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package mindmap
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "mind-map",
|
||||
Label: "思维导图",
|
||||
OwnedDefinitionKeys: []string{"skill.mind-map.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.mind-map"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package mindmap
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "mind-map",
|
||||
Label: "思维导图",
|
||||
PlanTitle: "思维导图生成流程",
|
||||
PlanSteps: []string{"识别主题主线", "拆分一级分支", "补齐关键节点", "整理导图源结构"},
|
||||
ResultTitle: "导图结构草案",
|
||||
Summary: "已把输入内容压成导图结构,并生成可继续分享和编辑的导图结果。",
|
||||
Highlights: []string{"可下载导图 SVG", "保留导图文字结构", "适合任务拆解与知识梳理"},
|
||||
ActionType: "mindmap",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ocrunderstanding
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "ocr-understanding",
|
||||
Label: "OCR / 图文理解",
|
||||
OwnedDefinitionKeys: []string{"skill.ocr-understanding.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.ocr-understanding"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ocrunderstanding
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "ocr-understanding",
|
||||
Label: "OCR / 图文理解",
|
||||
PlanTitle: "图文理解流程",
|
||||
PlanSteps: []string{"识别图像内容", "抽取文字与字段", "整理结构摘要", "输出可复用结果"},
|
||||
ResultTitle: "识别结果摘要",
|
||||
Summary: "上传图片后,会优先使用 PaddleOCR 识别文字,并把结果写入当前任务产物。",
|
||||
Highlights: []string{"走后端正式 OCR 链路", "识别结果会写入任务产物", "上传图片会长期保留到任务输入"},
|
||||
ActionType: "ocr",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package policyrewrite
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "policy-rewrite",
|
||||
Label: "制度改写",
|
||||
OwnedDefinitionKeys: []string{"skill.policy-rewrite.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.policy-rewrite"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package policyrewrite
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "policy-rewrite",
|
||||
Label: "制度改写",
|
||||
PlanTitle: "制度改写流程",
|
||||
PlanSteps: []string{"识别制度目标", "统一结构口径", "重写正式正文", "补充修订说明"},
|
||||
ResultTitle: "制度改写稿",
|
||||
Summary: "已将原制度改写为更清楚统一的正式版本,并补了修订说明和执行提示。",
|
||||
Highlights: []string{"适合制度、流程、规范文档", "兼顾正式表达与清晰可读", "默认沉淀改写正文和修订说明"},
|
||||
ActionType: "document",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package pptgeneration
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "ppt-generation",
|
||||
Label: "PPT 生成",
|
||||
OwnedDefinitionKeys: []string{"skill.ppt-generation.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.ppt-generation"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package pptgeneration
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "ppt-generation",
|
||||
Label: "PPT 生成",
|
||||
PlanTitle: "PPT 生成流程",
|
||||
PlanSteps: []string{"确认汇报目标", "规划页面结构", "提炼每页要点", "整理讲稿备注"},
|
||||
ResultTitle: "演示文稿骨架",
|
||||
Summary: "已按输入内容整理出演示逻辑,并生成可继续下载和细化的 PPT 结果。",
|
||||
Highlights: []string{"支持直接下载 .pptx", "同时保留大纲与讲稿备注", "适合汇报、方案和复盘"},
|
||||
ActionType: "presentation",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package progressreport
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "progress-report",
|
||||
Label: "周报 / 日报",
|
||||
OwnedDefinitionKeys: []string{"skill.progress-report.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.progress-report"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package progressreport
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "progress-report",
|
||||
Label: "周报 / 日报",
|
||||
PlanTitle: "进展汇报流程",
|
||||
PlanSteps: []string{"归并本期工作", "提炼关键进展", "整理风险事项", "输出汇报版本"},
|
||||
ResultTitle: "进展汇报草稿",
|
||||
Summary: "已整理出本期进展、风险事项和下一步计划,方便直接同步给负责人。",
|
||||
Highlights: []string{"适合日报、周报和阶段同步", "自动补齐风险与下步计划", "默认沉淀正式汇报版和简短摘要版"},
|
||||
ActionType: "report",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package projectplanning
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "project-planning",
|
||||
Label: "项目规划",
|
||||
OwnedDefinitionKeys: []string{"skill.project-planning.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.project-planning"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package projectplanning
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "project-planning",
|
||||
Label: "项目规划",
|
||||
PlanTitle: "项目规划流程",
|
||||
PlanSteps: []string{"明确目标边界", "拆解阶段路径", "整理里程碑", "补齐风险与依赖"},
|
||||
ResultTitle: "项目规划草案",
|
||||
Summary: "已把目标拆成阶段计划、里程碑和风险项,方便直接进入执行或评审。",
|
||||
Highlights: []string{"适合启动、排期和推进对齐", "默认沉淀阶段计划与风险清单", "结果适合继续流转到任务治理"},
|
||||
ActionType: "plan",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package proposalsummary
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "proposal-summary",
|
||||
Label: "方案摘要",
|
||||
OwnedDefinitionKeys: []string{"skill.proposal-summary.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.proposal-summary"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package proposalsummary
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "proposal-summary",
|
||||
Label: "方案摘要",
|
||||
PlanTitle: "方案摘要流程",
|
||||
PlanSteps: []string{"识别方案目标", "提炼核心亮点", "压缩实施路径", "输出摘要版本"},
|
||||
ResultTitle: "方案摘要稿",
|
||||
Summary: "已提炼出方案核心结论、价值亮点和实施建议,适合快速汇报或同步。",
|
||||
Highlights: []string{"适合方案汇报和管理层预读", "自动压缩价值点与实施路径", "默认沉淀摘要正文和亮点清单"},
|
||||
ActionType: "summary",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package reportgeneration
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "report-generation",
|
||||
Label: "报告生成",
|
||||
OwnedDefinitionKeys: []string{"skill.report-generation.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.report-generation"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
package reportgeneration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"eai_agentplatform/backend/internal/ai"
|
||||
"eai_agentplatform/backend/internal/config"
|
||||
"eai_agentplatform/backend/internal/model"
|
||||
"eai_agentplatform/backend/internal/store"
|
||||
)
|
||||
|
||||
// ReportGenRequest 报告生成请求
|
||||
type ReportGenRequest struct {
|
||||
Topic string `json:"topic"` // 报告主题,必填
|
||||
Summary string `json:"summary"` // 报告摘要/背景
|
||||
Context map[string]any `json:"context"` // 附加上下文
|
||||
Sections []string `json:"sections"` // 期望包含的章节(可选)
|
||||
Knowledge string `json:"knowledge_key"` // 关联知识库 key
|
||||
}
|
||||
|
||||
// ReportChapter 报告章节结构
|
||||
type ReportChapter struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// ReportContent 完整报告结构
|
||||
type ReportContent struct {
|
||||
Topic string `json:"topic"`
|
||||
Summary string `json:"summary"`
|
||||
Chapters []ReportChapter `json:"chapters"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// RetrieveReportKnowledge 为报告生成检索知识库片段
|
||||
func RetrieveReportKnowledge(topic string, spaceKey string) string {
|
||||
if spaceKey != "" {
|
||||
// 按空间检索
|
||||
var space model.KnowledgeSpace
|
||||
if err := store.DB.Where("key = ?", spaceKey).First(&space).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
var chunks []model.KnowledgeChunk
|
||||
store.DB.Where("space_id = ? AND approved = ?", space.ID, true).
|
||||
Order("importance DESC").
|
||||
Limit(10).
|
||||
Find(&chunks)
|
||||
parts := make([]string, 0, len(chunks))
|
||||
for _, ch := range chunks {
|
||||
parts = append(parts, ch.Content)
|
||||
}
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
|
||||
// 全局检索(按 title 匹配)
|
||||
var chunks []model.KnowledgeChunk
|
||||
store.DB.Where("approved = ?", true).
|
||||
Order("importance DESC").
|
||||
Limit(15).
|
||||
Find(&chunks)
|
||||
parts := make([]string, 0, len(chunks))
|
||||
for _, ch := range chunks {
|
||||
parts = append(parts, ch.Content)
|
||||
}
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
|
||||
// PromptReportWithSections 使用用户指定章节生成报告
|
||||
func PromptReportWithSections(req ReportGenRequest, knowledge string) []ReportChapter {
|
||||
sectionsStr := strings.Join(req.Sections, "、")
|
||||
systemPrompt := `你是 EAI 数字员工平台的报告生成引擎。你的任务是输出一份结构化的正式报告,语言为中文。`
|
||||
userPrompt := fmt.Sprintf(`请围绕以下主题撰写一份正式报告。
|
||||
|
||||
主题:%s
|
||||
|
||||
报告要求:
|
||||
1. 内容完整、逻辑清晰、避免空话
|
||||
2. 使用正式的书面语言,适合内部汇报或培训
|
||||
3. 报告必须包含以下章节(按顺序):%s
|
||||
4. 每个章节下至少 3-5 个要点
|
||||
5. 如有知识库资料可引用,请融入内容中
|
||||
|
||||
当前报告摘要/背景:%s
|
||||
|
||||
知识库相关资料(如有):
|
||||
%s`,
|
||||
req.Topic,
|
||||
sectionsStr,
|
||||
strings.TrimSpace(req.Summary),
|
||||
knowledge,
|
||||
)
|
||||
|
||||
aiRoute, _ := config.GetRoute("title_gen")
|
||||
aiContent, err := ai.GenerateWithFallback(aiRoute, []ai.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: userPrompt},
|
||||
})
|
||||
if err != nil {
|
||||
return fallbackReport(req.Topic)
|
||||
}
|
||||
|
||||
return parseReportMarkdown(aiContent, req.Sections)
|
||||
}
|
||||
|
||||
// PromptReportAutoChapters 让 LLM 自动规划报告章节
|
||||
func PromptReportAutoChapters(req ReportGenRequest, knowledge string) []ReportChapter {
|
||||
systemPrompt := `你是 EAI 数字员工平台的报告生成引擎。先规划报告章节,再撰写内容。输出必须严格为 JSON 数组,不要输出其他内容。`
|
||||
userPrompt := fmt.Sprintf(`请围绕以下主题撰写一份正式报告。
|
||||
|
||||
主题:%s
|
||||
|
||||
报告要求:
|
||||
1. 先规划 5-8 个合适的章节
|
||||
2. 每个章节包含:标题(title)和正文(body)
|
||||
3. 正文不少于 300 字
|
||||
4. 使用正式的书面语言
|
||||
|
||||
当前报告摘要/背景:%s
|
||||
|
||||
知识库相关资料(如有):
|
||||
%s
|
||||
|
||||
请输出 JSON 格式,每个条目包含 "title" 和 "body" 两个字段。`,
|
||||
req.Topic,
|
||||
strings.TrimSpace(req.Summary),
|
||||
knowledge,
|
||||
)
|
||||
|
||||
aiRoute, _ := config.GetRoute("title_gen")
|
||||
aiContent, err := ai.GenerateWithFallback(aiRoute, []ai.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: userPrompt},
|
||||
})
|
||||
if err != nil {
|
||||
return fallbackReport(req.Topic)
|
||||
}
|
||||
|
||||
return parseReportJSON(aiContent)
|
||||
}
|
||||
|
||||
// parseReportMarkdown 从 Markdown 格式解析报告为章节数组
|
||||
func parseReportMarkdown(content string, defaultSections []string) []ReportChapter {
|
||||
if len(defaultSections) == 0 {
|
||||
defaultSections = []string{"背景概述", "核心内容", "关键数据", "实施方案", "总结建议"}
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
chapters := make([]ReportChapter, 0, len(defaultSections))
|
||||
currentTitle := ""
|
||||
currentBody := ""
|
||||
|
||||
isChapterTitle := func(line string) bool {
|
||||
if strings.HasPrefix(line, "# ") || strings.HasPrefix(line, "## ") {
|
||||
return true
|
||||
}
|
||||
runes := []rune(line)
|
||||
if len(runes) >= 2 {
|
||||
// 数字序号:1、 1. 一、
|
||||
if (runes[0] >= '0' && runes[0] <= '9') && (runes[1] == '、' || runes[1] == '.') {
|
||||
return true
|
||||
}
|
||||
if runes[0] == '一' && runes[1] == '、' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// 短标题带冒号(排除 URL)
|
||||
if len(runes) < 30 && len(runes) > 3 && strings.Contains(line, ":") && !strings.Contains(line, "https") {
|
||||
// 排除 markdown 行内代码
|
||||
if !strings.Contains(line, "```") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "---") {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(chapters) < 10 && isChapterTitle(line) {
|
||||
if currentTitle != "" && currentBody != "" {
|
||||
chapters = append(chapters, ReportChapter{
|
||||
Title: strings.TrimSpace(currentTitle),
|
||||
Body: strings.TrimSpace(currentBody),
|
||||
})
|
||||
}
|
||||
currentTitle = strings.TrimLeft(line, "# \t")
|
||||
currentTitle = strings.TrimRight(currentTitle, " ")
|
||||
currentBody = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if currentTitle != "" {
|
||||
currentBody += line + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if currentTitle != "" && currentBody != "" {
|
||||
chapters = append(chapters, ReportChapter{
|
||||
Title: strings.TrimSpace(currentTitle),
|
||||
Body: strings.TrimSpace(currentBody),
|
||||
})
|
||||
}
|
||||
|
||||
if len(chapters) == 0 {
|
||||
for _, title := range defaultSections {
|
||||
chapters = append(chapters, ReportChapter{
|
||||
Title: title,
|
||||
Body: content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(chapters) > 15 {
|
||||
chapters = chapters[:15]
|
||||
}
|
||||
|
||||
return chapters
|
||||
}
|
||||
|
||||
// parseReportJSON 从 LLM 返回的 JSON 解析报告
|
||||
func parseReportJSON(content string) []ReportChapter {
|
||||
// 提取 JSON 块
|
||||
jsonStr := content
|
||||
if idx := strings.Index(content, "["); idx >= 0 {
|
||||
jsonStr = content[idx:]
|
||||
}
|
||||
if idx := strings.LastIndex(jsonStr, "]"); idx >= 0 {
|
||||
jsonStr = jsonStr[:idx+1]
|
||||
}
|
||||
|
||||
var chapters []ReportChapter
|
||||
if err := json.Unmarshal([]byte(jsonStr), &chapters); err == nil && len(chapters) > 0 {
|
||||
return chapters
|
||||
}
|
||||
|
||||
// 提取数组对象块
|
||||
start := strings.Index(content, "{")
|
||||
end := strings.LastIndex(content, "}")
|
||||
if start >= 0 && end > start {
|
||||
jsonStr2 := content[start : end+1]
|
||||
if err := json.Unmarshal([]byte(jsonStr2), &chapters); err == nil && len(chapters) > 0 {
|
||||
return chapters
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:生成默认章节
|
||||
return fallbackReport("")
|
||||
}
|
||||
|
||||
// fallbackReport 无 LLM 时的默认报告模板
|
||||
func fallbackReport(topic string) []ReportChapter {
|
||||
title := topic
|
||||
if title == "" {
|
||||
title = "分析报告"
|
||||
}
|
||||
return []ReportChapter{
|
||||
{
|
||||
Title: "一、背景概述",
|
||||
Body: fmt.Sprintf("本报告围绕「%s」主题展开,旨在梳理相关背景信息与核心要点,为内部学习与汇报提供参考。", title),
|
||||
},
|
||||
{
|
||||
Title: "二、核心内容",
|
||||
Body: fmt.Sprintf("在「%s」领域,当前主流实践聚焦于以下几个维度:知识体系构建、流程标准化、工具赋能与效果评估。通过系统化的培训与考核机制,帮助团队提升专业能力。", title),
|
||||
},
|
||||
{
|
||||
Title: "三、关键数据",
|
||||
Body: "关键指标包括:产品覆盖率、培训完成率、考试通过率、知识入库率、AI 使用频次等。具体数据需根据实际运营情况定期更新。",
|
||||
},
|
||||
{
|
||||
Title: "四、实施方案",
|
||||
Body: "建议分阶段推进:第一阶段建立知识体系与培训框架;第二阶段完善考核与评估机制;第三阶段引入 AI 辅助工具提升效率;第四阶段持续优化与迭代。",
|
||||
},
|
||||
{
|
||||
Title: "五、总结建议",
|
||||
Body: fmt.Sprintf("整体而言,「%s」是当前业务发展的核心方向之一。建议持续投入资源,建立系统化培训体系,结合 AI 工具提升效率,确保团队能力与业务发展同步。", title),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PromptReportPreview 预览模式:返回精简报告
|
||||
func PromptReportPreview(req ReportGenRequest) ReportContent {
|
||||
var chapters []ReportChapter
|
||||
if len(req.Sections) > 0 {
|
||||
chapters = PromptReportWithSections(req, RetrieveReportKnowledge(req.Topic, req.Knowledge))
|
||||
} else {
|
||||
chapters = PromptReportAutoChapters(req, RetrieveReportKnowledge(req.Topic, req.Knowledge))
|
||||
}
|
||||
return ReportContent{
|
||||
Topic: req.Topic,
|
||||
Summary: strings.TrimSpace(req.Summary),
|
||||
Chapters: chapters,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
package reportgeneration
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jung-kurt/gofpdf/v2"
|
||||
)
|
||||
|
||||
// ExportReportPDF 导出报告为 PDF 字节
|
||||
func ExportReportPDF(report ReportContent) ([]byte, error) {
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
|
||||
fontLoaded := false
|
||||
for _, candidate := range []string{
|
||||
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
||||
"/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc",
|
||||
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
|
||||
} {
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
pdf.AddUTF8Font("czk", "", candidate)
|
||||
fontLoaded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pdf.AddPage()
|
||||
|
||||
// 标题
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "B", 20)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "B", 20)
|
||||
}
|
||||
pdf.CellFormat(170, 12, report.Topic, "", 1, "C", false, 0, "")
|
||||
pdf.Ln(8)
|
||||
|
||||
// 摘要
|
||||
if strings.TrimSpace(report.Summary) != "" {
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "", 10)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
}
|
||||
pdf.MultiCell(170, 5, report.Summary, "", "L", false)
|
||||
pdf.Ln(6)
|
||||
}
|
||||
|
||||
// 目录
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "B", 14)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
}
|
||||
pdf.Cell(0, 8, "目录")
|
||||
pdf.Ln(4)
|
||||
for _, ch := range report.Chapters {
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "", 10)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
}
|
||||
pdf.Cell(0, 6, " "+ch.Title)
|
||||
pdf.Ln(5)
|
||||
}
|
||||
pdf.Ln(6)
|
||||
|
||||
// 章节
|
||||
for _, ch := range report.Chapters {
|
||||
pdf.AddPage()
|
||||
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "B", 14)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
}
|
||||
pdf.Cell(0, 8, ch.Title)
|
||||
pdf.Ln(2)
|
||||
|
||||
if fontLoaded {
|
||||
pdf.SetFont("czk", "", 10)
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
}
|
||||
pdf.MultiCell(170, 5, ch.Body, "", "L", false)
|
||||
pdf.Ln(6)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pdf.Output(&buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// zipWriteFile helper: create a file in zip and write content
|
||||
func zipWriteFile(zw *zip.Writer, name, content string) error {
|
||||
f, err := zw.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write([]byte(content))
|
||||
return err
|
||||
}
|
||||
|
||||
// escapeXML escapes HTML/XML special characters
|
||||
func escapeXML(s string) string {
|
||||
s = strings.ReplaceAll(s, "&", "&")
|
||||
s = strings.ReplaceAll(s, "<", "<")
|
||||
s = strings.ReplaceAll(s, ">", ">")
|
||||
s = strings.ReplaceAll(s, "'", "'")
|
||||
s = strings.ReplaceAll(s, `"`, """)
|
||||
return s
|
||||
}
|
||||
|
||||
// timeStr returns a fixed string for pptx sldId
|
||||
func timeStr() string {
|
||||
return "20060102T150405"
|
||||
}
|
||||
|
||||
func ExportReportDOCX(report ReportContent) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
zipWriteFile(zw, "[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>`)
|
||||
zipWriteFile(zw, "_rels/.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`)
|
||||
var doc strings.Builder
|
||||
doc.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` + `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>`)
|
||||
doc.WriteString(`<w:p><w:pPr><w:pStyle w:val="Title"/></w:pPr><w:r><w:rPr><w:b/><w:sz w:val="48"/></w:rPr><w:t>` + escapeXML(report.Topic) + `</w:t></w:r></w:p>`)
|
||||
doc.WriteString(`<w:p><w:r><w:rPr><w:b/><w:sz w:val="24"/></w:rPr><w:t>摘要</w:t></w:r></w:p>`)
|
||||
doc.WriteString(`<w:p><w:r><w:rPr><w:sz w:val="24"/></w:rPr><w:t>` + escapeXML(report.Summary) + `</w:t></w:r></w:p>`)
|
||||
for _, ch := range report.Chapters {
|
||||
doc.WriteString(`<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:rPr><w:b/><w:sz w:val="28"/></w:rPr><w:t>` + escapeXML(ch.Title) + `</w:t></w:r></w:p>`)
|
||||
doc.WriteString(`<w:p><w:r><w:rPr><w:sz w:val="22"/></w:rPr><w:t>` + escapeXML(ch.Body) + `</w:t></w:r></w:p>`)
|
||||
}
|
||||
doc.WriteString(`</w:body></w:document>`)
|
||||
zipWriteFile(zw, "word/document.xml", doc.String())
|
||||
zipWriteFile(zw, "word/styles.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styles xmlns="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><style type="paragraph" styleId="Title"><name val="Title"/><uiPriority uiPriority="9"/><rPr><rStyle val="Title"/><rFonts w:ascii="Arial" w:hAnsi="Arial"/><b/><sz w:val="48"/></rPr></style><style type="paragraph" styleId="Heading1"><name val="Heading 1"/><rPr><rStyle val="Heading1"/><rFonts w:ascii="Arial" w:hAnsi="Arial"/><b/><sz w:val="28"/></rPr></style></styles>`)
|
||||
zipWriteFile(zw, "word/_rels/document.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`)
|
||||
zipWriteFile(zw, "docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>`+escapeXML(report.Topic)+`</dc:title></cp:coreProperties>`)
|
||||
zipWriteFile(zw, "docProps/app.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Microsoft Word</Application></Properties>`)
|
||||
zw.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func ExportReportPPTX(report ReportContent) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
layoutRefs := make([]string, 0)
|
||||
for i := 0; i < len(report.Chapters)+1; i++ {
|
||||
layoutRefs = append(layoutRefs, "rId"+fmt.Sprint(i+1))
|
||||
}
|
||||
slideRels := strings.Join(layoutRefs, "")
|
||||
|
||||
zipWriteFile(zw, "[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/><Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/><Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/></Types>`)
|
||||
zipWriteFile(zw, "_rels/.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/></Relationships>`)
|
||||
|
||||
preXML := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
||||
`<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" changedTime="` + timeStr() + `">` +
|
||||
`<p:sldMasterId href="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"/>` +
|
||||
`<p:sldIds>` + slideRels + `</p:sldIds></p:presentation>`
|
||||
zipWriteFile(zw, "ppt/presentation.xml", preXML)
|
||||
|
||||
zipWriteFile(zw, "ppt/_rels/presentation.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMaster/slideMaster1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayouts" Target="slideLayouts/slideLayout1.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/></Relationships>`)
|
||||
|
||||
titleSlide := buildSlideTitle(report.Topic, report.Summary)
|
||||
zipWriteFile(zw, "ppt/slides/slide1.xml", titleSlide)
|
||||
zipWriteFile(zw, "ppt/slides/_rels/slide1.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/></Relationships>`)
|
||||
|
||||
for i, ch := range report.Chapters {
|
||||
slideNum := i + 2
|
||||
slidePath := fmt.Sprintf("ppt/slides/slide%d.xml", slideNum)
|
||||
relnPath := fmt.Sprintf("ppt/slides/_rels/slide%d.xml.rels", slideNum)
|
||||
zipWriteFile(zw, slidePath, buildSlideChapter(ch.Title, ch.Body))
|
||||
zipWriteFile(zw, relnPath, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/></Relationships>`)
|
||||
}
|
||||
|
||||
zipWriteFile(zw, "ppt/slideLayouts/slideLayout1.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:sldLayout xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" type="title"><p:cSld><p:spTree><p:sp><p:nvSpPr><p:cNvPr id="1" name="标题"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="914400" y="228600"/><p:ext cx="7315200" cy="1219200"/></p:xfrm></p:spPr><p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/><a:style><a:lnSz w="914400" h="914400"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr alignment="ctr"/><a:r><a:rPr lang="zh-CN" sz="4400" dirty="0"/><a:t>标题</a:t></a:r><a:endParaRPr lang="zh-CN" sz="4400" dirty="0"/></a:p></a:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="2" name="内容"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="457200" y="1524000"/><p:ext cx="6400800" cy="5334000"/></p:xfrm></p:spPr><p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" wrap="square" anchor="ctr"/><a:style><a:lnSz w="76200" h="76200"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr><a:indent>-457200"/></a:pPr><a:r><a:rPr lang="zh-CN" sz="2400" dirty="0"/><a:t>内容</a:t></a:r><a:endParaRPr lang="zh-CN" sz="2400" dirty="0"/></a:p></a:txBody></p:sp></p:spTree></p:cSld></p:sldLayout>`)
|
||||
|
||||
zipWriteFile(zw, "ppt/theme/theme1.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Design Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="1F497D"/></a:dk2><a:lt2><a:srgbClr val="E8E8E8"/></a:lt2><a:accent1><a:srgbClr val="4472C4"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="4472C4"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="9563C1"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin fontFamily="Calibri" script="latn"/><a:ea script="hani"/><a:cs fontFamily="Cambria" script="latin"/></a:majorFont><a:minorFont><a:latin fontFamily="Calibri" script="latn"/><a:ea script="hani"/><a:cs fontFamily="Cambria" script="latin"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"/></a:themeElements></a:theme>`)
|
||||
|
||||
zipWriteFile(zw, "ppt/media/", "")
|
||||
zw.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func buildSlideTitle(title, summary string) string {
|
||||
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
||||
`<p:slide xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" p:sldId="` + timeStr() + `">` +
|
||||
`<p:cSld><p:spTree>` +
|
||||
`<p:sp><p:nvSpPr><p:cNvPr id="1" name="标题"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="914400" y="228600"/><p:ext cx="7315200" cy="1219200"/></p:xfrm></p:spPr>` +
|
||||
`<p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" anchor="ctr"/><a:style><a:lnSz w="914400" h="914400"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr alignment="ctr"/><a:r><a:rPr lang="zh-CN" sz="4400" dirty="0"/><a:rPr><a:spcBk><a:spcPct val="100000"/></a:spcBk></a:rPr><a:b/><a:t>` + escapeXML(title) + `</a:t></a:r><a:endParaRPr lang="zh-CN" sz="4400" dirty="0"/></a:p></a:txBody></p:sp>` +
|
||||
`<p:sp><p:nvSpPr><p:cNvPr id="2" name="摘要"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="457200" y="1524000"/><p:ext cx="6400800" cy="5334000"/></p:xfrm></p:spPr>` +
|
||||
`<p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" wrap="square" anchor="ctr"/><a:style><a:lnSz w="76200" h="76200"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr><a:indent>-457200"/></a:pPr><a:r><a:rPr lang="zh-CN" sz="2400" dirty="0"/><a:rPr><a:spcBk><a:spcPct val="100000"/></a:spcBk></a:rPr><a:t>` + escapeXML(summary) + `</a:t></a:r><a:endParaRPr lang="zh-CN" sz="2400" dirty="0"/></a:p></a:txBody></p:sp>` +
|
||||
`</p:spTree></p:cSld></p:slide>`
|
||||
}
|
||||
|
||||
func buildSlideChapter(title, body string) string {
|
||||
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
||||
`<p:slide xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" p:sldId="` + timeStr() + `">` +
|
||||
`<p:cSld><p:spTree>` +
|
||||
`<p:sp><p:nvSpPr><p:cNvPr id="1" name="标题"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="914400" y="228600"/><p:ext cx="7315200" cy="914400"/></p:xfrm></p:spPr>` +
|
||||
`<p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" anchor="ctr"/><a:style><a:lnSz w="914400" h="914400"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr/><a:r><a:rPr lang="zh-CN" sz="3200" dirty="0"/><a:rPr><a:spcBk><a:spcPct val="100000"/></a:spcBk></a:rPr><a:b/><a:t>` + escapeXML(title) + `</a:t></a:r><a:endParaRPr lang="zh-CN" sz="3200" dirty="0"/></a:p></a:txBody></p:sp>` +
|
||||
`<p:sp><p:nvSpPr><p:cNvPr id="2" name="内容"/><p:cNvSpPr/></p:nvSpPr><p:spPr><p:xfrm><p:off x="457200" y="1219200"/><p:ext cx="6400800" cy="5791200"/></p:xfrm></p:spPr>` +
|
||||
`<p:txBody><a:bodyPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" wrap="square" anchor="ctr"/><a:style><a:lnSz w="76200" h="76200"/><a:effectLst/><a:spPr/><a:prstGeom prst="rect"/><a:fld/><a:p><a:pPr><a:indent>-457200"/></a:pPr><a:r><a:rPr lang="zh-CN" sz="2000" dirty="0"/><a:rPr><a:spcBk><a:spcPct val="100000"/></a:spcBk></a:rPr><a:t>` + escapeXML(body) + `</a:t></a:r><a:endParaRPr lang="zh-CN" sz="2000" dirty="0"/></a:p></a:txBody></p:sp>` +
|
||||
`</p:spTree></p:cSld></p:slide>`
|
||||
}
|
||||
|
||||
func ExportReportXLSX(report ReportContent) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
ss := []string{}
|
||||
addSS := func(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return len(ss)
|
||||
}
|
||||
ss = append(ss, s)
|
||||
return len(ss) - 1
|
||||
}
|
||||
addSS(report.Topic)
|
||||
addSS(report.Summary)
|
||||
for _, ch := range report.Chapters {
|
||||
addSS(ch.Title)
|
||||
addSS(ch.Body)
|
||||
}
|
||||
zipWriteFile(zw, "[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/></Types>`)
|
||||
zipWriteFile(zw, "_rels/.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`)
|
||||
zipWriteFile(zw, "xl/workbook.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><workbookPr date1904="false"/><bookViews><workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="14400"/></bookViews><sheets><sheet name="报告" sheetId="1" r:id="rId1"/></sheets></workbook>`)
|
||||
zipWriteFile(zw, "xl/_rels/workbook.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/></Relationships>`)
|
||||
var sb strings.Builder
|
||||
sb.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="` + fmt.Sprint(len(ss)) + `" uniqueCount="` + fmt.Sprint(len(ss)) + `">`)
|
||||
for _, s := range ss {
|
||||
sb.WriteString(`<si><t>` + escapeXML(s) + `</t></si>`)
|
||||
}
|
||||
sb.WriteString(`</sst>`)
|
||||
zipWriteFile(zw, "xl/sharedStrings.xml", sb.String())
|
||||
var sb2 strings.Builder
|
||||
sb2.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>`)
|
||||
row := 1
|
||||
sb2.WriteString(`<row r="` + fmt.Sprint(row) + `"><c r="A1" t="inlineStr"><is><t>` + escapeXML(report.Topic) + `</t></is></c></row>`)
|
||||
row = 2
|
||||
sb2.WriteString(`<row r="` + fmt.Sprint(row) + `"><c r="A2" t="inlineStr"><is><t>摘要</t></is></c><c r="B2" t="inlineStr"><is><t>` + escapeXML(report.Summary) + `</t></is></c></row>`)
|
||||
row = 3
|
||||
sb2.WriteString(`<row r="` + fmt.Sprint(row) + `"><c r="A3" t="inlineStr"><is><t>章节</t></is></c><c r="B3" t="inlineStr"><is><t>内容</t></is></c></row>`)
|
||||
for _, ch := range report.Chapters {
|
||||
row++
|
||||
sb2.WriteString(`<row r="` + fmt.Sprint(row) + `"><c r="A` + fmt.Sprint(row) + `" t="inlineStr"><is><t>` + escapeXML(ch.Title) + `</t></is></c><c r="B` + fmt.Sprint(row) + `" t="inlineStr"><is><t>` + escapeXML(ch.Body) + `</t></is></c></row>`)
|
||||
}
|
||||
sb2.WriteString(`</sheetData></worksheet>`)
|
||||
zipWriteFile(zw, "xl/worksheets/sheet1.xml", sb2.String())
|
||||
zw.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package smartassistant
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "smart-assistant",
|
||||
Label: "通用助手",
|
||||
OwnedDefinitionKeys: []string{"skill.smart-assistant.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.smart-assistant"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package surveysummary
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "survey-summary",
|
||||
Label: "表单问卷汇总",
|
||||
OwnedDefinitionKeys: []string{"skill.survey-summary.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.survey-summary"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package surveysummary
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "survey-summary",
|
||||
Label: "表单问卷汇总",
|
||||
PlanTitle: "问卷汇总流程",
|
||||
PlanSteps: []string{"归并表单结果", "提炼重点发现", "整理问题与建议", "输出汇总版本"},
|
||||
ResultTitle: "问卷汇总稿",
|
||||
Summary: "已整理出表单结果汇总、重点发现和改进建议,方便直接同步给负责人。",
|
||||
Highlights: []string{"适合反馈表、报名表、调查问卷", "自动压缩重点发现和改进建议", "默认沉淀汇总报告和行动建议"},
|
||||
ActionType: "report",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package tablecleanup
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "table-cleanup",
|
||||
Label: "表格整理",
|
||||
OwnedDefinitionKeys: []string{"skill.table-cleanup.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.table-cleanup"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tablecleanup
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "table-cleanup",
|
||||
Label: "表格整理",
|
||||
PlanTitle: "表格整理流程",
|
||||
PlanSteps: []string{"识别字段结构", "清洗原始数据", "整理表格内容", "补充字段说明"},
|
||||
ResultTitle: "表格整理结果",
|
||||
Summary: "已把输入内容整理成结构化表格,并补了字段说明和清洗备注。",
|
||||
Highlights: []string{"适合名单、台账、记录整理", "默认沉淀表格正文和字段说明", "便于继续导入 Excel 或系统"},
|
||||
ActionType: "table",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package texttoolkit
|
||||
|
||||
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
|
||||
|
||||
var Manifest = skillcontracts.Manifest{
|
||||
Key: "text-toolkit",
|
||||
Label: "文本处理",
|
||||
OwnedDefinitionKeys: []string{"skill.text-toolkit.definition"},
|
||||
OwnedCatalogEntries: []string{"catalog.skill.text-toolkit"},
|
||||
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package texttoolkit
|
||||
|
||||
import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
|
||||
|
||||
var OfficeRuntime = officecontracts.Runtime{
|
||||
Key: "text-toolkit",
|
||||
Label: "文本处理",
|
||||
PlanTitle: "文本处理流程",
|
||||
PlanSteps: []string{"识别清洗目标", "去除噪音符号", "统一格式与标点", "输出可复用文本"},
|
||||
ResultTitle: "清洗后文本",
|
||||
Summary: "已完成文本清洗、格式整理和统计,可直接复制或下载结果。",
|
||||
Highlights: []string{"去 Markdown 噪音", "输出文本统计", "适合高频复制到 Word 和 IM"},
|
||||
ActionType: "text",
|
||||
}
|
||||
Reference in New Issue
Block a user