refactor: 对象命名标准化落地(RoleKind→ObjectKind、capability/assistant 收口)
后端 - model.SkillDefinition.RoleKind 改名 ObjectKind(列 role_kind → object_kind); 新增 migrateSkillObjectKindColumn:先拷数据再显式 drop 旧列。 GORM AutoMigrate 只加不删,字段改名不手写迁移就会新旧两列并存。 - Specialist 结构化列更名走同一套 migrateColumnData: role_card_json → interaction_card_json、source_records_json → inputs_records_json - api/capability_definition.go(413 行)拆为 skill_action_definition.go, 不再让 capability 充当一级对象名;该文件同时承载技能与动作定义处理器 - 新增 model/app_definition.go + api/app_definition.go:应用目录定义正式建模 - SkillDefinition 移除已废弃的 LegacyObjectEntryRoute 字段,对应迁移分支一并删除 - skill_keys.go 注释更新:权威来源为前端 staticSkillCatalog 前端 - api/assistant.js → chat.js、api/capability.js → skill.js,新增 api/appDefinition.js - 新增 store/specialistCatalog.js、skillCatalog.js、appCatalog.js, 取代 config/productizedApps.js(已删) - config/workbench.js 静态目录去失真:businessApps → staticSpecialistCatalog (该变量装的其实是专员目录,与应用 app 语义直接冲突)、 availableSkills → staticSkillCatalog - 修复入口 query 协议断裂:写端 appCatalog 用 app_specialist/app_skill/app_prompt, 读端 SmartAssistantPage 读同名参数并挂响应式监听。 此前写读两侧命名不一致且无人读旧名,导致从应用目录打开应用时 预置专员/技能/提示词被静默丢弃、无任何报错 - 页面与组件局部变量去失真:拿到专员对象不再命名为 app 验证:CGO_ENABLED=0 go build ./... 通过;go test ./... 全包通过;npm run build 通过。 旧变量名 businessApps / availableSkills / productizedApps / api/capability / api/assistant 全仓 grep 无残留。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ func Init(dbPath string) (*gorm.DB, error) {
|
||||
&model.WorkerArtifact{},
|
||||
&model.WorkerRun{},
|
||||
&model.UserAppCenter{},
|
||||
&model.AppDefinition{},
|
||||
&model.OfficialAccountArticle{},
|
||||
&model.OfficialAccountHotspot{},
|
||||
); err != nil {
|
||||
@@ -71,6 +72,12 @@ func Init(dbPath string) (*gorm.DB, error) {
|
||||
if err := migrateObjectEntryRouteColumns(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := migrateSpecialistStructuredColumns(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := migrateSkillObjectKindColumn(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
DB = db
|
||||
return db, nil
|
||||
@@ -127,24 +134,6 @@ func migrateObjectEntryRouteColumns(db *gorm.DB) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.SkillDefinition{}, "legacy_object_entry_route") {
|
||||
skillLegacyObjectExpr := coalesceRouteExpr(
|
||||
db.Migrator().HasColumn(&model.SkillDefinition{}, "legacy_entry_route"),
|
||||
"legacy_entry_route",
|
||||
db.Migrator().HasColumn(&model.SkillDefinition{}, "legacy_route"),
|
||||
"legacy_route",
|
||||
)
|
||||
if skillLegacyObjectExpr != "" {
|
||||
if err := db.Exec(fmt.Sprintf(`
|
||||
UPDATE skill_definition
|
||||
SET legacy_object_entry_route = %s
|
||||
WHERE COALESCE(legacy_object_entry_route, '') = ''
|
||||
AND COALESCE(%s, '') <> ''
|
||||
`, skillLegacyObjectExpr, skillLegacyObjectExpr)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.SkillDefinition{}, "entry_route") {
|
||||
if err := dropColumn(db, "skill_definition", "entry_route"); err != nil {
|
||||
return err
|
||||
@@ -165,11 +154,53 @@ func migrateObjectEntryRouteColumns(db *gorm.DB) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.SkillDefinition{}, "legacy_object_entry_route") {
|
||||
if err := dropColumn(db, "skill_definition", "legacy_object_entry_route"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateSpecialistStructuredColumns(db *gorm.DB) error {
|
||||
if !db.Migrator().HasTable(&model.Specialist{}) {
|
||||
return nil
|
||||
}
|
||||
if err := migrateColumnData(db, "specialist", "interaction_card_json", "role_card_json"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateColumnData(db, "specialist", "inputs_records_json", "source_records_json"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateSkillObjectKindColumn(db *gorm.DB) error {
|
||||
if !db.Migrator().HasTable(&model.SkillDefinition{}) {
|
||||
return nil
|
||||
}
|
||||
return migrateColumnData(db, "skill_definition", "object_kind", "role_kind")
|
||||
}
|
||||
|
||||
func migrateColumnData(db *gorm.DB, tableName, targetColumn, legacyColumn string) error {
|
||||
if !db.Migrator().HasColumn(tableName, legacyColumn) {
|
||||
return nil
|
||||
}
|
||||
if db.Migrator().HasColumn(tableName, targetColumn) {
|
||||
if err := db.Exec(fmt.Sprintf(`
|
||||
UPDATE %s
|
||||
SET %s = %s
|
||||
WHERE COALESCE(%s, '') = ''
|
||||
AND COALESCE(%s, '') <> ''
|
||||
`, tableName, targetColumn, legacyColumn, targetColumn, legacyColumn)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return dropColumn(db, tableName, legacyColumn)
|
||||
}
|
||||
|
||||
func coalesceRouteExpr(primaryExists bool, primaryColumn string, fallbackExists bool, fallbackColumn string) string {
|
||||
parts := make([]string, 0, 2)
|
||||
if primaryExists {
|
||||
|
||||
@@ -44,6 +44,9 @@ func TestMigrateObjectEntryRouteColumnsDropsLegacyColumns(t *testing.T) {
|
||||
if err := db.Exec(`ALTER TABLE skill_definition ADD COLUMN legacy_route TEXT DEFAULT ''`).Error; err != nil {
|
||||
t.Fatalf("add skill_definition.legacy_route: %v", err)
|
||||
}
|
||||
if err := db.Exec(`ALTER TABLE skill_definition ADD COLUMN legacy_object_entry_route TEXT DEFAULT ''`).Error; err != nil {
|
||||
t.Fatalf("add skill_definition.legacy_object_entry_route: %v", err)
|
||||
}
|
||||
|
||||
specialist := model.Specialist{
|
||||
Key: "spec-demo",
|
||||
@@ -62,13 +65,12 @@ func TestMigrateObjectEntryRouteColumnsDropsLegacyColumns(t *testing.T) {
|
||||
}
|
||||
|
||||
skill := model.SkillDefinition{
|
||||
Key: "skill-demo",
|
||||
Label: "技能演示",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "",
|
||||
LegacyObjectEntryRoute: "",
|
||||
State: "active",
|
||||
Key: "skill-demo",
|
||||
Label: "技能演示",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "",
|
||||
State: "active",
|
||||
}
|
||||
if err := db.Create(&skill).Error; err != nil {
|
||||
t.Fatalf("create skill: %v", err)
|
||||
@@ -98,9 +100,6 @@ func TestMigrateObjectEntryRouteColumnsDropsLegacyColumns(t *testing.T) {
|
||||
if migratedSkill.ObjectEntryRoute != "/legacy-skill" {
|
||||
t.Fatalf("skill object_entry_route = %q, want %q", migratedSkill.ObjectEntryRoute, "/legacy-skill")
|
||||
}
|
||||
if migratedSkill.LegacyObjectEntryRoute != "/legacy-old-skill" {
|
||||
t.Fatalf("skill legacy_object_entry_route = %q, want %q", migratedSkill.LegacyObjectEntryRoute, "/legacy-old-skill")
|
||||
}
|
||||
|
||||
if db.Migrator().HasColumn(&model.Specialist{}, "entry_route") {
|
||||
t.Fatalf("specialist.entry_route should be dropped")
|
||||
@@ -120,4 +119,101 @@ func TestMigrateObjectEntryRouteColumnsDropsLegacyColumns(t *testing.T) {
|
||||
if db.Migrator().HasColumn(&model.SkillDefinition{}, "legacy_route") {
|
||||
t.Fatalf("skill_definition.legacy_route should be dropped")
|
||||
}
|
||||
if db.Migrator().HasColumn("skill_definition", "legacy_object_entry_route") {
|
||||
t.Fatalf("skill_definition.legacy_object_entry_route should be dropped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSpecialistStructuredColumnsDropsLegacyColumns(t *testing.T) {
|
||||
db := openMigrationTestDB(t)
|
||||
if err := db.AutoMigrate(&model.Specialist{}); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Exec(`ALTER TABLE specialist ADD COLUMN role_card_json TEXT DEFAULT ''`).Error; err != nil {
|
||||
t.Fatalf("add specialist.role_card_json: %v", err)
|
||||
}
|
||||
if err := db.Exec(`ALTER TABLE specialist ADD COLUMN source_records_json TEXT DEFAULT ''`).Error; err != nil {
|
||||
t.Fatalf("add specialist.source_records_json: %v", err)
|
||||
}
|
||||
|
||||
item := model.Specialist{
|
||||
Key: "spec-structured",
|
||||
Label: "结构化专员",
|
||||
Tier: "generic",
|
||||
WorkerType: "dw",
|
||||
ObjectEntryRoute: "/home",
|
||||
State: "active",
|
||||
MarketTag: "installed",
|
||||
}
|
||||
if err := db.Create(&item).Error; err != nil {
|
||||
t.Fatalf("create specialist: %v", err)
|
||||
}
|
||||
if err := db.Exec(`UPDATE specialist SET interaction_card_json = '', role_card_json = ?, inputs_records_json = '', source_records_json = ? WHERE id = ?`,
|
||||
`{"greeting":"hello"}`, `[{"input_name":"crm"}]`, item.ID,
|
||||
).Error; err != nil {
|
||||
t.Fatalf("seed specialist legacy structured columns: %v", err)
|
||||
}
|
||||
|
||||
if err := migrateSpecialistStructuredColumns(db); err != nil {
|
||||
t.Fatalf("migrate specialist structured columns: %v", err)
|
||||
}
|
||||
|
||||
var migrated model.Specialist
|
||||
if err := db.First(&migrated, item.ID).Error; err != nil {
|
||||
t.Fatalf("reload specialist: %v", err)
|
||||
}
|
||||
if migrated.InteractionCardJSON != `{"greeting":"hello"}` {
|
||||
t.Fatalf("interaction_card_json = %q", migrated.InteractionCardJSON)
|
||||
}
|
||||
if migrated.InputsRecordsJSON != `[{"input_name":"crm"}]` {
|
||||
t.Fatalf("inputs_records_json = %q", migrated.InputsRecordsJSON)
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.Specialist{}, "role_card_json") {
|
||||
t.Fatalf("specialist.role_card_json should be dropped")
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.Specialist{}, "source_records_json") {
|
||||
t.Fatalf("specialist.source_records_json should be dropped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSkillObjectKindColumnDropsLegacyColumn(t *testing.T) {
|
||||
db := openMigrationTestDB(t)
|
||||
if err := db.AutoMigrate(&model.SkillDefinition{}); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Exec(`ALTER TABLE skill_definition ADD COLUMN role_kind TEXT DEFAULT ''`).Error; err != nil {
|
||||
t.Fatalf("add skill_definition.role_kind: %v", err)
|
||||
}
|
||||
|
||||
item := model.SkillDefinition{
|
||||
Key: "skill-kind-demo",
|
||||
Label: "技能类型演示",
|
||||
ObjectKind: "",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
State: "active",
|
||||
}
|
||||
if err := db.Create(&item).Error; err != nil {
|
||||
t.Fatalf("create skill: %v", err)
|
||||
}
|
||||
if err := db.Exec(`UPDATE skill_definition SET object_kind = '', role_kind = ? WHERE id = ?`, "skill", item.ID).Error; err != nil {
|
||||
t.Fatalf("seed skill legacy object kind: %v", err)
|
||||
}
|
||||
|
||||
if err := migrateSkillObjectKindColumn(db); err != nil {
|
||||
t.Fatalf("migrate skill object kind column: %v", err)
|
||||
}
|
||||
|
||||
var migrated model.SkillDefinition
|
||||
if err := db.First(&migrated, item.ID).Error; err != nil {
|
||||
t.Fatalf("reload skill: %v", err)
|
||||
}
|
||||
if migrated.ObjectKind != "skill" {
|
||||
t.Fatalf("object_kind = %q", migrated.ObjectKind)
|
||||
}
|
||||
if db.Migrator().HasColumn(&model.SkillDefinition{}, "role_kind") {
|
||||
t.Fatalf("skill_definition.role_kind should be dropped")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,9 @@ func SeedDefaults() error {
|
||||
if err := seedActionDefinitions(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seedAppDefinitions(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -322,6 +325,19 @@ func applySkillCodes(items []model.SkillDefinition) []model.SkillDefinition {
|
||||
return items
|
||||
}
|
||||
|
||||
func applyAppCodes(items []model.AppDefinition) []model.AppDefinition {
|
||||
for i := range items {
|
||||
suffix := fmt.Sprintf("%02d", i+1)
|
||||
if strings.TrimSpace(items[i].DisplayCode) == "" {
|
||||
items[i].DisplayCode = "应" + suffix
|
||||
}
|
||||
if strings.TrimSpace(items[i].EAILogicCode) == "" {
|
||||
items[i].EAILogicCode = "EAI-A-" + suffix
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func seedSpecialists() error {
|
||||
items := applySpecialistCodes([]model.Specialist{
|
||||
{
|
||||
@@ -582,6 +598,30 @@ func seedSpecialists() error {
|
||||
State: "active",
|
||||
SortOrder: 90,
|
||||
},
|
||||
{
|
||||
Key: "presentation-briefing",
|
||||
Label: "汇报专员",
|
||||
Tier: "generic",
|
||||
WorkerType: "dw",
|
||||
ObjectEntryRoute: "/apps/presentation-briefing",
|
||||
Summary: "经营汇报、方案汇报与培训课件统一出稿",
|
||||
WorkStatus: "2 个待成稿",
|
||||
RiskLabel: "1 个页数待压缩",
|
||||
Color: "#4f46e5",
|
||||
Stage: "演示编排",
|
||||
Progress: 68,
|
||||
MarketTag: "已安装",
|
||||
Version: "v1.0",
|
||||
ConnectorScope: "连接任务结果 / 知识库 / 模板中心",
|
||||
PermissionScope: "读取材料、生成演示稿、导出讲稿备注",
|
||||
ResourceBindings: "演示模板、知识库、任务产物、汇报素材",
|
||||
InfoSources: "主题目标、受众、汇报材料、页数限制",
|
||||
BaseSkills: "演示结构规划、页面要点提炼、讲稿备注生成、口径统一",
|
||||
AIAssistance: "自动压缩材料并给出适合管理层、客户或培训场景的页面结构",
|
||||
GeneratedSkills: "把高质量汇报结构沉淀为可复用的演示模板",
|
||||
State: "active",
|
||||
SortOrder: 100,
|
||||
},
|
||||
})
|
||||
// 岗位说明书与技能绑定按 key 挂载(见 seed_specialist_rules.go)
|
||||
if err := applySpecialistRuleFiles(items); err != nil {
|
||||
@@ -608,8 +648,8 @@ func seedSpecialists() error {
|
||||
if existing.WorkerType == "" {
|
||||
updates["worker_type"] = item.WorkerType
|
||||
}
|
||||
if existing.RoleCardJSON == "" {
|
||||
updates["role_card_json"] = item.RoleCardJSON
|
||||
if existing.InteractionCardJSON == "" {
|
||||
updates["interaction_card_json"] = item.InteractionCardJSON
|
||||
}
|
||||
if existing.PermissionScope == "" {
|
||||
updates["permission_scope"] = item.PermissionScope
|
||||
@@ -637,7 +677,7 @@ func seedSpecialists() error {
|
||||
updates["allowed_skills"] = item.AllowedSkills
|
||||
}
|
||||
if existing.InputsRecordsJSON == "" {
|
||||
updates["source_records_json"] = item.InputsRecordsJSON
|
||||
updates["inputs_records_json"] = item.InputsRecordsJSON
|
||||
}
|
||||
if existing.PermissionRecordsJSON == "" {
|
||||
updates["permission_records_json"] = item.PermissionRecordsJSON
|
||||
@@ -666,7 +706,7 @@ func seedSkillDefinitions() error {
|
||||
Key: "smart-assistant",
|
||||
Label: "通用助手",
|
||||
Description: "默认协作入口,负责问题梳理、任务拆解和对话推进。",
|
||||
RoleKind: "assistant",
|
||||
ObjectKind: "assistant",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
@@ -681,118 +721,112 @@ func seedSkillDefinitions() error {
|
||||
SortOrder: 10,
|
||||
},
|
||||
{
|
||||
Key: "document-translate",
|
||||
Label: "文档翻译",
|
||||
Description: "把文本和文档稳定翻成目标语言,并保留语气和结构。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/tools/document-translate",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把这段产品说明翻译成英文,保持专业语气", "把这封邮件翻译成日文,语气礼貌一点", "把这份中文文档整理成英文摘要"}),
|
||||
PromptTemplate: "明确源语言、目标语言、语气和输出格式后执行翻译。",
|
||||
ActionRefsJSON: mustJSON([]string{"document.translate"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"content": "text_or_document", "target_language": "string"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"translation": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"translated_document"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"document"}, "action_types": []string{"translate"}}),
|
||||
State: "active",
|
||||
SortOrder: 20,
|
||||
Key: "document-translate",
|
||||
Label: "文档翻译",
|
||||
Description: "把文本和文档稳定翻成目标语言,并保留语气和结构。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把这段产品说明翻译成英文,保持专业语气", "把这封邮件翻译成日文,语气礼貌一点", "把这份中文文档整理成英文摘要"}),
|
||||
PromptTemplate: "明确源语言、目标语言、语气和输出格式后执行翻译。",
|
||||
ActionRefsJSON: mustJSON([]string{"document.translate"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"content": "text_or_document", "target_language": "string"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"translation": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"translated_document"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"document"}, "action_types": []string{"translate"}}),
|
||||
State: "active",
|
||||
SortOrder: 20,
|
||||
},
|
||||
{
|
||||
Key: "copy-proofreading",
|
||||
Label: "文案校对",
|
||||
Description: "检查错别字、语病和表达不顺,输出润色建议。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/tools/copy-proofreading",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"检查这段销售话术里的错别字和语病", "把这段产品介绍润得更正式一点", "帮我校对这封对外邮件,保留原意"}),
|
||||
PromptTemplate: "围绕原文语气给出校对与改写结果。",
|
||||
ActionRefsJSON: mustJSON([]string{"copy.proofread"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"content": "text"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"proofread_result": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"clean_copy"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"copy"}, "action_types": []string{"review"}}),
|
||||
State: "active",
|
||||
SortOrder: 30,
|
||||
Key: "copy-proofreading",
|
||||
Label: "文案校对",
|
||||
Description: "检查错别字、语病和表达不顺,输出润色建议。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"检查这段销售话术里的错别字和语病", "把这段产品介绍润得更正式一点", "帮我校对这封对外邮件,保留原意"}),
|
||||
PromptTemplate: "围绕原文语气给出校对与改写结果。",
|
||||
ActionRefsJSON: mustJSON([]string{"copy.proofread"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"content": "text"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"proofread_result": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"clean_copy"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"copy"}, "action_types": []string{"review"}}),
|
||||
State: "active",
|
||||
SortOrder: 30,
|
||||
},
|
||||
{
|
||||
Key: "audio-transcribe",
|
||||
Label: "语音转写",
|
||||
Description: "把音频内容转成结构化文本,便于继续总结与提取。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/tools/audio-transcribe",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把这段会议录音转成文字,并分段整理", "先转写这段采访,再提取重点", "把语音内容整理成可发群里的纪要"}),
|
||||
PromptTemplate: "先转写音频,再按需要输出整理版文本。",
|
||||
ActionRefsJSON: mustJSON([]string{"audio.transcribe"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"audio": "file_or_url"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"transcript": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"transcript"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"audio"}, "action_types": []string{"extract"}}),
|
||||
State: "active",
|
||||
SortOrder: 40,
|
||||
Key: "audio-transcribe",
|
||||
Label: "语音转写",
|
||||
Description: "把音频内容转成结构化文本,便于继续总结与提取。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把这段会议录音转成文字,并分段整理", "先转写这段采访,再提取重点", "把语音内容整理成可发群里的纪要"}),
|
||||
PromptTemplate: "先转写音频,再按需要输出整理版文本。",
|
||||
ActionRefsJSON: mustJSON([]string{"audio.transcribe"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"audio": "file_or_url"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"transcript": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"transcript"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"audio"}, "action_types": []string{"extract"}}),
|
||||
State: "active",
|
||||
SortOrder: 40,
|
||||
},
|
||||
{
|
||||
Key: "batch-extract",
|
||||
Label: "批量提取",
|
||||
Description: "从一批内容里抽取结构化字段和关键信息。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/tools/batch-extract",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"从这批简历里提取姓名、岗位和年限", "把这些合同里的付款条款统一抽出来", "帮我批量提取文章标题、作者和发布时间"}),
|
||||
PromptTemplate: "明确字段范围、输出格式和异常处理规则后执行批量提取。",
|
||||
ActionRefsJSON: mustJSON([]string{"batch.extract"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"items": []string{"text_or_document"}, "fields": []string{"string"}}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"rows": []string{"object"}}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"spreadsheet", "json"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"document", "resume", "contract"}, "action_types": []string{"extract"}}),
|
||||
State: "active",
|
||||
SortOrder: 50,
|
||||
Key: "batch-extract",
|
||||
Label: "批量提取",
|
||||
Description: "从一批内容里抽取结构化字段和关键信息。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"从这批简历里提取姓名、岗位和年限", "把这些合同里的付款条款统一抽出来", "帮我批量提取文章标题、作者和发布时间"}),
|
||||
PromptTemplate: "明确字段范围、输出格式和异常处理规则后执行批量提取。",
|
||||
ActionRefsJSON: mustJSON([]string{"batch.extract"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"items": []string{"text_or_document"}, "fields": []string{"string"}}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"rows": []string{"object"}}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"spreadsheet", "json"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"document", "resume", "contract"}, "action_types": []string{"extract"}}),
|
||||
State: "active",
|
||||
SortOrder: 50,
|
||||
},
|
||||
{
|
||||
Key: "contract-review",
|
||||
Label: "合同审查",
|
||||
Description: "定位风险条款、待确认项并输出审查建议。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/tools/contract-review",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"审查这份采购合同的风险点", "重点看赔偿责任和终止条款", "帮我整理一版待人工确认的风险清单"}),
|
||||
PromptTemplate: "按照甲方风控视角识别风险条款、例外项和红线建议。",
|
||||
ActionRefsJSON: mustJSON([]string{"contract.review"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"contract": "document_or_text"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"risk_report": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"risk_report", "redline"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"contract"}, "action_types": []string{"review", "analyze"}}),
|
||||
State: "active",
|
||||
SortOrder: 60,
|
||||
Key: "contract-review",
|
||||
Label: "合同审查",
|
||||
Description: "定位风险条款、待确认项并输出审查建议。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"审查这份采购合同的风险点", "重点看赔偿责任和终止条款", "帮我整理一版待人工确认的风险清单"}),
|
||||
PromptTemplate: "按照甲方风控视角识别风险条款、例外项和红线建议。",
|
||||
ActionRefsJSON: mustJSON([]string{"contract.review"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"contract": "document_or_text"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"risk_report": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"risk_report", "redline"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"contract"}, "action_types": []string{"review", "analyze"}}),
|
||||
State: "active",
|
||||
SortOrder: 60,
|
||||
},
|
||||
{
|
||||
Key: "report-generation",
|
||||
Label: "报告生成",
|
||||
Description: "把过程材料整理成日报、周报、复盘和交付说明。",
|
||||
RoleKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
LegacyObjectEntryRoute: "/report-gen",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把今天的工作记录整理成日报", "根据这些节点生成项目周报", "帮我把这次交付过程整理成复盘说明"}),
|
||||
PromptTemplate: "先梳理结论与结构,再输出对应报告版本。",
|
||||
ActionRefsJSON: mustJSON([]string{"report.generate"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"materials": []string{"text_or_document"}, "report_type": "string"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"report": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"report"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"report", "task"}, "action_types": []string{"draft"}}),
|
||||
State: "active",
|
||||
SortOrder: 70,
|
||||
Key: "report-generation",
|
||||
Label: "报告生成",
|
||||
Description: "把过程材料整理成日报、周报、复盘和交付说明。",
|
||||
ObjectKind: "skill",
|
||||
Source: "eai",
|
||||
ObjectEntryRoute: "/home",
|
||||
ExposedToUser: true,
|
||||
StarterPromptsJSON: mustJSON([]string{"把今天的工作记录整理成日报", "根据这些节点生成项目周报", "帮我把这次交付过程整理成复盘说明"}),
|
||||
PromptTemplate: "先梳理结论与结构,再输出对应报告版本。",
|
||||
ActionRefsJSON: mustJSON([]string{"report.generate"}),
|
||||
InputSchemaJSON: mustJSON(map[string]any{"materials": []string{"text_or_document"}, "report_type": "string"}),
|
||||
OutputSchemaJSON: mustJSON(map[string]any{"report": "text"}),
|
||||
ArtifactSchemaJSON: mustJSON(map[string]any{"artifacts": []string{"report"}}),
|
||||
OntologyBindingJSON: mustJSON(map[string]any{"object_types": []string{"report", "task"}, "action_types": []string{"draft"}}),
|
||||
State: "active",
|
||||
SortOrder: 70,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -817,9 +851,6 @@ func seedSkillDefinitions() error {
|
||||
if existing.ObjectEntryRoute == "" {
|
||||
updates["object_entry_route"] = item.ObjectEntryRoute
|
||||
}
|
||||
if existing.LegacyObjectEntryRoute == "" {
|
||||
updates["legacy_object_entry_route"] = item.LegacyObjectEntryRoute
|
||||
}
|
||||
if existing.StarterPromptsJSON == "" {
|
||||
updates["starter_prompts_json"] = item.StarterPromptsJSON
|
||||
}
|
||||
@@ -990,6 +1021,337 @@ func seedActionDefinitions() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedAppDefinitions() error {
|
||||
items := applyAppCodes([]model.AppDefinition{
|
||||
{
|
||||
Key: "knowledge-hub",
|
||||
Label: "知识库",
|
||||
Badge: "内建 APP",
|
||||
Kind: "组织级 APP",
|
||||
MarketTag: "内建 APP",
|
||||
WorkerType: "adw",
|
||||
Tier: "platform",
|
||||
Source: "eai",
|
||||
Color: "#5b7cff",
|
||||
IconText: "知",
|
||||
CoverTone: "linear-gradient(135deg, #5b7cff 0%, #8fb4ff 100%)",
|
||||
Summary: "组织级默认存在的知识底座 APP,为专员、技能与其它 APP 提供知识支持。",
|
||||
Description: "知识库是平台里的知识底座,用来沉淀资料、规则和证据,并为专员、技能和 APP 提供统一知识支持。",
|
||||
OpenRoute: "/knowledge-hub",
|
||||
DefaultPrompt: "我想先整理这批知识资料",
|
||||
PromptsJSON: mustJSON([]string{"我想先整理这批知识资料", "帮我看看这部分内容适合怎么入库", "知识库更适合承接哪些任务"}),
|
||||
TagsJSON: mustJSON([]string{"知识沉淀", "证据引用", "组织底座"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 10,
|
||||
},
|
||||
{
|
||||
Key: "internal-exam",
|
||||
Label: "内部考试APP",
|
||||
Badge: "长程 APP",
|
||||
Kind: "内部考试",
|
||||
MarketTag: "长程 APP",
|
||||
WorkerType: "adw",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#29b36a",
|
||||
IconText: "考",
|
||||
CoverTone: "linear-gradient(135deg, #1c9f5a 0%, #6ee7b7 100%)",
|
||||
Summary: "围绕练习、自测、正式考试、记录、证书和岗位应学组织的内部考试 APP。",
|
||||
Description: "内部考试 APP 用来承接训练、练习、考试、成绩记录和证书结果,适合需要完整过程管理的学习场景。",
|
||||
OpenRoute: "/exam/self-test",
|
||||
DefaultPrompt: "我想组织一轮内部考试",
|
||||
PromptsJSON: mustJSON([]string{"我想组织一轮内部考试", "帮我看看这个考试流程怎么搭", "这个场景适合放到内部考试 APP 吗"}),
|
||||
TagsJSON: mustJSON([]string{"训练流程", "考试记录", "结果沉淀"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 20,
|
||||
},
|
||||
{
|
||||
Key: "internal-training",
|
||||
Label: "内部培训APP",
|
||||
Badge: "长程 APP",
|
||||
Kind: "内部培训",
|
||||
MarketTag: "长程 APP",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#9b6bff",
|
||||
IconText: "培",
|
||||
CoverTone: "linear-gradient(135deg, #8b5cf6 0%, #c4b5fd 100%)",
|
||||
Summary: "统一承接销售培训、产品知识和公司介绍三个培训模块的内部培训 APP。",
|
||||
Description: "内部培训 APP 用来把销售培训、产品知识和公司介绍收在同一个对象下面,避免把同一条培训链拆成多个 APP。",
|
||||
OpenRoute: "/courses",
|
||||
DefaultPrompt: "我想搭一个内部培训 APP",
|
||||
PromptsJSON: mustJSON([]string{"我想搭一个内部培训 APP", "帮我梳理销售培训、产品知识和公司介绍的关系", "这个培训主题应该挂到内部培训 APP 吗"}),
|
||||
TagsJSON: mustJSON([]string{"课程组织", "产品知识", "组织导览"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 30,
|
||||
},
|
||||
{
|
||||
Key: "docs-center",
|
||||
Label: "文档中心",
|
||||
Badge: "办公中枢",
|
||||
Kind: "结果沉淀",
|
||||
MarketTag: "办公中枢",
|
||||
WorkerType: "adw",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#2563eb",
|
||||
IconText: "档",
|
||||
CoverTone: "linear-gradient(135deg, #2563eb 0%, #60a5fa 100%)",
|
||||
Summary: "集中查看任务上传物与产物,把结果留住、找得到、能继续复用。",
|
||||
Description: "把任务里产生的上传材料和交付结果集中收在一个地方,方便继续搜索、回看、复制和打开原任务。",
|
||||
OpenRoute: "/docs-center",
|
||||
DefaultPrompt: "看我最近生成过哪些文档和产物",
|
||||
PromptsJSON: mustJSON([]string{"看我最近生成过哪些文档和产物", "按任务回看最近交付结果", "集中查看上传材料和生成内容"}),
|
||||
TagsJSON: mustJSON([]string{"上传物", "产物", "结果沉淀", "集中检索"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 40,
|
||||
},
|
||||
{
|
||||
Key: "app-meeting-minutes",
|
||||
Label: "会议纪要",
|
||||
Badge: "成品应用",
|
||||
Kind: "办公速用",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#0f766e",
|
||||
IconText: "会",
|
||||
CoverTone: "linear-gradient(135deg, #0f766e 0%, #14b8a6 100%)",
|
||||
Summary: "直接进入会议纪要应用,快速整理结论、待办和责任人。",
|
||||
Description: "适合把会议记录、语音转写或零散要点收成正式纪要和行动清单。",
|
||||
SkillKey: "meeting-minutes",
|
||||
DefaultPrompt: "把这段会议记录整理成正式纪要",
|
||||
PromptsJSON: mustJSON([]string{"把这段会议记录整理成正式纪要", "提炼结论、行动项和责任人", "根据讨论内容生成会后同步稿"}),
|
||||
TagsJSON: mustJSON([]string{"会议纪要", "行动项", "责任人", "汇总"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 50,
|
||||
},
|
||||
{
|
||||
Key: "app-contract-brief",
|
||||
Label: "合同摘要",
|
||||
Badge: "成品应用",
|
||||
Kind: "法务办公",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#b91c1c",
|
||||
IconText: "合",
|
||||
CoverTone: "linear-gradient(135deg, #991b1b 0%, #ef4444 100%)",
|
||||
Summary: "直接进入合同摘要应用,快速拿到条款要点、风险和待确认项。",
|
||||
Description: "适合采购合同、合作协议和服务条款的业务预读与快速审查。",
|
||||
SkillKey: "contract-brief",
|
||||
DefaultPrompt: "把这份采购合同提炼成要点摘要",
|
||||
PromptsJSON: mustJSON([]string{"把这份采购合同提炼成要点摘要", "标记高风险条款和待确认事项", "生成业务负责人可读的合同摘要"}),
|
||||
TagsJSON: mustJSON([]string{"合同摘要", "风险提示", "待确认项", "快速预读"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 60,
|
||||
},
|
||||
{
|
||||
Key: "app-proposal-summary",
|
||||
Label: "方案摘要",
|
||||
Badge: "成品应用",
|
||||
Kind: "汇报加速",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#7c3aed",
|
||||
IconText: "方",
|
||||
CoverTone: "linear-gradient(135deg, #6d28d9 0%, #a78bfa 100%)",
|
||||
Summary: "直接进入方案摘要应用,把长方案压成管理层可读的短版本。",
|
||||
Description: "适合售前方案、项目方案和内部提案的结论压缩与亮点提炼。",
|
||||
SkillKey: "proposal-summary",
|
||||
DefaultPrompt: "把这份解决方案压成一版管理层摘要",
|
||||
PromptsJSON: mustJSON([]string{"把这份解决方案压成一版管理层摘要", "提炼亮点和实施建议", "生成汇报短版和亮点清单"}),
|
||||
TagsJSON: mustJSON([]string{"方案摘要", "亮点提炼", "管理层预读", "汇报短版"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 70,
|
||||
},
|
||||
{
|
||||
Key: "app-progress-report",
|
||||
Label: "周报 / 日报",
|
||||
Badge: "成品应用",
|
||||
Kind: "进展同步",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#1d4ed8",
|
||||
IconText: "报",
|
||||
CoverTone: "linear-gradient(135deg, #1d4ed8 0%, #60a5fa 100%)",
|
||||
Summary: "直接进入周报 / 日报应用,快速整理进展、风险和下步计划。",
|
||||
Description: "适合把聊天记录、事项推进和工作流水收成日报、周报或阶段同步稿。",
|
||||
SkillKey: "progress-report",
|
||||
DefaultPrompt: "根据这些工作记录整理一版项目周报",
|
||||
PromptsJSON: mustJSON([]string{"根据这些工作记录整理一版项目周报", "整理日报并补风险和计划", "生成给负责人的阶段同步"}),
|
||||
TagsJSON: mustJSON([]string{"周报", "日报", "风险同步", "下步计划"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 80,
|
||||
},
|
||||
{
|
||||
Key: "app-ppt-generation",
|
||||
Label: "汇报 PPT",
|
||||
Badge: "成品应用",
|
||||
Kind: "汇报交付",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#4f46e5",
|
||||
IconText: "P",
|
||||
CoverTone: "linear-gradient(135deg, #4338ca 0%, #818cf8 100%)",
|
||||
Summary: "直接进入汇报 PPT 应用,默认挂上汇报专员,快速拿到页面结构、讲稿备注和演示骨架。",
|
||||
Description: "适合经营汇报、方案汇报、培训课件和项目复盘。打开就进入“汇报专员 + PPT 技能”的样板链路。",
|
||||
SpecialistKey: "presentation-briefing",
|
||||
SkillKey: "ppt-generation",
|
||||
DefaultPrompt: "帮我做一版 8 页管理层汇报 PPT",
|
||||
PromptsJSON: mustJSON([]string{"帮我做一版 8 页管理层汇报 PPT", "按汇报对象输出页面大纲和讲稿备注", "把这批材料压成一版培训课件骨架"}),
|
||||
TagsJSON: mustJSON([]string{"汇报 PPT", "演示大纲", "讲稿备注", "专员样板"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 90,
|
||||
},
|
||||
{
|
||||
Key: "app-mind-map",
|
||||
Label: "思维导图",
|
||||
Badge: "成品应用",
|
||||
Kind: "结构整理",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#ea580c",
|
||||
IconText: "图",
|
||||
CoverTone: "linear-gradient(135deg, #c2410c 0%, #fb923c 100%)",
|
||||
Summary: "直接进入思维导图应用,快速把主题拆成树状结构。",
|
||||
Description: "适合方案拆解、培训提纲、需求梳理和知识结构整理。",
|
||||
SkillKey: "mind-map",
|
||||
DefaultPrompt: "把这个主题整理成思维导图",
|
||||
PromptsJSON: mustJSON([]string{"把这个主题整理成思维导图", "按树形结构拆解方案", "生成一版培训提纲导图"}),
|
||||
TagsJSON: mustJSON([]string{"思维导图", "结构拆解", "提纲", "知识整理"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 100,
|
||||
},
|
||||
{
|
||||
Key: "app-ocr-understanding",
|
||||
Label: "OCR 图文提取",
|
||||
Badge: "成品应用",
|
||||
Kind: "图文理解",
|
||||
MarketTag: "成品应用",
|
||||
WorkerType: "worker",
|
||||
Tier: "business",
|
||||
Source: "eai",
|
||||
Color: "#0ea5e9",
|
||||
IconText: "扫",
|
||||
CoverTone: "linear-gradient(135deg, #0284c7 0%, #38bdf8 100%)",
|
||||
Summary: "直接进入 OCR 应用,上传图片后提取文字并沉淀到任务产物。",
|
||||
Description: "适合截图、照片、扫描件和证据图片的文本抽取与继续整理。",
|
||||
SkillKey: "ocr-understanding",
|
||||
DefaultPrompt: "识别这张图片里的文字",
|
||||
PromptsJSON: mustJSON([]string{"识别这张图片里的文字", "把扫描件抽成可继续编辑的文本", "提取截图中的关键信息"}),
|
||||
TagsJSON: mustJSON([]string{"OCR", "图文提取", "图片识别", "证据整理"}),
|
||||
InstallState: "installed",
|
||||
ExposedToUser: true,
|
||||
State: "active",
|
||||
SortOrder: 110,
|
||||
},
|
||||
})
|
||||
|
||||
for _, item := range items {
|
||||
var existing model.AppDefinition
|
||||
if err := DB.Where("key = ?", item.Key).First(&existing).Error; err != nil {
|
||||
if err := DB.Create(&item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
updates := map[string]any{}
|
||||
if existing.DisplayCode == "" {
|
||||
updates["display_code"] = item.DisplayCode
|
||||
}
|
||||
if existing.EAILogicCode == "" {
|
||||
updates["eailogic_code"] = item.EAILogicCode
|
||||
}
|
||||
if existing.Badge == "" {
|
||||
updates["badge"] = item.Badge
|
||||
}
|
||||
if existing.Kind == "" {
|
||||
updates["kind"] = item.Kind
|
||||
}
|
||||
if existing.MarketTag == "" {
|
||||
updates["market_tag"] = item.MarketTag
|
||||
}
|
||||
if existing.WorkerType == "" {
|
||||
updates["worker_type"] = item.WorkerType
|
||||
}
|
||||
if existing.Tier == "" {
|
||||
updates["tier"] = item.Tier
|
||||
}
|
||||
if existing.Color == "" {
|
||||
updates["color"] = item.Color
|
||||
}
|
||||
if existing.IconText == "" {
|
||||
updates["icon_text"] = item.IconText
|
||||
}
|
||||
if existing.CoverTone == "" {
|
||||
updates["cover_tone"] = item.CoverTone
|
||||
}
|
||||
if existing.Summary == "" {
|
||||
updates["summary"] = item.Summary
|
||||
}
|
||||
if existing.Description == "" {
|
||||
updates["description"] = item.Description
|
||||
}
|
||||
if existing.OpenRoute == "" {
|
||||
updates["open_route"] = item.OpenRoute
|
||||
}
|
||||
if existing.SpecialistKey == "" {
|
||||
updates["specialist_key"] = item.SpecialistKey
|
||||
}
|
||||
if existing.SkillKey == "" {
|
||||
updates["skill_key"] = item.SkillKey
|
||||
}
|
||||
if existing.DefaultPrompt == "" {
|
||||
updates["default_prompt"] = item.DefaultPrompt
|
||||
}
|
||||
if existing.PromptsJSON == "" {
|
||||
updates["prompts_json"] = item.PromptsJSON
|
||||
}
|
||||
if existing.TagsJSON == "" {
|
||||
updates["tags_json"] = item.TagsJSON
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if err := DB.Model(&existing).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("[OK] 应用定义种子已导入")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureSeedMedia(adminID uint, approvedDir string, item mediaSeed, bindID *uint) error {
|
||||
dst := filepath.Join(approvedDir, item.storedName)
|
||||
if err := ensureMediaLink(item.sourcePath, dst); err != nil {
|
||||
|
||||
@@ -173,6 +173,28 @@ var specialistRuleFileDrafts = map[string]string{
|
||||
- 不承诺工期、不承诺价格——这两项由销售与交付确认。
|
||||
- 不替研发承诺功能,不贬低竞品。`,
|
||||
|
||||
"presentation-briefing": `# 汇报专员
|
||||
|
||||
## 你是谁
|
||||
你负责把零散材料收成能讲、能交、能继续细化的汇报稿:经营汇报、方案汇报、培训课件、项目复盘。
|
||||
你面对的是马上要上台讲的人,他要的是一版结构清楚、页数合适、重点能讲明白的演示稿。
|
||||
|
||||
## 你怎么推进
|
||||
1. 先确认汇报对象、场景和页数限制:给管理层、给客户、给培训学员,写法完全不同。
|
||||
2. 先出结构,不急着写满内容:按「开场结论 → 现状 / 问题 → 方案 / 进展 → 风险 / 下一步」组织。
|
||||
3. 每页只保留一层主结论,宁可留白,不要把 Word 直接搬进 PPT。
|
||||
4. 最后补讲稿备注:每页写清楚要讲什么、强调什么、哪些数字必须口径一致。
|
||||
|
||||
## 输出要求
|
||||
- 先给页纲,再给每页要点,再补讲稿备注。
|
||||
- 所有数字、时间、范围要带口径;没有依据的地方写「待补」,不要自己补齐。
|
||||
- 如果页数明显超了,优先帮用户压缩,而不是机械地继续加页。
|
||||
|
||||
## 边界
|
||||
- 不编造数据、案例、客户反馈。
|
||||
- 不把未确认方案写成既定事实。
|
||||
- 视觉美化可以建议,但当前重点是结构与表达,不承诺设计成品。`,
|
||||
|
||||
"logistics-fulfillment": `# 履约跟单专员
|
||||
|
||||
## 你是谁
|
||||
@@ -249,6 +271,7 @@ var specialistSkillBindings = map[string][]string{
|
||||
"wechat-official-account": {"longform-writing", "copy-proofreading", "mind-map"},
|
||||
"contract-review": {"contract-review", "contract-brief", "batch-extract"},
|
||||
"solution-proposal": {"proposal-summary", "ppt-generation", "project-planning", "mind-map"},
|
||||
"presentation-briefing": {"ppt-generation", "proposal-summary", "mind-map", "progress-report"},
|
||||
"logistics-fulfillment": {"progress-report", "table-cleanup", "email-drafting"},
|
||||
"hr-email-sorter": {"email-drafting", "batch-extract", "table-cleanup", "ocr-understanding"},
|
||||
"resume-processor": {"ocr-understanding", "batch-extract", "table-cleanup", "interview-summary"},
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// seedSpecialistKeys 从 seedSpecialists 的字面量里抽专员 key。
|
||||
// 直接反射那份清单不方便(它建在函数体里),所以从源码文本抓——
|
||||
// 与 skill_keys_test.go 抓前端 availableSkills 是同一套路子。
|
||||
// 与 skill_keys_test.go 抓前端 staticSkillCatalog 是同一套路子。
|
||||
//
|
||||
// 必须限定在 seedSpecialists 到 seedSkillDefinitions 之间:seed.go 里
|
||||
// 技能、动作清单也用 Key: 字面量,而其中 contract-review / report-generation
|
||||
|
||||
@@ -12,7 +12,7 @@ func EnsureSpecialistStructuredRecords(item *model.Specialist) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
EnsureSpecialistRoleCard(item)
|
||||
EnsureSpecialistInteractionCard(item)
|
||||
if strings.TrimSpace(item.InputsRecordsJSON) == "" {
|
||||
item.InputsRecordsJSON = mustJSON(buildInputsRecords(item))
|
||||
}
|
||||
@@ -27,23 +27,23 @@ func EnsureSpecialistStructuredRecords(item *model.Specialist) {
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureSpecialistRoleCard(item *model.Specialist) {
|
||||
if item == nil || strings.TrimSpace(item.RoleCardJSON) != "" {
|
||||
return
|
||||
}
|
||||
item.RoleCardJSON = mustJSON(map[string]any{
|
||||
"name": item.Label,
|
||||
"tagline": firstNonEmpty(item.Summary, item.WorkStatus, item.Label),
|
||||
"greeting": "我是" + item.Label + ",已经准备好接手这条任务并继续推进。",
|
||||
"relationship_to_user": "你的协作搭档",
|
||||
"tone": "专业、直接、可执行",
|
||||
"opening_prompt": "告诉我当前事项、目标和约束,我先帮你起步。",
|
||||
"starter_prompts": defaultStarterPrompts(item),
|
||||
"boundaries": []string{
|
||||
"高风险动作会明确标记待确认",
|
||||
"需要人工审批的步骤不会默认自动执行",
|
||||
},
|
||||
})
|
||||
func EnsureSpecialistInteractionCard(item *model.Specialist) {
|
||||
if item == nil || strings.TrimSpace(item.InteractionCardJSON) != "" {
|
||||
return
|
||||
}
|
||||
item.InteractionCardJSON = mustJSON(map[string]any{
|
||||
"name": item.Label,
|
||||
"tagline": firstNonEmpty(item.Summary, item.WorkStatus, item.Label),
|
||||
"greeting": "我是" + item.Label + ",已经准备好接手这条任务并继续推进。",
|
||||
"relationship_to_user": "你的协作搭档",
|
||||
"tone": "专业、直接、可执行",
|
||||
"opening_prompt": "告诉我当前事项、目标和约束,我先帮你起步。",
|
||||
"starter_prompts": defaultStarterPrompts(item),
|
||||
"boundaries": []string{
|
||||
"高风险动作会明确标记待确认",
|
||||
"需要人工审批的步骤不会默认自动执行",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func ValidateStructuredRecordsJSON(value string) bool {
|
||||
@@ -56,21 +56,21 @@ func ValidateStructuredRecordsJSON(value string) bool {
|
||||
}
|
||||
|
||||
func ValidateJSONObjectJSON(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
var obj map[string]any
|
||||
return json.Unmarshal([]byte(value), &obj) == nil
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
var obj map[string]any
|
||||
return json.Unmarshal([]byte(value), &obj) == nil
|
||||
}
|
||||
|
||||
func ValidateJSONStringArray(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
var arr []string
|
||||
return json.Unmarshal([]byte(value), &arr) == nil
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
var arr []string
|
||||
return json.Unmarshal([]byte(value), &arr) == nil
|
||||
}
|
||||
|
||||
func buildInputsRecords(item *model.Specialist) []map[string]any {
|
||||
@@ -216,20 +216,20 @@ func buildResultRecords(item *model.Specialist) []map[string]any {
|
||||
}
|
||||
|
||||
func defaultStarterPrompts(item *model.Specialist) []string {
|
||||
prompts := make([]string, 0, 3)
|
||||
for _, skill := range splitText(item.BaseSkills) {
|
||||
prompts = append(prompts, "帮我处理:"+skill)
|
||||
if len(prompts) == 3 {
|
||||
return prompts
|
||||
}
|
||||
}
|
||||
if item.Summary != "" {
|
||||
prompts = append(prompts, "继续推进这条事项")
|
||||
}
|
||||
if len(prompts) == 0 {
|
||||
prompts = []string{"先帮我梳理当前事项", "告诉我下一步怎么做", "先看有哪些待确认点"}
|
||||
}
|
||||
return prompts
|
||||
prompts := make([]string, 0, 3)
|
||||
for _, skill := range splitText(item.BaseSkills) {
|
||||
prompts = append(prompts, "帮我处理:"+skill)
|
||||
if len(prompts) == 3 {
|
||||
return prompts
|
||||
}
|
||||
}
|
||||
if item.Summary != "" {
|
||||
prompts = append(prompts, "继续推进这条事项")
|
||||
}
|
||||
if len(prompts) == 0 {
|
||||
prompts = []string{"先帮我梳理当前事项", "告诉我下一步怎么做", "先看有哪些待确认点"}
|
||||
}
|
||||
return prompts
|
||||
}
|
||||
|
||||
func mustJSON(v any) string {
|
||||
|
||||
Reference in New Issue
Block a user