refactor: 收口技能与专员目录定义

统一 skill 与 specialist 清单,移除旧的 office 执行包与下线对象,并补入 text_to_speech、customer_followup 等当前目录定义与种子数据。
This commit is contained in:
eaiadmin
2026-09-22 23:38:56 +08:00
parent 90031b75f3
commit b7238ad667
148 changed files with 1230 additions and 1710 deletions
@@ -199,7 +199,7 @@ func ExecuteOfficeSkillOCR(c *gin.Context) {
TaskID: task.ID,
SpecialistKey: task.SpecialistKey,
ActionKey: "ocr-understanding",
ActionTitle: "OCR / 图文理解",
ActionTitle: "图片/扫描件智能识别",
ActionType: "ocr",
Status: "done",
InputJSON: string(inputJSON),
@@ -0,0 +1,192 @@
package skillapi
import (
"crypto/sha1"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"eai_agentplatform/backend/internal/config"
"eai_agentplatform/backend/internal/middleware"
"eai_agentplatform/backend/internal/model"
specialistruntime "eai_agentplatform/backend/internal/specialists/runtime"
"eai_agentplatform/backend/internal/store"
"eai_agentplatform/backend/internal/web"
)
// ttsDefaultVoice 默认中文女声(edge-tts 自带音色)。
const ttsDefaultVoice = "zh-CN-XiaoxiaoNeural"
// ttsAudioDir 存放合成 MP3 的目录,与公众号配图一致放在数据库同级的 tts_audio 下。
func ttsAudioDir() string {
cfg := config.Load()
return filepath.Join(filepath.Dir(cfg.DBPath), "tts_audio")
}
func defaultTTSText(text string) string {
text = strings.TrimSpace(text)
if text == "" {
return "这是一个文字转语音的示例。"
}
return text
}
// ExecuteOfficeSkillTTS POST /api/skills/office/tts —— 文字转语音。
// body: { "task_id": <任务ID>, "text": "要朗读的文字", "voice": "音色(可空)", "rate": "语速(可空, 如 +0% / -10%)" }
func ExecuteOfficeSkillTTS(c *gin.Context) {
user := middleware.CurrentUser(c)
if user == nil {
web.Fail(c, web.NewAuthError("未登录"))
return
}
var req struct {
TaskID uint `json:"task_id"`
Text string `json:"text"`
Voice string `json:"voice"`
Rate string `json:"rate"`
}
if err := c.ShouldBindJSON(&req); err != nil {
web.Fail(c, web.NewBadRequest("请求参数错误"))
return
}
if req.TaskID == 0 {
web.Fail(c, web.NewBadRequest("task_id 必填"))
return
}
text := defaultTTSText(req.Text)
voice := strings.TrimSpace(req.Voice)
if voice == "" {
voice = ttsDefaultVoice
}
rate := strings.TrimSpace(req.Rate)
task, ok := loadMyOwnedTask(c, user, req.TaskID)
if !ok {
return
}
now := time.Now()
dir := ttsAudioDir()
if err := os.MkdirAll(dir, 0o755); err != nil {
web.Fail(c, web.NewBadRequest("无法创建音频目录"))
return
}
sum := sha1.Sum([]byte(text + "|" + voice))
filename := fmt.Sprintf("task_%d_%s_%x.mp3", task.ID, now.Format("20060102-150405"), sum[:6])
audioPath := filepath.Join(dir, filename)
args := []string{"-m", "edge_tts", "--text", text, "--voice", voice, "--write-media", audioPath}
if rate != "" {
args = append(args, "--rate", rate)
}
cmd := exec.Command("python3", args...)
var stderr strings.Builder
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
web.Fail(c, web.NewBadRequest("语音合成失败:"+firstNonEmpty(strings.TrimSpace(stderr.String()), err.Error())))
return
}
if _, err := os.Stat(audioPath); err != nil {
web.Fail(c, web.NewBadRequest("语音合成失败:未生成音频文件"))
return
}
audioURL := "/api/tts/audio/" + filename
inputJSON, _ := json.Marshal(gin.H{"text": text, "voice": voice, "rate": rate})
outputJSON, _ := json.Marshal(gin.H{
"summary": "已将文本合成为语音,可在线播放或下载。",
"text": text,
"voice": voice,
"audio_url": audioURL,
})
logsJSON, _ := json.Marshal([]string{
"接收文字转语音请求",
fmt.Sprintf("调用本地 TTS 引擎(voice=%s)", voice),
"已生成 MP3 音频",
})
artifactPayloadJSON, _ := json.Marshal(gin.H{
"text": text,
"voice": voice,
"audio_url": audioURL,
"format": "mp3",
})
sourceRefsJSON, _ := json.Marshal([]string{"本地 edge-tts"})
var run model.TaskRun
var artifact model.TaskArtifact
err := store.DB.Transaction(func(tx *gorm.DB) error {
run = model.TaskRun{
TaskID: task.ID,
SpecialistKey: task.SpecialistKey,
ActionKey: "text-to-speech",
ActionTitle: "文字转语音",
ActionType: "audio",
Status: "done",
InputJSON: string(inputJSON),
OutputJSON: string(outputJSON),
LogsJSON: string(logsJSON),
StartedAt: now,
FinishedAt: &now,
}
if err := tx.Create(&run).Error; err != nil {
return err
}
artifact = model.TaskArtifact{
TaskID: task.ID,
SpecialistKey: task.SpecialistKey,
Title: fmt.Sprintf("%s - 语音合成结果", task.Title),
ArtifactType: "audio",
Status: specialistruntime.ArtifactStatusDraft,
ContentText: text,
ContentJSON: string(artifactPayloadJSON),
SourceRefsJSON: string(sourceRefsJSON),
CreatedByRunID: &run.ID,
}
if err := tx.Create(&artifact).Error; err != nil {
return err
}
task.CurrentRunID = &run.ID
task.CurrentResult = "已将文本合成为语音并生成 MP3。"
task.Status = specialistruntime.TaskStatusDraft
task.LastTriggeredAt = &now
return tx.Save(&task).Error
})
if err != nil {
web.Fail(c, web.NewBadRequest("保存结果失败:"+err.Error()))
return
}
freshTask, _ := reloadTask(task.ID)
web.OK(c, gin.H{
"audio_url": audioURL,
"text": text,
"voice": voice,
"rate": rate,
"run": run,
"artifact": artifact,
"task": freshTask,
})
}
// ServeTTSAudio GET /api/tts/audio/:filename —— 提供生成的 MP3 音频文件。
func ServeTTSAudio(c *gin.Context) {
filename := filepath.Base(strings.TrimSpace(c.Param("filename")))
if filename == "." || filename == "" {
web.Fail(c, web.NewNotFoundError("音频不存在"))
return
}
fullPath := filepath.Join(ttsAudioDir(), filename)
if _, err := os.Stat(fullPath); err != nil {
web.Fail(c, web.NewNotFoundError("音频不存在"))
return
}
c.File(fullPath)
}
@@ -7,7 +7,6 @@ import (
contractreview "eai_agentplatform/backend/internal/skills/packages/contract_review"
copyproofreading "eai_agentplatform/backend/internal/skills/packages/copy_proofreading"
documenttranslate "eai_agentplatform/backend/internal/skills/packages/document_translate"
emaildrafting "eai_agentplatform/backend/internal/skills/packages/email_drafting"
interviewsummary "eai_agentplatform/backend/internal/skills/packages/interview_summary"
longformwriting "eai_agentplatform/backend/internal/skills/packages/longform_writing"
meetingminutes "eai_agentplatform/backend/internal/skills/packages/meeting_minutes"
@@ -16,13 +15,10 @@ import (
policyrewrite "eai_agentplatform/backend/internal/skills/packages/policy_rewrite"
pptgeneration "eai_agentplatform/backend/internal/skills/packages/ppt_generation"
progressreport "eai_agentplatform/backend/internal/skills/packages/progress_report"
projectplanning "eai_agentplatform/backend/internal/skills/packages/project_planning"
proposalsummary "eai_agentplatform/backend/internal/skills/packages/proposal_summary"
reportgeneration "eai_agentplatform/backend/internal/skills/packages/report_generation"
smartassistant "eai_agentplatform/backend/internal/skills/packages/smart_assistant"
surveysummary "eai_agentplatform/backend/internal/skills/packages/survey_summary"
tablecleanup "eai_agentplatform/backend/internal/skills/packages/table_cleanup"
texttoolkit "eai_agentplatform/backend/internal/skills/packages/text_toolkit"
texttospeech "eai_agentplatform/backend/internal/skills/packages/text_to_speech"
)
var builtinRegistry = NewRegistry(
@@ -36,18 +32,14 @@ var builtinRegistry = NewRegistry(
pptgeneration.Manifest,
mindmap.Manifest,
longformwriting.Manifest,
texttoolkit.Manifest,
ocrunderstanding.Manifest,
meetingminutes.Manifest,
projectplanning.Manifest,
emaildrafting.Manifest,
tablecleanup.Manifest,
proposalsummary.Manifest,
progressreport.Manifest,
contractbrief.Manifest,
interviewsummary.Manifest,
policyrewrite.Manifest,
surveysummary.Manifest,
texttospeech.Manifest,
)
func BuiltinRegistry() *Registry {
@@ -11,8 +11,10 @@ type SkillDefinition struct {
EAILogicCode string `gorm:"column:eailogic_code;size:32;default:'';index" json:"eailogic_code"`
Description string `gorm:"type:text" json:"description"`
ObjectKind string `gorm:"column:object_kind;size:32;not null;default:skill;index" json:"object_kind"`
Source string `gorm:"size:32;not null;default:eai;index" json:"source"`
ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;default:''" json:"object_entry_route"`
Source string `gorm:"size:32;not null;default:eai;index" json:"source"`
ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;default:''" json:"object_entry_route"` // 业务对象进入入口(D26 口径)
Color string `gorm:"size:16;default:''" json:"color"` // 展示主色(面向用户展示层,唯一的真值在后端)
InteractionCardJSON string `gorm:"column:interaction_card_json;type:text" json:"interaction_card_json"` // 交互卡对象(JSON,snake_case 与专员一致)
// 这里**有意不写** `default:true`。带上它,GORM 在 Create 时会跳过布尔零值字段
// (false 被当成「没填」),改由库默认值 true 生效 —— 于是对象显式设的 false
// 被持久层改写成 true,创建接口永远设不出「不暴露给用户」的技能。
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "audio-transcribe",
Label: "语音转写",
Label: "会议音频分析(转写)",
OwnedDefinitionKeys: []string{"skill.audio-transcribe.definition"},
OwnedCatalogEntries: []string{"catalog.skill.audio-transcribe"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "batch-extract",
Label: "批量提取",
Label: "批量字段提取 → Excel",
OwnedDefinitionKeys: []string{"skill.batch-extract.definition"},
OwnedCatalogEntries: []string{"catalog.skill.batch-extract"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "contract-brief",
Label: "合同要点提炼",
Label: "合同批量审查与提取(提取)",
OwnedDefinitionKeys: []string{"skill.contract-brief.definition"},
OwnedCatalogEntries: []string{"catalog.skill.contract-brief"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "contract-brief",
Label: "合同要点提炼",
Label: "合同批量审查与提取(提取)",
PlanTitle: "合同要点提炼流程",
PlanSteps: []string{"识别合同结构", "提炼核心条款", "标记风险事项", "输出摘要版本"},
ResultTitle: "合同要点摘要",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "contract-review",
Label: "合同审查",
Label: "合同批量审查与提取(审查)",
OwnedDefinitionKeys: []string{"skill.contract-review.definition"},
OwnedCatalogEntries: []string{"catalog.skill.contract-review"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -1,11 +0,0 @@
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,
}
@@ -1,14 +0,0 @@
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",
}
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "interview-summary",
Label: "访谈整理",
Label: "招聘简历助手",
OwnedDefinitionKeys: []string{"skill.interview-summary.definition"},
OwnedCatalogEntries: []string{"catalog.skill.interview-summary"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "interview-summary",
Label: "访谈整理",
Label: "招聘简历助手",
PlanTitle: "访谈整理流程",
PlanSteps: []string{"归并访谈内容", "提炼主题观点", "总结关键洞察", "输出整理版本"},
ResultTitle: "访谈整理稿",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "longform-writing",
Label: "长文创作",
Label: "公文辅助写作(长文)",
OwnedDefinitionKeys: []string{"skill.longform-writing.definition"},
OwnedCatalogEntries: []string{"catalog.skill.longform-writing"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "longform-writing",
Label: "长文创作",
Label: "公文辅助写作(长文)",
PlanTitle: "长文创作流程",
PlanSteps: []string{"明确文体与受众", "规划章节大纲", "生成正文骨架", "整理摘要与交付稿"},
ResultTitle: "长文草稿",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "meeting-minutes",
Label: "会议纪要",
Label: "会议音频分析(纪要)",
OwnedDefinitionKeys: []string{"skill.meeting-minutes.definition"},
OwnedCatalogEntries: []string{"catalog.skill.meeting-minutes"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "meeting-minutes",
Label: "会议纪要",
Label: "会议音频分析(纪要)",
PlanTitle: "会议纪要整理流程",
PlanSteps: []string{"识别会议主题", "提炼关键结论", "整理行动项", "输出同步版本"},
ResultTitle: "会议纪要草稿",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "mind-map",
Label: "思维导图",
Label: "会议音频分析(思维导图)",
OwnedDefinitionKeys: []string{"skill.mind-map.definition"},
OwnedCatalogEntries: []string{"catalog.skill.mind-map"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "mind-map",
Label: "思维导图",
Label: "会议音频分析(思维导图)",
PlanTitle: "思维导图生成流程",
PlanSteps: []string{"识别主题主线", "拆分一级分支", "补齐关键节点", "整理导图源结构"},
ResultTitle: "导图结构草案",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "ocr-understanding",
Label: "OCR / 图文理解",
Label: "图片/扫描件智能识别",
OwnedDefinitionKeys: []string{"skill.ocr-understanding.definition"},
OwnedCatalogEntries: []string{"catalog.skill.ocr-understanding"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "ocr-understanding",
Label: "OCR / 图文理解",
Label: "图片/扫描件智能识别",
PlanTitle: "图文理解流程",
PlanSteps: []string{"识别图像内容", "抽取文字与字段", "整理结构摘要", "输出可复用结果"},
ResultTitle: "识别结果摘要",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "policy-rewrite",
Label: "制度改写",
Label: "公文辅助写作(制度)",
OwnedDefinitionKeys: []string{"skill.policy-rewrite.definition"},
OwnedCatalogEntries: []string{"catalog.skill.policy-rewrite"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "policy-rewrite",
Label: "制度改写",
Label: "公文辅助写作(制度)",
PlanTitle: "制度改写流程",
PlanSteps: []string{"识别制度目标", "统一结构口径", "重写正式正文", "补充修订说明"},
ResultTitle: "制度改写稿",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "ppt-generation",
Label: "PPT 生成",
Label: "一键生成 PPT",
OwnedDefinitionKeys: []string{"skill.ppt-generation.definition"},
OwnedCatalogEntries: []string{"catalog.skill.ppt-generation"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "ppt-generation",
Label: "PPT 生成",
Label: "一键生成 PPT",
PlanTitle: "PPT 生成流程",
PlanSteps: []string{"确认汇报目标", "规划页面结构", "提炼每页要点", "整理讲稿备注"},
ResultTitle: "演示文稿骨架",
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "progress-report",
Label: "周报 / 日报",
Label: "一键周报",
OwnedDefinitionKeys: []string{"skill.progress-report.definition"},
OwnedCatalogEntries: []string{"catalog.skill.progress-report"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "progress-report",
Label: "周报 / 日报",
Label: "一键周报",
PlanTitle: "进展汇报流程",
PlanSteps: []string{"归并本期工作", "提炼关键进展", "整理风险事项", "输出汇报版本"},
ResultTitle: "进展汇报草稿",
@@ -1,11 +0,0 @@
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,
}
@@ -1,14 +0,0 @@
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",
}
@@ -1,11 +0,0 @@
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,
}
@@ -1,14 +0,0 @@
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",
}
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "report-generation",
Label: "报告生成",
Label: "一键生成报告",
OwnedDefinitionKeys: []string{"skill.report-generation.definition"},
OwnedCatalogEntries: []string{"catalog.skill.report-generation"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "smart-assistant",
Label: "通用助手",
Label: "智能助手",
OwnedDefinitionKeys: []string{"skill.smart-assistant.definition"},
OwnedCatalogEntries: []string{"catalog.skill.smart-assistant"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -1,11 +0,0 @@
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,
}
@@ -1,14 +0,0 @@
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",
}
@@ -4,7 +4,7 @@ import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "table-cleanup",
Label: "表格整理",
Label: "知识库表格视图",
OwnedDefinitionKeys: []string{"skill.table-cleanup.definition"},
OwnedCatalogEntries: []string{"catalog.skill.table-cleanup"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
@@ -4,7 +4,7 @@ import officecontracts "eai_agentplatform/backend/internal/skills/runtime/office
var OfficeRuntime = officecontracts.Runtime{
Key: "table-cleanup",
Label: "表格整理",
Label: "知识库表格视图",
PlanTitle: "表格整理流程",
PlanSteps: []string{"识别字段结构", "清洗原始数据", "整理表格内容", "补充字段说明"},
ResultTitle: "表格整理结果",
@@ -0,0 +1,11 @@
package texttospeech
import skillcontracts "eai_agentplatform/backend/internal/skills/contracts"
var Manifest = skillcontracts.Manifest{
Key: "text-to-speech",
Label: "文字转语音",
OwnedDefinitionKeys: []string{"skill.text-to-speech.definition"},
OwnedCatalogEntries: []string{"catalog.skill.text-to-speech"},
ReferencePolicy: skillcontracts.SharedReferenceReadonlyHistory,
}
@@ -1,11 +0,0 @@
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,
}
@@ -1,14 +0,0 @@
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",
}
@@ -98,112 +98,6 @@ func BuildMeetingMinutesArtifacts(inputText string) []gin.H {
}
}
func BuildEmailDraftingArtifacts(inputText string) []gin.H {
fields, bodyText := parseOfficeFields(inputText, map[string][]string{
"recipient": {"收件人", "对象", "recipient", "to"},
"cc": {"抄送", "抄送人", "cc"},
"topic": {"主题", "标题", "topic", "subject"},
"purpose": {"目的", "意图", "purpose"},
"action": {"希望对方", "需要对方", "action"},
"deadline": {"时间", "截止时间", "deadline"},
"tone": {"语气", "风格", "tone", "style"},
})
topic := firstNonEmpty(fields["topic"], pickOfficeTopic(firstNonEmpty(bodyText, inputText), "邮件主题"))
recipient := firstNonEmpty(fields["recipient"], "各位")
ccList := splitSimpleList(fields["cc"])
purpose := firstNonEmpty(fields["purpose"], "同步背景和当前进展")
action := firstNonEmpty(fields["action"], "请协助确认并继续推进")
deadline := firstNonEmpty(fields["deadline"], "请尽快反馈")
tone := firstNonEmpty(fields["tone"], "正式、明确、礼貌")
points := extractOfficePoints(firstNonEmpty(bodyText, inputText), []string{"交代背景和当前情况", "说明希望对方配合或确认的事项", "给出时间点和下一步动作", "保持礼貌但结论清楚"})
subjectVariants := buildEmailSubjectVariants(topic, purpose)
subject := subjectVariants[0]
emailContent := strings.TrimSpace(strings.Join([]string{
"主题:" + subject,
"收件人:" + recipient,
"抄送:" + firstNonEmpty(strings.Join(ccList, "、"), "无"),
"",
recipient + "好,",
"",
purpose + "。" + firstNonEmpty(pointAt(points, 0), "这边同步一下当前事项的背景和进展。"),
action + "。" + firstNonEmpty(pointAt(points, 1), "目前需要大家协助确认相关事项。"),
deadline + "。" + firstNonEmpty(pointAt(points, 2), "如无问题,请按约定时间继续推进。"),
"",
"语气建议:" + tone + "。如有问题,请直接回复沟通。",
"",
"谢谢。",
}, "\n"))
shortContent := strings.TrimSpace(strings.Join([]string{
"【" + topic + "】",
firstNonEmpty(pointAt(points, 1), "请协助确认相关事项。"),
firstNonEmpty(pointAt(points, 2), "如无问题请按时推进。"),
}, "\n"))
briefContent := strings.TrimSpace(strings.Join([]string{
"# 邮件要点",
"",
"- 主题:" + subject,
"- " + firstNonEmpty(pointAt(points, 0), "交代背景和当前情况"),
"- " + firstNonEmpty(pointAt(points, 1), "说明希望对方配合或确认的事项"),
"- " + firstNonEmpty(pointAt(points, 2), "给出时间点和下一步动作"),
"- " + firstNonEmpty(pointAt(points, 3), "保持礼貌但结论清楚"),
}, "\n"))
subjectLines := []string{
"# 主题与抄送建议",
"",
"- 收件人:" + recipient,
"- 抄送建议:" + firstNonEmpty(strings.Join(ccList, "、"), "建议抄送直属负责人 / 相关协同方"),
"",
"## 可选主题",
}
for index, item := range subjectVariants {
subjectLines = append(subjectLines, fmt.Sprintf("%d. %s", index+1, item))
}
subjectContent := strings.TrimSpace(strings.Join(subjectLines, "\n"))
slug := officeSlug(topic, "email-drafting")
return []gin.H{
{
"key": "email",
"title": "正式邮件",
"description": "可直接复制到邮箱发送的完整版本。",
"kind": "text",
"fileFormat": "txt",
"downloadName": slug + ".txt",
"mimeType": "text/plain;charset=utf-8",
"content": emailContent,
},
{
"key": "short-followup",
"title": "简短跟进版",
"description": "适合发到 IM 或群里的短消息版本。",
"kind": "text",
"fileFormat": "txt",
"downloadName": slug + "-short.txt",
"mimeType": "text/plain;charset=utf-8",
"content": shortContent,
},
{
"key": "brief",
"title": "邮件要点",
"description": "方便审批或复核邮件口径的要点版。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-brief.md",
"mimeType": "text/markdown;charset=utf-8",
"content": briefContent,
},
{
"key": "subject-variants",
"title": "主题与抄送建议",
"description": "提供多版主题候选和抄送建议。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-subject.md",
"mimeType": "text/markdown;charset=utf-8",
"content": subjectContent,
},
}
}
func BuildProgressReportArtifacts(inputText string) []gin.H {
topic := pickOfficeTopic(inputText, "进展汇报")
points := extractOfficePoints(inputText, []string{"本期完成的关键工作", "当前风险和阻塞项", "需要协同支持的事项", "下一步计划"})
@@ -114,109 +114,6 @@ func BuildMindMapArtifacts(inputText string) []gin.H {
}
}
func BuildProjectPlanningArtifacts(inputText string) []gin.H {
fields, bodyText := parseOfficeFields(inputText, map[string][]string{
"topic": {"项目", "项目名称", "主题", "topic"},
"deadline": {"时间要求", "截止时间", "deadline"},
"owner": {"负责人", "owner"},
"goal": {"目标", "goal"},
"start": {"开始时间", "启动时间", "start"},
})
topic := firstNonEmpty(fields["topic"], pickOfficeTopic(firstNonEmpty(bodyText, inputText), "项目规划"))
deadline := firstNonEmpty(fields["deadline"], "待确认")
owner := firstNonEmpty(fields["owner"], "待指定")
goal := firstNonEmpty(fields["goal"], "先明确目标、阶段、依赖和协同方式")
start := firstNonEmpty(fields["start"], "尽快启动")
points := extractOfficePoints(firstNonEmpty(bodyText, inputText), []string{"明确目标、范围和成功标准", "梳理关键里程碑与交付节点", "安排执行分工与协同节奏", "识别风险与外部依赖"})
stageRows := buildProjectStageRows(points, owner, deadline)
planContent := strings.TrimSpace(strings.Join([]string{
"# " + topic,
"",
"## 项目目标",
fmt.Sprintf("围绕“%s”整理一版可执行的项目规划,先确保目标、阶段和依赖都清楚。", topic),
"- 负责人:" + owner,
"- 启动时间:" + start,
"- 时间要求:" + deadline,
"- 总体目标:" + goal,
"",
"## 阶段安排",
"### 阶段 1:启动与对齐",
"- " + firstNonEmpty(pointAt(points, 0), "明确目标和边界"),
"### 阶段 2:执行与推进",
"- " + firstNonEmpty(pointAt(points, 1), "推进关键里程碑和核心交付"),
"### 阶段 3:验收与收口",
"- " + firstNonEmpty(pointAt(points, 2), "完成验收、复盘和沉淀"),
}, "\n"))
milestoneContent := strings.TrimSpace(strings.Join([]string{
"# 里程碑清单",
"",
"1. M1:" + firstNonEmpty(pointAt(points, 0), "明确目标和边界"),
"2. M2:" + firstNonEmpty(pointAt(points, 1), "推进关键里程碑和核心交付"),
"3. M3:" + firstNonEmpty(pointAt(points, 2), "完成验收、复盘和沉淀"),
"4. M4:" + firstNonEmpty(pointAt(points, 3), "确认依赖和外部配合事项"),
}, "\n"))
riskContent := strings.TrimSpace(strings.Join([]string{
"# 风险与依赖",
"",
"- 风险 1:" + firstNonEmpty(pointAt(points, 3), "关键依赖尚未完全明确"),
"- 风险 2:跨团队协同节奏可能影响排期",
"- 建议:将关键依赖提前前置到周会或启动会确认",
}, "\n"))
scheduleLines := []string{
"# 阶段排期表",
"",
"| 阶段 | 时间窗 | 负责人 | 目标 | 交付物 |",
"| --- | --- | --- | --- | --- |",
}
for _, item := range stageRows {
scheduleLines = append(scheduleLines, fmt.Sprintf("| %s | %s | %s | %s | %s |", asString(item["stage"]), asString(item["window"]), asString(item["owner"]), asString(item["goal"]), asString(item["output"])))
}
scheduleContent := strings.TrimSpace(strings.Join(scheduleLines, "\n"))
slug := officeSlug(topic, "project-planning")
return []gin.H{
{
"key": "project-plan",
"title": "项目计划",
"description": "适合直接评审和继续细化的主计划。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + ".md",
"mimeType": "text/markdown;charset=utf-8",
"content": planContent,
},
{
"key": "milestones",
"title": "里程碑清单",
"description": "关键节点和交付节奏的简洁版列表。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-milestones.md",
"mimeType": "text/markdown;charset=utf-8",
"content": milestoneContent,
},
{
"key": "risks",
"title": "风险清单",
"description": "项目推进前需要确认的风险与依赖。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-risks.md",
"mimeType": "text/markdown;charset=utf-8",
"content": riskContent,
},
{
"key": "schedule",
"title": "阶段排期表",
"description": "更接近甘特视角的阶段、时间窗和交付物表。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-schedule.md",
"mimeType": "text/markdown;charset=utf-8",
"content": scheduleContent,
},
}
}
func BuildTableCleanupArtifacts(inputText string) []gin.H {
fields, bodyText := parseOfficeFields(inputText, map[string][]string{
"topic": {"主题", "标题", "topic", "title"},
@@ -58,115 +58,6 @@ func BuildLongformArtifacts(inputText string) []gin.H {
}
}
func BuildTextToolkitArtifacts(inputText string) []gin.H {
cleaned := stripMarkdownText(inputText)
stats := strings.Join([]string{
"# 文本统计",
"",
fmt.Sprintf("- 字符数:%d", len([]rune(cleaned))),
fmt.Sprintf("- 行数:%d", len(splitMeaningfulLines(cleaned))),
fmt.Sprintf("- 原始长度:%d", len([]rune(strings.TrimSpace(inputText)))),
"",
"## 处理说明",
"- 已移除常见 Markdown 标记",
"- 已压缩多余空行",
"- 已保留正文可读性",
}, "\n")
slug := officeSlug(pickOfficeTopic(inputText, "cleaned-text"), "text-toolkit")
return []gin.H{
{
"key": "cleaned",
"title": "清洗后文本",
"description": "适合直接粘贴到文档或聊天工具。",
"kind": "text",
"fileFormat": "txt",
"downloadName": slug + ".txt",
"mimeType": "text/plain;charset=utf-8",
"content": cleaned,
},
{
"key": "stats",
"title": "文本统计",
"description": "记录本次清洗和统计结果。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-stats.md",
"mimeType": "text/markdown;charset=utf-8",
"content": stats,
},
}
}
func BuildProposalSummaryArtifacts(inputText string) []gin.H {
topic := pickOfficeTopic(inputText, "方案摘要")
points := extractOfficePoints(inputText, []string{"明确方案目标和解决的问题", "提炼核心价值与收益", "压缩关键实施路径", "明确需要决策的事项"})
summaryContent := strings.TrimSpace(strings.Join([]string{
"# " + topic,
"",
"## 核心结论",
fmt.Sprintf("%s 已压缩为适合汇报和快速预读的摘要版本,重点保留目标、价值点和实施建议。", topic),
"",
"## 关键亮点",
"- " + firstNonEmpty(pointAt(points, 0), "明确方案目标和解决的问题"),
"- " + firstNonEmpty(pointAt(points, 1), "提炼核心价值与收益"),
"- " + firstNonEmpty(pointAt(points, 2), "压缩关键实施路径"),
"- " + firstNonEmpty(pointAt(points, 3), "明确需要决策的事项"),
"",
"## 实施建议",
"- 建议先确认资源与排期",
"- 建议围绕高优先级场景先落一版 MVP",
"- 建议同步关键负责人做进一步评审",
}, "\n"))
highlightsContent := strings.TrimSpace(strings.Join([]string{
"# 亮点清单",
"",
"1. " + firstNonEmpty(pointAt(points, 0), "明确方案目标和解决的问题"),
"2. " + firstNonEmpty(pointAt(points, 1), "提炼核心价值与收益"),
"3. " + firstNonEmpty(pointAt(points, 2), "压缩关键实施路径"),
"4. " + firstNonEmpty(pointAt(points, 3), "明确需要决策的事项"),
}, "\n"))
briefingContent := strings.TrimSpace(strings.Join([]string{
"【" + topic + " 摘要】",
"1. " + firstNonEmpty(pointAt(points, 0), "明确方案目标和解决的问题"),
"2. " + firstNonEmpty(pointAt(points, 1), "提炼核心价值与收益"),
"3. " + firstNonEmpty(pointAt(points, 2), "压缩关键实施路径"),
"建议按上述结论进入下一步决策或评审。",
}, "\n"))
slug := officeSlug(topic, "proposal-summary")
return []gin.H{
{
"key": "summary",
"title": "方案摘要",
"description": "适合直接汇报或继续细化的主摘要。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + ".md",
"mimeType": "text/markdown;charset=utf-8",
"content": summaryContent,
},
{
"key": "highlights",
"title": "亮点清单",
"description": "压缩提炼后的价值点列表。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-highlights.md",
"mimeType": "text/markdown;charset=utf-8",
"content": highlightsContent,
},
{
"key": "briefing",
"title": "汇报短版",
"description": "适合发群或口头汇报前快速同步的短摘要。",
"kind": "text",
"fileFormat": "txt",
"downloadName": slug + "-briefing.txt",
"mimeType": "text/plain;charset=utf-8",
"content": briefingContent,
},
}
}
func BuildContractBriefArtifacts(inputText string) []gin.H {
fields, bodyText := parseOfficeFields(inputText, map[string][]string{
"topic": {"合同名称", "主题", "topic"},
@@ -422,67 +313,3 @@ func BuildPolicyRewriteArtifacts(inputText string) []gin.H {
},
}
}
func BuildSurveySummaryArtifacts(inputText string) []gin.H {
topic := pickOfficeTopic(inputText, "问卷汇总")
points := extractOfficePoints(inputText, []string{"参与者的主要反馈方向", "高频问题或不满意点", "值得保留的积极反馈", "后续改进建议"})
reportContent := strings.TrimSpace(strings.Join([]string{
"# " + topic,
"",
"## 汇总结论",
"- " + firstNonEmpty(pointAt(points, 0), "参与者的主要反馈方向"),
"- " + firstNonEmpty(pointAt(points, 1), "高频问题或不满意点"),
"- " + firstNonEmpty(pointAt(points, 2), "值得保留的积极反馈"),
"- " + firstNonEmpty(pointAt(points, 3), "后续改进建议"),
"",
"## 汇总说明",
"已把表单和问卷结果压缩成便于阅读和决策的汇总结论。",
}, "\n"))
findingsContent := strings.TrimSpace(strings.Join([]string{
"# 重点发现",
"",
"- 发现 1:" + firstNonEmpty(pointAt(points, 0), "参与者主要关注体验和执行效果"),
"- 发现 2:" + firstNonEmpty(pointAt(points, 1), "部分问题集中在流程或内容安排上"),
"- 发现 3:" + firstNonEmpty(pointAt(points, 2), "已有若干积极反馈值得继续保留"),
}, "\n"))
actionsContent := strings.TrimSpace(strings.Join([]string{
"# 改进建议",
"",
"- [ ] " + firstNonEmpty(pointAt(points, 3), "根据汇总结果制定下一步改进动作"),
"- [ ] 对高频问题安排专项优化",
"- [ ] 向负责人同步汇总结论和优先级建议",
}, "\n"))
slug := officeSlug(topic, "survey-summary")
return []gin.H{
{
"key": "report",
"title": "汇总报告",
"description": "适合直接汇报或继续细化的主报告。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + ".md",
"mimeType": "text/markdown;charset=utf-8",
"content": reportContent,
},
{
"key": "findings",
"title": "重点发现",
"description": "从表单结果中提炼出的关键发现。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-findings.md",
"mimeType": "text/markdown;charset=utf-8",
"content": findingsContent,
},
{
"key": "actions",
"title": "改进建议",
"description": "建议后续继续推进的改进行动。",
"kind": "text",
"fileFormat": "md",
"downloadName": slug + "-actions.md",
"mimeType": "text/markdown;charset=utf-8",
"content": actionsContent,
},
}
}
@@ -2,7 +2,6 @@ package office
import (
contractbrief "eai_agentplatform/backend/internal/skills/packages/contract_brief"
emaildrafting "eai_agentplatform/backend/internal/skills/packages/email_drafting"
interviewsummary "eai_agentplatform/backend/internal/skills/packages/interview_summary"
longformwriting "eai_agentplatform/backend/internal/skills/packages/longform_writing"
meetingminutes "eai_agentplatform/backend/internal/skills/packages/meeting_minutes"
@@ -11,11 +10,7 @@ import (
policyrewrite "eai_agentplatform/backend/internal/skills/packages/policy_rewrite"
pptgeneration "eai_agentplatform/backend/internal/skills/packages/ppt_generation"
progressreport "eai_agentplatform/backend/internal/skills/packages/progress_report"
projectplanning "eai_agentplatform/backend/internal/skills/packages/project_planning"
proposalsummary "eai_agentplatform/backend/internal/skills/packages/proposal_summary"
surveysummary "eai_agentplatform/backend/internal/skills/packages/survey_summary"
tablecleanup "eai_agentplatform/backend/internal/skills/packages/table_cleanup"
texttoolkit "eai_agentplatform/backend/internal/skills/packages/text_toolkit"
officecontracts "eai_agentplatform/backend/internal/skills/runtime/office/contracts"
"github.com/gin-gonic/gin"
)
@@ -29,18 +24,13 @@ var builtinRegistry = NewRegistry(
withBuild(pptgeneration.OfficeRuntime, BuildPPTArtifacts),
withBuild(mindmap.OfficeRuntime, BuildMindMapArtifacts),
withBuild(longformwriting.OfficeRuntime, BuildLongformArtifacts),
withBuild(texttoolkit.OfficeRuntime, BuildTextToolkitArtifacts),
ocrunderstanding.OfficeRuntime,
withBuild(meetingminutes.OfficeRuntime, BuildMeetingMinutesArtifacts),
withBuild(projectplanning.OfficeRuntime, BuildProjectPlanningArtifacts),
withBuild(emaildrafting.OfficeRuntime, BuildEmailDraftingArtifacts),
withBuild(tablecleanup.OfficeRuntime, BuildTableCleanupArtifacts),
withBuild(proposalsummary.OfficeRuntime, BuildProposalSummaryArtifacts),
withBuild(progressreport.OfficeRuntime, BuildProgressReportArtifacts),
withBuild(contractbrief.OfficeRuntime, BuildContractBriefArtifacts),
withBuild(interviewsummary.OfficeRuntime, BuildInterviewSummaryArtifacts),
withBuild(policyrewrite.OfficeRuntime, BuildPolicyRewriteArtifacts),
withBuild(surveysummary.OfficeRuntime, BuildSurveySummaryArtifacts),
)
func BuiltinRegistry() *Registry {
@@ -71,26 +71,6 @@ func parseMeetingActions(text string, fallback []string) []gin.H {
return out
}
func buildEmailSubjectVariants(topic, purpose string) []string {
base := firstNonEmpty(topic, "事项")
context := strings.TrimSpace(purpose)
items := []string{
base + " - 请协助确认",
base + " - 需要回复",
base + " - 进展同步",
}
if context != "" {
runes := []rune(context)
if len(runes) > 12 {
context = string(runes[:12])
}
items = append(items, base+" - "+context)
} else {
items = append(items, base+" - 待处理提醒")
}
return items
}
func formatMeetingActionLine(actions []gin.H, index int) string {
if index < 0 || index >= len(actions) {
return fmt.Sprintf("%d. 待补充行动项", index+1)
@@ -149,29 +129,3 @@ func buildContractRedlineItems(points, focusKeywords []string) []gin.H {
}
return out
}
func stripMarkdownText(text string) string {
text = strings.ReplaceAll(text, "\r", "\n")
blockCode := regexp.MustCompile("```[\\s\\S]*?```")
text = blockCode.ReplaceAllStringFunc(text, func(value string) string {
return strings.TrimSpace(strings.ReplaceAll(value, "```", ""))
})
replacers := []struct {
pattern string
value string
}{
{"`([^`]+)`", "$1"},
{"^#{1,6}\\s+", ""},
{"^\\s*[-*+]\\s+", ""},
{"^\\s*\\d+[.)]\\s+", ""},
{"\\*\\*(.*?)\\*\\*", "$1"},
{"\\*(.*?)\\*", "$1"},
{"!\\[[^\\]]*\\]\\([^)]*\\)", ""},
{"\\[([^\\]]+)\\]\\(([^)]*)\\)", "$1"},
}
for _, item := range replacers {
text = regexp.MustCompile(item.pattern).ReplaceAllString(text, item.value)
}
text = regexp.MustCompile(`\n{3,}`).ReplaceAllString(text, "\n\n")
return strings.TrimSpace(text)
}
@@ -217,31 +217,6 @@ func splitSimpleList(value string) []string {
return out
}
func buildProjectStageRows(points []string, owner, deadline string) []gin.H {
labels := []string{"阶段 1", "阶段 2", "阶段 3", "阶段 4"}
windows := []string{"第 1 周", "第 2 周", "第 3 周", firstNonEmpty(deadline, "收口前")}
rows := make([]gin.H, 0, len(labels))
for index, label := range labels {
output := "收口结论"
switch index {
case 0:
output = "目标确认"
case 1:
output = "阶段交付"
case 2:
output = "验收材料"
}
rows = append(rows, gin.H{
"stage": label,
"window": windows[index],
"goal": firstNonEmpty(pointAt(points, index), "补充"+label+"目标"),
"owner": firstNonEmpty(owner, "待指定"),
"output": output,
})
}
return rows
}
func repeatString(value string, count int) []string {
items := make([]string, 0, count)
for index := 0; index < count; index++ {