diff --git a/eai_agentplatform/backend-go/internal/api/app_definition.go b/eai_agentplatform/backend-go/internal/api/app_definition.go
new file mode 100644
index 0000000..2cb6408
--- /dev/null
+++ b/eai_agentplatform/backend-go/internal/api/app_definition.go
@@ -0,0 +1,233 @@
+package api
+
+import (
+ "strings"
+
+ "github.com/gin-gonic/gin"
+
+ "eai_agentplatform/backend/internal/model"
+ "eai_agentplatform/backend/internal/store"
+ "eai_agentplatform/backend/internal/web"
+)
+
+type appDefinitionReq struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+ Badge string `json:"badge"`
+ Kind string `json:"kind"`
+ MarketTag string `json:"market_tag"`
+ WorkerType string `json:"worker_type"`
+ Tier string `json:"tier"`
+ Source string `json:"source"`
+ Color string `json:"color"`
+ IconText string `json:"icon_text"`
+ CoverTone string `json:"cover_tone"`
+ Summary string `json:"summary"`
+ Description string `json:"description"`
+ OpenRoute string `json:"open_route"`
+ SpecialistKey string `json:"specialist_key"`
+ SkillKey string `json:"skill_key"`
+ DefaultPrompt string `json:"default_prompt"`
+ PromptsJSON string `json:"prompts_json"`
+ TagsJSON string `json:"tags_json"`
+ InstallState string `json:"install_state"`
+ ExposedToUser bool `json:"exposed_to_user"`
+ State string `json:"state"`
+ SortOrder int `json:"sort_order"`
+}
+
+func normalizeAppDefinitionReq(req *appDefinitionReq) {
+ req.Key = strings.TrimSpace(req.Key)
+ req.Label = strings.TrimSpace(req.Label)
+ req.Badge = strings.TrimSpace(req.Badge)
+ req.Kind = strings.TrimSpace(req.Kind)
+ req.MarketTag = strings.TrimSpace(req.MarketTag)
+ req.WorkerType = strings.TrimSpace(req.WorkerType)
+ req.Tier = strings.TrimSpace(req.Tier)
+ req.Source = strings.TrimSpace(req.Source)
+ req.Color = strings.TrimSpace(req.Color)
+ req.IconText = strings.TrimSpace(req.IconText)
+ req.CoverTone = strings.TrimSpace(req.CoverTone)
+ req.Summary = strings.TrimSpace(req.Summary)
+ req.Description = strings.TrimSpace(req.Description)
+ req.OpenRoute = strings.TrimSpace(req.OpenRoute)
+ req.SpecialistKey = strings.TrimSpace(req.SpecialistKey)
+ req.SkillKey = strings.TrimSpace(req.SkillKey)
+ req.DefaultPrompt = strings.TrimSpace(req.DefaultPrompt)
+ req.PromptsJSON = strings.TrimSpace(req.PromptsJSON)
+ req.TagsJSON = strings.TrimSpace(req.TagsJSON)
+ req.InstallState = strings.TrimSpace(req.InstallState)
+ req.State = strings.TrimSpace(req.State)
+}
+
+func validateAppDefinitionReq(req *appDefinitionReq) *web.AppError {
+ normalizeAppDefinitionReq(req)
+ if req.Key == "" || req.Label == "" {
+ return web.NewBadRequest("key、label 为必填")
+ }
+ if req.WorkerType == "" {
+ req.WorkerType = "worker"
+ }
+ if req.Tier == "" {
+ req.Tier = "business"
+ }
+ if req.Source == "" {
+ req.Source = "eai"
+ }
+ if req.InstallState == "" {
+ req.InstallState = "installed"
+ }
+ if req.State == "" {
+ req.State = "active"
+ }
+ if req.State != "active" && req.State != "inactive" {
+ return web.NewBadRequest("state 只能是 active 或 inactive")
+ }
+ if !store.ValidateJSONStringArray(req.PromptsJSON) {
+ return web.NewBadRequest("prompts_json 必须是字符串数组")
+ }
+ if !store.ValidateJSONStringArray(req.TagsJSON) {
+ return web.NewBadRequest("tags_json 必须是字符串数组")
+ }
+ return nil
+}
+
+func ListAppDefinitions(c *gin.Context) {
+ q := store.DB.Model(&model.AppDefinition{})
+ if c.Query("state") == "" {
+ q = q.Where("state = ?", "active")
+ } else {
+ q = q.Where("state = ?", c.Query("state"))
+ }
+ if c.Query("exposed_to_user") != "" {
+ q = q.Where("exposed_to_user = ?", c.Query("exposed_to_user") == "true")
+ }
+ var items []model.AppDefinition
+ if err := q.Order("sort_order ASC, id ASC").Find(&items).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("查询应用定义失败"))
+ return
+ }
+ web.OK(c, items)
+}
+
+func GetAppDefinitionByKey(c *gin.Context) {
+ key := strings.TrimSpace(c.Param("key"))
+ if key == "" {
+ web.Fail(c, web.NewBadRequest("应用 key 不能为空"))
+ return
+ }
+ var item model.AppDefinition
+ if err := store.DB.Where("key = ?", key).First(&item).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("应用定义不存在"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func CreateAppDefinition(c *gin.Context) {
+ var req appDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateAppDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item := model.AppDefinition{
+ Key: req.Key,
+ Label: req.Label,
+ Badge: req.Badge,
+ Kind: req.Kind,
+ MarketTag: req.MarketTag,
+ WorkerType: req.WorkerType,
+ Tier: req.Tier,
+ Source: req.Source,
+ Color: req.Color,
+ IconText: req.IconText,
+ CoverTone: req.CoverTone,
+ Summary: req.Summary,
+ Description: req.Description,
+ OpenRoute: req.OpenRoute,
+ SpecialistKey: req.SpecialistKey,
+ SkillKey: req.SkillKey,
+ DefaultPrompt: req.DefaultPrompt,
+ PromptsJSON: req.PromptsJSON,
+ TagsJSON: req.TagsJSON,
+ InstallState: req.InstallState,
+ ExposedToUser: req.ExposedToUser,
+ State: req.State,
+ SortOrder: req.SortOrder,
+ }
+ if err := store.DB.Create(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("创建应用定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func UpdateAppDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.AppDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("应用定义不存在"))
+ return
+ }
+ var req appDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateAppDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item.Key = req.Key
+ item.Label = req.Label
+ item.Badge = req.Badge
+ item.Kind = req.Kind
+ item.MarketTag = req.MarketTag
+ item.WorkerType = req.WorkerType
+ item.Tier = req.Tier
+ item.Source = req.Source
+ item.Color = req.Color
+ item.IconText = req.IconText
+ item.CoverTone = req.CoverTone
+ item.Summary = req.Summary
+ item.Description = req.Description
+ item.OpenRoute = req.OpenRoute
+ item.SpecialistKey = req.SpecialistKey
+ item.SkillKey = req.SkillKey
+ item.DefaultPrompt = req.DefaultPrompt
+ item.PromptsJSON = req.PromptsJSON
+ item.TagsJSON = req.TagsJSON
+ item.InstallState = req.InstallState
+ item.ExposedToUser = req.ExposedToUser
+ item.State = req.State
+ item.SortOrder = req.SortOrder
+ if err := store.DB.Save(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("更新应用定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func DeleteAppDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.AppDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("应用定义不存在"))
+ return
+ }
+ if err := store.DB.Delete(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("删除应用定义失败"))
+ return
+ }
+ web.OK(c, gin.H{"id": id, "deleted": true})
+}
diff --git a/eai_agentplatform/backend-go/internal/api/capability_definition.go b/eai_agentplatform/backend-go/internal/api/capability_definition.go
deleted file mode 100644
index e3221aa..0000000
--- a/eai_agentplatform/backend-go/internal/api/capability_definition.go
+++ /dev/null
@@ -1,413 +0,0 @@
-package api
-
-import (
- "strings"
-
- "github.com/gin-gonic/gin"
-
- "eai_agentplatform/backend/internal/model"
- "eai_agentplatform/backend/internal/store"
- "eai_agentplatform/backend/internal/web"
-)
-
-type skillDefinitionReq struct {
- Key string `json:"key"`
- Label string `json:"label"`
- Description string `json:"description"`
- RoleKind string `json:"role_kind"`
- Source string `json:"source"`
- ObjectEntryRoute string `json:"object_entry_route"`
- LegacyObjectEntryRoute string `json:"legacy_object_entry_route"`
- ExposedToUser bool `json:"exposed_to_user"`
- StarterPromptsJSON string `json:"starter_prompts_json"`
- PromptTemplate string `json:"prompt_template"`
- InputSchemaJSON string `json:"input_schema_json"`
- OutputSchemaJSON string `json:"output_schema_json"`
- ArtifactSchemaJSON string `json:"artifact_schema_json"`
- ActionRefsJSON string `json:"action_refs_json"`
- PolicyRefsJSON string `json:"policy_refs_json"`
- OntologyBindingJSON string `json:"ontology_binding_json"`
- State string `json:"state"`
- SortOrder int `json:"sort_order"`
-}
-
-type actionDefinitionReq struct {
- Key string `json:"key"`
- Label string `json:"label"`
- Description string `json:"description"`
- ActionType string `json:"action_type"`
- ConnectorRef string `json:"connector_ref"`
- InputSchemaJSON string `json:"input_schema_json"`
- OutputSchemaJSON string `json:"output_schema_json"`
- RiskLevel string `json:"risk_level"`
- ApprovalMode string `json:"approval_mode"`
- AuditLevel string `json:"audit_level"`
- ExposedToUser bool `json:"exposed_to_user"`
- OntologyBindingJSON string `json:"ontology_binding_json"`
- State string `json:"state"`
- SortOrder int `json:"sort_order"`
-}
-
-func normalizeSkillDefinitionReq(req *skillDefinitionReq) {
- req.Key = strings.TrimSpace(req.Key)
- req.Label = strings.TrimSpace(req.Label)
- req.Description = strings.TrimSpace(req.Description)
- req.RoleKind = strings.TrimSpace(req.RoleKind)
- req.Source = strings.TrimSpace(req.Source)
- req.ObjectEntryRoute = strings.TrimSpace(req.ObjectEntryRoute)
- req.LegacyObjectEntryRoute = strings.TrimSpace(req.LegacyObjectEntryRoute)
- req.StarterPromptsJSON = strings.TrimSpace(req.StarterPromptsJSON)
- req.PromptTemplate = strings.TrimSpace(req.PromptTemplate)
- req.InputSchemaJSON = strings.TrimSpace(req.InputSchemaJSON)
- req.OutputSchemaJSON = strings.TrimSpace(req.OutputSchemaJSON)
- req.ArtifactSchemaJSON = strings.TrimSpace(req.ArtifactSchemaJSON)
- req.ActionRefsJSON = strings.TrimSpace(req.ActionRefsJSON)
- req.PolicyRefsJSON = strings.TrimSpace(req.PolicyRefsJSON)
- req.OntologyBindingJSON = strings.TrimSpace(req.OntologyBindingJSON)
- req.State = strings.TrimSpace(req.State)
-}
-
-func validateSkillDefinitionReq(req *skillDefinitionReq) *web.AppError {
- normalizeSkillDefinitionReq(req)
- if req.Key == "" || req.Label == "" {
- return web.NewBadRequest("key、label 为必填")
- }
- if req.RoleKind == "" {
- req.RoleKind = "skill"
- }
- switch req.RoleKind {
- case "assistant", "specialist", "skill":
- default:
- return web.NewBadRequest("role_kind 只能是 assistant、specialist 或 skill")
- }
- if req.Source == "" {
- req.Source = "eai"
- }
- if req.State == "" {
- req.State = "active"
- }
- if req.State != "active" && req.State != "inactive" {
- return web.NewBadRequest("state 只能是 active 或 inactive")
- }
- if !store.ValidateJSONStringArray(req.StarterPromptsJSON) {
- return web.NewBadRequest("starter_prompts_json 必须是字符串数组")
- }
- if !store.ValidateJSONObjectJSON(req.InputSchemaJSON) {
- return web.NewBadRequest("input_schema_json 必须是 JSON 对象")
- }
- if !store.ValidateJSONObjectJSON(req.OutputSchemaJSON) {
- return web.NewBadRequest("output_schema_json 必须是 JSON 对象")
- }
- if !store.ValidateJSONObjectJSON(req.ArtifactSchemaJSON) {
- return web.NewBadRequest("artifact_schema_json 必须是 JSON 对象")
- }
- if !store.ValidateJSONStringArray(req.ActionRefsJSON) {
- return web.NewBadRequest("action_refs_json 必须是字符串数组")
- }
- if !store.ValidateJSONStringArray(req.PolicyRefsJSON) {
- return web.NewBadRequest("policy_refs_json 必须是字符串数组")
- }
- if !store.ValidateJSONObjectJSON(req.OntologyBindingJSON) {
- return web.NewBadRequest("ontology_binding_json 必须是 JSON 对象")
- }
- return nil
-}
-
-func normalizeActionDefinitionReq(req *actionDefinitionReq) {
- req.Key = strings.TrimSpace(req.Key)
- req.Label = strings.TrimSpace(req.Label)
- req.Description = strings.TrimSpace(req.Description)
- req.ActionType = strings.TrimSpace(req.ActionType)
- req.ConnectorRef = strings.TrimSpace(req.ConnectorRef)
- req.InputSchemaJSON = strings.TrimSpace(req.InputSchemaJSON)
- req.OutputSchemaJSON = strings.TrimSpace(req.OutputSchemaJSON)
- req.RiskLevel = strings.TrimSpace(req.RiskLevel)
- req.ApprovalMode = strings.TrimSpace(req.ApprovalMode)
- req.AuditLevel = strings.TrimSpace(req.AuditLevel)
- req.OntologyBindingJSON = strings.TrimSpace(req.OntologyBindingJSON)
- req.State = strings.TrimSpace(req.State)
-}
-
-func validateActionDefinitionReq(req *actionDefinitionReq) *web.AppError {
- normalizeActionDefinitionReq(req)
- if req.Key == "" || req.Label == "" {
- return web.NewBadRequest("key、label 为必填")
- }
- if req.ActionType == "" {
- req.ActionType = "execution"
- }
- if req.RiskLevel == "" {
- req.RiskLevel = "low"
- }
- if req.ApprovalMode == "" {
- req.ApprovalMode = "not_required"
- }
- if req.AuditLevel == "" {
- req.AuditLevel = "standard"
- }
- if req.State == "" {
- req.State = "active"
- }
- if req.State != "active" && req.State != "inactive" {
- return web.NewBadRequest("state 只能是 active 或 inactive")
- }
- if !store.ValidateJSONObjectJSON(req.InputSchemaJSON) {
- return web.NewBadRequest("input_schema_json 必须是 JSON 对象")
- }
- if !store.ValidateJSONObjectJSON(req.OutputSchemaJSON) {
- return web.NewBadRequest("output_schema_json 必须是 JSON 对象")
- }
- if !store.ValidateJSONObjectJSON(req.OntologyBindingJSON) {
- return web.NewBadRequest("ontology_binding_json 必须是 JSON 对象")
- }
- return nil
-}
-
-func ListSkillDefinitions(c *gin.Context) {
- q := store.DB.Model(&model.SkillDefinition{})
- if c.Query("state") == "" {
- q = q.Where("state = ?", "active")
- } else {
- q = q.Where("state = ?", c.Query("state"))
- }
- if c.Query("exposed_to_user") != "" {
- q = q.Where("exposed_to_user = ?", c.Query("exposed_to_user") == "true")
- }
- var items []model.SkillDefinition
- if err := q.Order("sort_order ASC, id ASC").Find(&items).Error; err != nil {
- web.Fail(c, web.NewBadRequest("查询技能定义失败"))
- return
- }
- web.OK(c, items)
-}
-
-func GetSkillDefinitionByKey(c *gin.Context) {
- key := strings.TrimSpace(c.Param("key"))
- if key == "" {
- web.Fail(c, web.NewBadRequest("技能 key 不能为空"))
- return
- }
- var item model.SkillDefinition
- if err := store.DB.Where("key = ?", key).First(&item).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("技能定义不存在"))
- return
- }
- web.OK(c, item)
-}
-
-func CreateSkillDefinition(c *gin.Context) {
- var req skillDefinitionReq
- if err := c.ShouldBindJSON(&req); err != nil {
- web.Fail(c, web.NewBadRequest("请求参数错误"))
- return
- }
- if appErr := validateSkillDefinitionReq(&req); appErr != nil {
- web.Fail(c, appErr)
- return
- }
- item := model.SkillDefinition{
- Key: req.Key,
- Label: req.Label,
- Description: req.Description,
- RoleKind: req.RoleKind,
- Source: req.Source,
- ObjectEntryRoute: req.ObjectEntryRoute,
- LegacyObjectEntryRoute: req.LegacyObjectEntryRoute,
- ExposedToUser: req.ExposedToUser,
- StarterPromptsJSON: req.StarterPromptsJSON,
- PromptTemplate: req.PromptTemplate,
- InputSchemaJSON: req.InputSchemaJSON,
- OutputSchemaJSON: req.OutputSchemaJSON,
- ArtifactSchemaJSON: req.ArtifactSchemaJSON,
- ActionRefsJSON: req.ActionRefsJSON,
- PolicyRefsJSON: req.PolicyRefsJSON,
- OntologyBindingJSON: req.OntologyBindingJSON,
- State: req.State,
- SortOrder: req.SortOrder,
- }
- if err := store.DB.Create(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("创建技能定义失败"))
- return
- }
- web.OK(c, item)
-}
-
-func UpdateSkillDefinition(c *gin.Context) {
- id, ok := parseID(c, "id")
- if !ok {
- return
- }
- var item model.SkillDefinition
- if err := store.DB.First(&item, id).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("技能定义不存在"))
- return
- }
- var req skillDefinitionReq
- if err := c.ShouldBindJSON(&req); err != nil {
- web.Fail(c, web.NewBadRequest("请求参数错误"))
- return
- }
- if appErr := validateSkillDefinitionReq(&req); appErr != nil {
- web.Fail(c, appErr)
- return
- }
- item.Key = req.Key
- item.Label = req.Label
- item.Description = req.Description
- item.RoleKind = req.RoleKind
- item.Source = req.Source
- item.ObjectEntryRoute = req.ObjectEntryRoute
- item.LegacyObjectEntryRoute = req.LegacyObjectEntryRoute
- item.ExposedToUser = req.ExposedToUser
- item.StarterPromptsJSON = req.StarterPromptsJSON
- item.PromptTemplate = req.PromptTemplate
- item.InputSchemaJSON = req.InputSchemaJSON
- item.OutputSchemaJSON = req.OutputSchemaJSON
- item.ArtifactSchemaJSON = req.ArtifactSchemaJSON
- item.ActionRefsJSON = req.ActionRefsJSON
- item.PolicyRefsJSON = req.PolicyRefsJSON
- item.OntologyBindingJSON = req.OntologyBindingJSON
- item.State = req.State
- item.SortOrder = req.SortOrder
- if err := store.DB.Save(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("更新技能定义失败"))
- return
- }
- web.OK(c, item)
-}
-
-func DeleteSkillDefinition(c *gin.Context) {
- id, ok := parseID(c, "id")
- if !ok {
- return
- }
- var item model.SkillDefinition
- if err := store.DB.First(&item, id).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("技能定义不存在"))
- return
- }
- if err := store.DB.Delete(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("删除技能定义失败"))
- return
- }
- web.OK(c, gin.H{"id": id, "deleted": true})
-}
-
-func ListActionDefinitions(c *gin.Context) {
- q := store.DB.Model(&model.ActionDefinition{})
- if c.Query("state") == "" {
- q = q.Where("state = ?", "active")
- } else {
- q = q.Where("state = ?", c.Query("state"))
- }
- var items []model.ActionDefinition
- if err := q.Order("sort_order ASC, id ASC").Find(&items).Error; err != nil {
- web.Fail(c, web.NewBadRequest("查询 Action 定义失败"))
- return
- }
- web.OK(c, items)
-}
-
-func GetActionDefinitionByKey(c *gin.Context) {
- key := strings.TrimSpace(c.Param("key"))
- if key == "" {
- web.Fail(c, web.NewBadRequest("action key 不能为空"))
- return
- }
- var item model.ActionDefinition
- if err := store.DB.Where("key = ?", key).First(&item).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
- return
- }
- web.OK(c, item)
-}
-
-func CreateActionDefinition(c *gin.Context) {
- var req actionDefinitionReq
- if err := c.ShouldBindJSON(&req); err != nil {
- web.Fail(c, web.NewBadRequest("请求参数错误"))
- return
- }
- if appErr := validateActionDefinitionReq(&req); appErr != nil {
- web.Fail(c, appErr)
- return
- }
- item := model.ActionDefinition{
- Key: req.Key,
- Label: req.Label,
- Description: req.Description,
- ActionType: req.ActionType,
- ConnectorRef: req.ConnectorRef,
- InputSchemaJSON: req.InputSchemaJSON,
- OutputSchemaJSON: req.OutputSchemaJSON,
- RiskLevel: req.RiskLevel,
- ApprovalMode: req.ApprovalMode,
- AuditLevel: req.AuditLevel,
- ExposedToUser: req.ExposedToUser,
- OntologyBindingJSON: req.OntologyBindingJSON,
- State: req.State,
- SortOrder: req.SortOrder,
- }
- if err := store.DB.Create(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("创建 Action 定义失败"))
- return
- }
- web.OK(c, item)
-}
-
-func UpdateActionDefinition(c *gin.Context) {
- id, ok := parseID(c, "id")
- if !ok {
- return
- }
- var item model.ActionDefinition
- if err := store.DB.First(&item, id).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
- return
- }
- var req actionDefinitionReq
- if err := c.ShouldBindJSON(&req); err != nil {
- web.Fail(c, web.NewBadRequest("请求参数错误"))
- return
- }
- if appErr := validateActionDefinitionReq(&req); appErr != nil {
- web.Fail(c, appErr)
- return
- }
- item.Key = req.Key
- item.Label = req.Label
- item.Description = req.Description
- item.ActionType = req.ActionType
- item.ConnectorRef = req.ConnectorRef
- item.InputSchemaJSON = req.InputSchemaJSON
- item.OutputSchemaJSON = req.OutputSchemaJSON
- item.RiskLevel = req.RiskLevel
- item.ApprovalMode = req.ApprovalMode
- item.AuditLevel = req.AuditLevel
- item.ExposedToUser = req.ExposedToUser
- item.OntologyBindingJSON = req.OntologyBindingJSON
- item.State = req.State
- item.SortOrder = req.SortOrder
- if err := store.DB.Save(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("更新 Action 定义失败"))
- return
- }
- web.OK(c, item)
-}
-
-func DeleteActionDefinition(c *gin.Context) {
- id, ok := parseID(c, "id")
- if !ok {
- return
- }
- var item model.ActionDefinition
- if err := store.DB.First(&item, id).Error; err != nil {
- web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
- return
- }
- if err := store.DB.Delete(&item).Error; err != nil {
- web.Fail(c, web.NewBadRequest("删除 Action 定义失败"))
- return
- }
- web.OK(c, gin.H{"id": id, "deleted": true})
-}
diff --git a/eai_agentplatform/backend-go/internal/api/router.go b/eai_agentplatform/backend-go/internal/api/router.go
index 5d3125c..e9e7e5a 100644
--- a/eai_agentplatform/backend-go/internal/api/router.go
+++ b/eai_agentplatform/backend-go/internal/api/router.go
@@ -45,6 +45,8 @@ func RegisterRoutes(r *gin.Engine, cfg *config.Config) {
r.GET("/api/specialists/summary", middleware.Auth(cfg), SpecialistSummary)
r.GET("/api/skills", middleware.Auth(cfg), ListSkillDefinitions)
r.GET("/api/skills/by-key/:key", middleware.Auth(cfg), GetSkillDefinitionByKey)
+ r.GET("/api/apps", middleware.Auth(cfg), ListAppDefinitions)
+ r.GET("/api/apps/by-key/:key", middleware.Auth(cfg), GetAppDefinitionByKey)
r.GET("/api/actions", middleware.Auth(cfg), ListActionDefinitions)
r.GET("/api/actions/by-key/:key", middleware.Auth(cfg), GetActionDefinitionByKey)
r.GET("/api/workbench/overview", middleware.Auth(cfg), WorkbenchOverview)
@@ -205,6 +207,9 @@ func RegisterRoutes(r *gin.Engine, cfg *config.Config) {
admin.POST("/skills", CreateSkillDefinition)
admin.PUT("/skills/:id", UpdateSkillDefinition)
admin.DELETE("/skills/:id", DeleteSkillDefinition)
+ admin.POST("/apps", CreateAppDefinition)
+ admin.PUT("/apps/:id", UpdateAppDefinition)
+ admin.DELETE("/apps/:id", DeleteAppDefinition)
admin.POST("/actions", CreateActionDefinition)
admin.PUT("/actions/:id", UpdateActionDefinition)
admin.DELETE("/actions/:id", DeleteActionDefinition)
diff --git a/eai_agentplatform/backend-go/internal/api/skill_action_definition.go b/eai_agentplatform/backend-go/internal/api/skill_action_definition.go
new file mode 100644
index 0000000..65ccd26
--- /dev/null
+++ b/eai_agentplatform/backend-go/internal/api/skill_action_definition.go
@@ -0,0 +1,409 @@
+package api
+
+import (
+ "strings"
+
+ "github.com/gin-gonic/gin"
+
+ "eai_agentplatform/backend/internal/model"
+ "eai_agentplatform/backend/internal/store"
+ "eai_agentplatform/backend/internal/web"
+)
+
+type skillDefinitionReq struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+ Description string `json:"description"`
+ ObjectKind string `json:"object_kind"`
+ Source string `json:"source"`
+ ObjectEntryRoute string `json:"object_entry_route"`
+ ExposedToUser bool `json:"exposed_to_user"`
+ StarterPromptsJSON string `json:"starter_prompts_json"`
+ PromptTemplate string `json:"prompt_template"`
+ InputSchemaJSON string `json:"input_schema_json"`
+ OutputSchemaJSON string `json:"output_schema_json"`
+ ArtifactSchemaJSON string `json:"artifact_schema_json"`
+ ActionRefsJSON string `json:"action_refs_json"`
+ PolicyRefsJSON string `json:"policy_refs_json"`
+ OntologyBindingJSON string `json:"ontology_binding_json"`
+ State string `json:"state"`
+ SortOrder int `json:"sort_order"`
+}
+
+type actionDefinitionReq struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+ Description string `json:"description"`
+ ActionType string `json:"action_type"`
+ ConnectorRef string `json:"connector_ref"`
+ InputSchemaJSON string `json:"input_schema_json"`
+ OutputSchemaJSON string `json:"output_schema_json"`
+ RiskLevel string `json:"risk_level"`
+ ApprovalMode string `json:"approval_mode"`
+ AuditLevel string `json:"audit_level"`
+ ExposedToUser bool `json:"exposed_to_user"`
+ OntologyBindingJSON string `json:"ontology_binding_json"`
+ State string `json:"state"`
+ SortOrder int `json:"sort_order"`
+}
+
+func normalizeSkillDefinitionReq(req *skillDefinitionReq) {
+ req.Key = strings.TrimSpace(req.Key)
+ req.Label = strings.TrimSpace(req.Label)
+ req.Description = strings.TrimSpace(req.Description)
+ req.ObjectKind = strings.TrimSpace(req.ObjectKind)
+ req.Source = strings.TrimSpace(req.Source)
+ req.ObjectEntryRoute = strings.TrimSpace(req.ObjectEntryRoute)
+ req.StarterPromptsJSON = strings.TrimSpace(req.StarterPromptsJSON)
+ req.PromptTemplate = strings.TrimSpace(req.PromptTemplate)
+ req.InputSchemaJSON = strings.TrimSpace(req.InputSchemaJSON)
+ req.OutputSchemaJSON = strings.TrimSpace(req.OutputSchemaJSON)
+ req.ArtifactSchemaJSON = strings.TrimSpace(req.ArtifactSchemaJSON)
+ req.ActionRefsJSON = strings.TrimSpace(req.ActionRefsJSON)
+ req.PolicyRefsJSON = strings.TrimSpace(req.PolicyRefsJSON)
+ req.OntologyBindingJSON = strings.TrimSpace(req.OntologyBindingJSON)
+ req.State = strings.TrimSpace(req.State)
+}
+
+func validateSkillDefinitionReq(req *skillDefinitionReq) *web.AppError {
+ normalizeSkillDefinitionReq(req)
+ if req.Key == "" || req.Label == "" {
+ return web.NewBadRequest("key、label 为必填")
+ }
+ if req.ObjectKind == "" {
+ req.ObjectKind = "skill"
+ }
+ switch req.ObjectKind {
+ case "assistant", "specialist", "skill":
+ default:
+ return web.NewBadRequest("object_kind 只能是 assistant、specialist 或 skill")
+ }
+ if req.Source == "" {
+ req.Source = "eai"
+ }
+ if req.State == "" {
+ req.State = "active"
+ }
+ if req.State != "active" && req.State != "inactive" {
+ return web.NewBadRequest("state 只能是 active 或 inactive")
+ }
+ if !store.ValidateJSONStringArray(req.StarterPromptsJSON) {
+ return web.NewBadRequest("starter_prompts_json 必须是字符串数组")
+ }
+ if !store.ValidateJSONObjectJSON(req.InputSchemaJSON) {
+ return web.NewBadRequest("input_schema_json 必须是 JSON 对象")
+ }
+ if !store.ValidateJSONObjectJSON(req.OutputSchemaJSON) {
+ return web.NewBadRequest("output_schema_json 必须是 JSON 对象")
+ }
+ if !store.ValidateJSONObjectJSON(req.ArtifactSchemaJSON) {
+ return web.NewBadRequest("artifact_schema_json 必须是 JSON 对象")
+ }
+ if !store.ValidateJSONStringArray(req.ActionRefsJSON) {
+ return web.NewBadRequest("action_refs_json 必须是字符串数组")
+ }
+ if !store.ValidateJSONStringArray(req.PolicyRefsJSON) {
+ return web.NewBadRequest("policy_refs_json 必须是字符串数组")
+ }
+ if !store.ValidateJSONObjectJSON(req.OntologyBindingJSON) {
+ return web.NewBadRequest("ontology_binding_json 必须是 JSON 对象")
+ }
+ return nil
+}
+
+func normalizeActionDefinitionReq(req *actionDefinitionReq) {
+ req.Key = strings.TrimSpace(req.Key)
+ req.Label = strings.TrimSpace(req.Label)
+ req.Description = strings.TrimSpace(req.Description)
+ req.ActionType = strings.TrimSpace(req.ActionType)
+ req.ConnectorRef = strings.TrimSpace(req.ConnectorRef)
+ req.InputSchemaJSON = strings.TrimSpace(req.InputSchemaJSON)
+ req.OutputSchemaJSON = strings.TrimSpace(req.OutputSchemaJSON)
+ req.RiskLevel = strings.TrimSpace(req.RiskLevel)
+ req.ApprovalMode = strings.TrimSpace(req.ApprovalMode)
+ req.AuditLevel = strings.TrimSpace(req.AuditLevel)
+ req.OntologyBindingJSON = strings.TrimSpace(req.OntologyBindingJSON)
+ req.State = strings.TrimSpace(req.State)
+}
+
+func validateActionDefinitionReq(req *actionDefinitionReq) *web.AppError {
+ normalizeActionDefinitionReq(req)
+ if req.Key == "" || req.Label == "" {
+ return web.NewBadRequest("key、label 为必填")
+ }
+ if req.ActionType == "" {
+ req.ActionType = "execution"
+ }
+ if req.RiskLevel == "" {
+ req.RiskLevel = "low"
+ }
+ if req.ApprovalMode == "" {
+ req.ApprovalMode = "not_required"
+ }
+ if req.AuditLevel == "" {
+ req.AuditLevel = "standard"
+ }
+ if req.State == "" {
+ req.State = "active"
+ }
+ if req.State != "active" && req.State != "inactive" {
+ return web.NewBadRequest("state 只能是 active 或 inactive")
+ }
+ if !store.ValidateJSONObjectJSON(req.InputSchemaJSON) {
+ return web.NewBadRequest("input_schema_json 必须是 JSON 对象")
+ }
+ if !store.ValidateJSONObjectJSON(req.OutputSchemaJSON) {
+ return web.NewBadRequest("output_schema_json 必须是 JSON 对象")
+ }
+ if !store.ValidateJSONObjectJSON(req.OntologyBindingJSON) {
+ return web.NewBadRequest("ontology_binding_json 必须是 JSON 对象")
+ }
+ return nil
+}
+
+func ListSkillDefinitions(c *gin.Context) {
+ q := store.DB.Model(&model.SkillDefinition{})
+ if c.Query("state") == "" {
+ q = q.Where("state = ?", "active")
+ } else {
+ q = q.Where("state = ?", c.Query("state"))
+ }
+ if c.Query("exposed_to_user") != "" {
+ q = q.Where("exposed_to_user = ?", c.Query("exposed_to_user") == "true")
+ }
+ var items []model.SkillDefinition
+ if err := q.Order("sort_order ASC, id ASC").Find(&items).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("查询技能定义失败"))
+ return
+ }
+ web.OK(c, items)
+}
+
+func GetSkillDefinitionByKey(c *gin.Context) {
+ key := strings.TrimSpace(c.Param("key"))
+ if key == "" {
+ web.Fail(c, web.NewBadRequest("技能 key 不能为空"))
+ return
+ }
+ var item model.SkillDefinition
+ if err := store.DB.Where("key = ?", key).First(&item).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("技能定义不存在"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func CreateSkillDefinition(c *gin.Context) {
+ var req skillDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateSkillDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item := model.SkillDefinition{
+ Key: req.Key,
+ Label: req.Label,
+ Description: req.Description,
+ ObjectKind: req.ObjectKind,
+ Source: req.Source,
+ ObjectEntryRoute: req.ObjectEntryRoute,
+ ExposedToUser: req.ExposedToUser,
+ StarterPromptsJSON: req.StarterPromptsJSON,
+ PromptTemplate: req.PromptTemplate,
+ InputSchemaJSON: req.InputSchemaJSON,
+ OutputSchemaJSON: req.OutputSchemaJSON,
+ ArtifactSchemaJSON: req.ArtifactSchemaJSON,
+ ActionRefsJSON: req.ActionRefsJSON,
+ PolicyRefsJSON: req.PolicyRefsJSON,
+ OntologyBindingJSON: req.OntologyBindingJSON,
+ State: req.State,
+ SortOrder: req.SortOrder,
+ }
+ if err := store.DB.Create(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("创建技能定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func UpdateSkillDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.SkillDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("技能定义不存在"))
+ return
+ }
+ var req skillDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateSkillDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item.Key = req.Key
+ item.Label = req.Label
+ item.Description = req.Description
+ item.ObjectKind = req.ObjectKind
+ item.Source = req.Source
+ item.ObjectEntryRoute = req.ObjectEntryRoute
+ item.ExposedToUser = req.ExposedToUser
+ item.StarterPromptsJSON = req.StarterPromptsJSON
+ item.PromptTemplate = req.PromptTemplate
+ item.InputSchemaJSON = req.InputSchemaJSON
+ item.OutputSchemaJSON = req.OutputSchemaJSON
+ item.ArtifactSchemaJSON = req.ArtifactSchemaJSON
+ item.ActionRefsJSON = req.ActionRefsJSON
+ item.PolicyRefsJSON = req.PolicyRefsJSON
+ item.OntologyBindingJSON = req.OntologyBindingJSON
+ item.State = req.State
+ item.SortOrder = req.SortOrder
+ if err := store.DB.Save(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("更新技能定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func DeleteSkillDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.SkillDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("技能定义不存在"))
+ return
+ }
+ if err := store.DB.Delete(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("删除技能定义失败"))
+ return
+ }
+ web.OK(c, gin.H{"id": id, "deleted": true})
+}
+
+func ListActionDefinitions(c *gin.Context) {
+ q := store.DB.Model(&model.ActionDefinition{})
+ if c.Query("state") == "" {
+ q = q.Where("state = ?", "active")
+ } else {
+ q = q.Where("state = ?", c.Query("state"))
+ }
+ var items []model.ActionDefinition
+ if err := q.Order("sort_order ASC, id ASC").Find(&items).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("查询 Action 定义失败"))
+ return
+ }
+ web.OK(c, items)
+}
+
+func GetActionDefinitionByKey(c *gin.Context) {
+ key := strings.TrimSpace(c.Param("key"))
+ if key == "" {
+ web.Fail(c, web.NewBadRequest("action key 不能为空"))
+ return
+ }
+ var item model.ActionDefinition
+ if err := store.DB.Where("key = ?", key).First(&item).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func CreateActionDefinition(c *gin.Context) {
+ var req actionDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateActionDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item := model.ActionDefinition{
+ Key: req.Key,
+ Label: req.Label,
+ Description: req.Description,
+ ActionType: req.ActionType,
+ ConnectorRef: req.ConnectorRef,
+ InputSchemaJSON: req.InputSchemaJSON,
+ OutputSchemaJSON: req.OutputSchemaJSON,
+ RiskLevel: req.RiskLevel,
+ ApprovalMode: req.ApprovalMode,
+ AuditLevel: req.AuditLevel,
+ ExposedToUser: req.ExposedToUser,
+ OntologyBindingJSON: req.OntologyBindingJSON,
+ State: req.State,
+ SortOrder: req.SortOrder,
+ }
+ if err := store.DB.Create(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("创建 Action 定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func UpdateActionDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.ActionDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
+ return
+ }
+ var req actionDefinitionReq
+ if err := c.ShouldBindJSON(&req); err != nil {
+ web.Fail(c, web.NewBadRequest("请求参数错误"))
+ return
+ }
+ if appErr := validateActionDefinitionReq(&req); appErr != nil {
+ web.Fail(c, appErr)
+ return
+ }
+ item.Key = req.Key
+ item.Label = req.Label
+ item.Description = req.Description
+ item.ActionType = req.ActionType
+ item.ConnectorRef = req.ConnectorRef
+ item.InputSchemaJSON = req.InputSchemaJSON
+ item.OutputSchemaJSON = req.OutputSchemaJSON
+ item.RiskLevel = req.RiskLevel
+ item.ApprovalMode = req.ApprovalMode
+ item.AuditLevel = req.AuditLevel
+ item.ExposedToUser = req.ExposedToUser
+ item.OntologyBindingJSON = req.OntologyBindingJSON
+ item.State = req.State
+ item.SortOrder = req.SortOrder
+ if err := store.DB.Save(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("更新 Action 定义失败"))
+ return
+ }
+ web.OK(c, item)
+}
+
+func DeleteActionDefinition(c *gin.Context) {
+ id, ok := parseID(c, "id")
+ if !ok {
+ return
+ }
+ var item model.ActionDefinition
+ if err := store.DB.First(&item, id).Error; err != nil {
+ web.Fail(c, web.NewNotFoundError("Action 定义不存在"))
+ return
+ }
+ if err := store.DB.Delete(&item).Error; err != nil {
+ web.Fail(c, web.NewBadRequest("删除 Action 定义失败"))
+ return
+ }
+ web.OK(c, gin.H{"id": id, "deleted": true})
+}
diff --git a/eai_agentplatform/backend-go/internal/api/specialist.go b/eai_agentplatform/backend-go/internal/api/specialist.go
index a26ab8b..168bd41 100644
--- a/eai_agentplatform/backend-go/internal/api/specialist.go
+++ b/eai_agentplatform/backend-go/internal/api/specialist.go
@@ -110,41 +110,40 @@ func SpecialistSummary(c *gin.Context) {
}
type specialistReq struct {
- Key string `json:"key"`
- Label string `json:"label"`
- DisplayCode string `json:"display_code"`
- EAILogicCode string `json:"eailogic_code"`
- Tier string `json:"tier"`
- WorkerType string `json:"worker_type"`
- ObjectEntryRoute string `json:"object_entry_route"`
- Summary string `json:"summary"`
- RoleCardJSON string `json:"role_card_json"`
- WorkStatus string `json:"work_status"`
- RiskLabel string `json:"risk_label"`
- Color string `json:"color"`
- Stage string `json:"stage"`
- Progress int `json:"progress"`
- MarketTag string `json:"market_tag"`
- Version string `json:"version"`
- ConnectorScope string `json:"connector_scope"`
- PermissionScope string `json:"permission_scope"`
- ResourceBindings string `json:"resource_bindings"`
- InfoSources string `json:"info_sources"`
- BaseSkills string `json:"base_skills"`
- AIAssistance string `json:"ai_assistance"`
- GeneratedSkills string `json:"generated_skills"`
+ Key string `json:"key"`
+ Label string `json:"label"`
+ DisplayCode string `json:"display_code"`
+ EAILogicCode string `json:"eailogic_code"`
+ Tier string `json:"tier"`
+ WorkerType string `json:"worker_type"`
+ ObjectEntryRoute string `json:"object_entry_route"`
+ Summary string `json:"summary"`
+ InteractionCardJSON string `json:"interaction_card_json"`
+ WorkStatus string `json:"work_status"`
+ RiskLabel string `json:"risk_label"`
+ Color string `json:"color"`
+ Stage string `json:"stage"`
+ Progress int `json:"progress"`
+ MarketTag string `json:"market_tag"`
+ Version string `json:"version"`
+ ConnectorScope string `json:"connector_scope"`
+ PermissionScope string `json:"permission_scope"`
+ ResourceBindings string `json:"resource_bindings"`
+ InfoSources string `json:"info_sources"`
+ BaseSkills string `json:"base_skills"`
+ AIAssistance string `json:"ai_assistance"`
+ GeneratedSkills string `json:"generated_skills"`
// RuleFileMarkdown 岗位说明书正文(Markdown),会话创建时注入 System Prompt
RuleFileMarkdown string `json:"rule_file_markdown"`
// AllowedSkills 绑定技能 key 的 JSON 数组字符串,顺序即优先级(首个为主技能)。
// 元素须在 model.ValidSkillKeys 内;写入前会被规范化成紧凑 JSON。
- AllowedSkills string `json:"allowed_skills"`
- InputsRecordsJSON string `json:"inputs_records_json"`
- LegacySourceRecordsJSON string `json:"source_records_json"`
- PermissionRecordsJSON string `json:"permission_records_json"`
- ActionRecordsJSON string `json:"action_records_json"`
- ResultRecordsJSON string `json:"result_records_json"`
- State string `json:"state"`
- SortOrder int `json:"sort_order"`
+ AllowedSkills string `json:"allowed_skills"`
+ InputsRecordsJSON string `json:"inputs_records_json"`
+ PermissionRecordsJSON string `json:"permission_records_json"`
+ ActionRecordsJSON string `json:"action_records_json"`
+ ResultRecordsJSON string `json:"result_records_json"`
+ State string `json:"state"`
+ SortOrder int `json:"sort_order"`
}
func normalizeSpecialistReq(req *specialistReq) {
@@ -156,7 +155,7 @@ func normalizeSpecialistReq(req *specialistReq) {
req.WorkerType = strings.TrimSpace(req.WorkerType)
req.ObjectEntryRoute = strings.TrimSpace(req.ObjectEntryRoute)
req.Summary = strings.TrimSpace(req.Summary)
- req.RoleCardJSON = strings.TrimSpace(req.RoleCardJSON)
+ req.InteractionCardJSON = strings.TrimSpace(req.InteractionCardJSON)
req.WorkStatus = strings.TrimSpace(req.WorkStatus)
req.RiskLabel = strings.TrimSpace(req.RiskLabel)
req.Color = strings.TrimSpace(req.Color)
@@ -173,10 +172,6 @@ func normalizeSpecialistReq(req *specialistReq) {
req.RuleFileMarkdown = strings.TrimSpace(req.RuleFileMarkdown)
req.AllowedSkills = strings.TrimSpace(req.AllowedSkills)
req.InputsRecordsJSON = strings.TrimSpace(req.InputsRecordsJSON)
- req.LegacySourceRecordsJSON = strings.TrimSpace(req.LegacySourceRecordsJSON)
- if req.InputsRecordsJSON == "" {
- req.InputsRecordsJSON = req.LegacySourceRecordsJSON
- }
req.PermissionRecordsJSON = strings.TrimSpace(req.PermissionRecordsJSON)
req.ActionRecordsJSON = strings.TrimSpace(req.ActionRecordsJSON)
req.ResultRecordsJSON = strings.TrimSpace(req.ResultRecordsJSON)
@@ -212,8 +207,8 @@ func validateSpecialistReq(req *specialistReq) *web.AppError {
if !store.ValidateStructuredRecordsJSON(req.InputsRecordsJSON) {
return web.NewBadRequest("inputs_records_json 必须是 JSON 数组")
}
- if !store.ValidateJSONObjectJSON(req.RoleCardJSON) {
- return web.NewBadRequest("role_card_json 必须是 JSON 对象")
+ if !store.ValidateJSONObjectJSON(req.InteractionCardJSON) {
+ return web.NewBadRequest("interaction_card_json 必须是 JSON 对象")
}
if !store.ValidateStructuredRecordsJSON(req.PermissionRecordsJSON) {
return web.NewBadRequest("permission_records_json 必须是 JSON 数组")
@@ -282,7 +277,7 @@ func CreateSpecialist(c *gin.Context) {
WorkerType: req.WorkerType,
ObjectEntryRoute: req.ObjectEntryRoute,
Summary: req.Summary,
- RoleCardJSON: req.RoleCardJSON,
+ InteractionCardJSON: req.InteractionCardJSON,
WorkStatus: req.WorkStatus,
RiskLabel: req.RiskLabel,
Color: req.Color,
@@ -351,7 +346,7 @@ func UpdateSpecialist(c *gin.Context) {
item.WorkerType = req.WorkerType
item.ObjectEntryRoute = req.ObjectEntryRoute
item.Summary = req.Summary
- item.RoleCardJSON = req.RoleCardJSON
+ item.InteractionCardJSON = req.InteractionCardJSON
item.WorkStatus = req.WorkStatus
item.RiskLabel = req.RiskLabel
item.Color = req.Color
diff --git a/eai_agentplatform/backend-go/internal/model/app_definition.go b/eai_agentplatform/backend-go/internal/model/app_definition.go
new file mode 100644
index 0000000..eec6b11
--- /dev/null
+++ b/eai_agentplatform/backend-go/internal/model/app_definition.go
@@ -0,0 +1,38 @@
+package model
+
+import "time"
+
+// AppDefinition 面向用户的一键应用目录定义。
+// 它是“成品入口”这一层:决定应用怎么展示、默认挂哪个专员/技能、打开后走哪条路。
+type AppDefinition struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Key string `gorm:"size:64;uniqueIndex;not null" json:"key"`
+ Label string `gorm:"size:128;not null" json:"label"`
+ DisplayCode string `gorm:"column:display_code;size:16;default:'';index" json:"display_code"`
+ EAILogicCode string `gorm:"column:eailogic_code;size:32;default:'';index" json:"eailogic_code"`
+ Badge string `gorm:"size:32;default:''" json:"badge"`
+ Kind string `gorm:"size:64;default:''" json:"kind"`
+ MarketTag string `gorm:"column:market_tag;size:32;default:'';index" json:"market_tag"`
+ WorkerType string `gorm:"size:16;default:'';index" json:"worker_type"`
+ Tier string `gorm:"size:16;default:'';index" json:"tier"`
+ Source string `gorm:"size:32;not null;default:eai;index" json:"source"` // eai / custom / imported
+ Color string `gorm:"size:32;default:''" json:"color"`
+ IconText string `gorm:"column:icon_text;size:16;default:''" json:"icon_text"`
+ CoverTone string `gorm:"column:cover_tone;type:text" json:"cover_tone"`
+ Summary string `gorm:"type:text" json:"summary"`
+ Description string `gorm:"type:text" json:"description"`
+ OpenRoute string `gorm:"column:open_route;size:256;default:''" json:"open_route"`
+ SpecialistKey string `gorm:"column:specialist_key;size:64;default:'';index" json:"specialist_key"`
+ SkillKey string `gorm:"column:skill_key;size:64;default:'';index" json:"skill_key"`
+ DefaultPrompt string `gorm:"column:default_prompt;type:text" json:"default_prompt"`
+ PromptsJSON string `gorm:"column:prompts_json;type:text" json:"prompts_json"`
+ TagsJSON string `gorm:"column:tags_json;type:text" json:"tags_json"`
+ InstallState string `gorm:"column:install_state;size:16;not null;default:installed;index" json:"install_state"`
+ ExposedToUser bool `gorm:"column:exposed_to_user;not null;default:true;index" json:"exposed_to_user"`
+ State string `gorm:"size:16;not null;default:active;index" json:"state"` // active / inactive
+ SortOrder int `gorm:"not null;default:0;index" json:"sort_order"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+func (AppDefinition) TableName() string { return "app_definition" }
diff --git a/eai_agentplatform/backend-go/internal/model/skill_definition.go b/eai_agentplatform/backend-go/internal/model/skill_definition.go
index 6712fcd..f19fab8 100644
--- a/eai_agentplatform/backend-go/internal/model/skill_definition.go
+++ b/eai_agentplatform/backend-go/internal/model/skill_definition.go
@@ -4,29 +4,28 @@ import "time"
// SkillDefinition 对外暴露的任务级能力定义。
type SkillDefinition struct {
- ID uint `gorm:"primaryKey" json:"id"`
- Key string `gorm:"size:64;uniqueIndex;not null" json:"key"`
- Label string `gorm:"size:128;not null" json:"label"`
- DisplayCode string `gorm:"column:display_code;size:16;default:'';index" json:"display_code"`
- EAILogicCode string `gorm:"column:eailogic_code;size:32;default:'';index" json:"eailogic_code"`
- Description string `gorm:"type:text" json:"description"`
- RoleKind string `gorm:"size:32;not null;default:skill;index" json:"role_kind"` // assistant / specialist / skill
- Source string `gorm:"size:32;not null;default:eai;index" json:"source"` // eai / custom
- ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;default:''" json:"object_entry_route"`
- LegacyObjectEntryRoute string `gorm:"column:legacy_object_entry_route;size:128;default:''" json:"legacy_object_entry_route"`
- ExposedToUser bool `gorm:"not null;default:true;index" json:"exposed_to_user"`
- StarterPromptsJSON string `gorm:"type:text" json:"starter_prompts_json"`
- PromptTemplate string `gorm:"type:text" json:"prompt_template"`
- InputSchemaJSON string `gorm:"type:text" json:"input_schema_json"`
- OutputSchemaJSON string `gorm:"type:text" json:"output_schema_json"`
- ArtifactSchemaJSON string `gorm:"type:text" json:"artifact_schema_json"`
- ActionRefsJSON string `gorm:"type:text" json:"action_refs_json"`
- PolicyRefsJSON string `gorm:"type:text" json:"policy_refs_json"`
- OntologyBindingJSON string `gorm:"type:text" json:"ontology_binding_json"`
- State string `gorm:"size:16;not null;default:active;index" json:"state"`
- SortOrder int `gorm:"not null;default:0;index" json:"sort_order"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ Key string `gorm:"size:64;uniqueIndex;not null" json:"key"`
+ Label string `gorm:"size:128;not null" json:"label"`
+ DisplayCode string `gorm:"column:display_code;size:16;default:'';index" json:"display_code"`
+ 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"` // assistant / specialist / skill
+ Source string `gorm:"size:32;not null;default:eai;index" json:"source"` // eai / custom
+ ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;default:''" json:"object_entry_route"`
+ ExposedToUser bool `gorm:"not null;default:true;index" json:"exposed_to_user"`
+ StarterPromptsJSON string `gorm:"type:text" json:"starter_prompts_json"`
+ PromptTemplate string `gorm:"type:text" json:"prompt_template"`
+ InputSchemaJSON string `gorm:"type:text" json:"input_schema_json"`
+ OutputSchemaJSON string `gorm:"type:text" json:"output_schema_json"`
+ ArtifactSchemaJSON string `gorm:"type:text" json:"artifact_schema_json"`
+ ActionRefsJSON string `gorm:"type:text" json:"action_refs_json"`
+ PolicyRefsJSON string `gorm:"type:text" json:"policy_refs_json"`
+ OntologyBindingJSON string `gorm:"type:text" json:"ontology_binding_json"`
+ State string `gorm:"size:16;not null;default:active;index" json:"state"`
+ SortOrder int `gorm:"not null;default:0;index" json:"sort_order"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
}
func (SkillDefinition) TableName() string { return "skill_definition" }
diff --git a/eai_agentplatform/backend-go/internal/model/skill_keys.go b/eai_agentplatform/backend-go/internal/model/skill_keys.go
index 954932a..7f75c59 100644
--- a/eai_agentplatform/backend-go/internal/model/skill_keys.go
+++ b/eai_agentplatform/backend-go/internal/model/skill_keys.go
@@ -9,7 +9,7 @@ import (
// ValidSkillKeys 技能 key 全集,用于校验 Specialist.AllowedSkills。
//
-// 权威来源是前端 frontend/src/config/workbench.js 的 availableSkills ——
+// 权威来源是前端 frontend/src/config/workbench.js 的 staticSkillCatalog ——
// 技能实现仍归前端,后端只持有 key 清单,用于录入时挡住拼写错误。
// 两个清单的一致性由 skill_keys_test.go 守着,改动任一侧都会让测试失败。
//
diff --git a/eai_agentplatform/backend-go/internal/model/skill_keys_test.go b/eai_agentplatform/backend-go/internal/model/skill_keys_test.go
index 2a3b1cc..c05e7bd 100644
--- a/eai_agentplatform/backend-go/internal/model/skill_keys_test.go
+++ b/eai_agentplatform/backend-go/internal/model/skill_keys_test.go
@@ -12,11 +12,11 @@ import (
// workbenchJSRelPath 前端技能目录的相对路径(相对本包目录)。
const workbenchJSRelPath = "../../../frontend/src/config/workbench.js"
-// createSkillDefinitionRe 抓 availableSkills 数组里每个 createSkillDefinition 的字面量 key。
+// createSkillDefinitionRe 抓 staticSkillCatalog 数组里每个 createSkillDefinition 的字面量 key。
// 依赖 workbench.js 里「createSkillDefinition({ 紧跟 key: '…'」这一写法。
var createSkillDefinitionRe = regexp.MustCompile(`createSkillDefinition\(\{\s*key:\s*'([^']+)'`)
-// TestValidSkillKeysMatchFrontend 守住 backend 的 ValidSkillKeys 与前端 availableSkills 一致。
+// TestValidSkillKeysMatchFrontend 守住 backend 的 ValidSkillKeys 与前端 staticSkillCatalog 一致。
//
// 这两份清单天然会漂移:技能实现在前端,后端只持有 key 做录入校验。任何一侧增删技能
// 而忘了另一侧,这里就会红——这正是本测试存在的唯一理由。
@@ -54,11 +54,11 @@ func TestValidSkillKeysMatchFrontend(t *testing.T) {
sort.Strings(onlyFrontend)
if len(onlyBackend) > 0 {
- t.Errorf("ValidSkillKeys 里有、前端 availableSkills 里没有的 key(后端多写或前端已删):\n %s",
+ t.Errorf("ValidSkillKeys 里有、前端 staticSkillCatalog 里没有的 key(后端多写或前端已删):\n %s",
strings.Join(onlyBackend, "\n "))
}
if len(onlyFrontend) > 0 {
- t.Errorf("前端 availableSkills 里有、ValidSkillKeys 里没有的 key(新增技能后忘了补后端清单):\n %s",
+ t.Errorf("前端 staticSkillCatalog 里有、ValidSkillKeys 里没有的 key(新增技能后忘了补后端清单):\n %s",
strings.Join(onlyFrontend, "\n "))
}
}
diff --git a/eai_agentplatform/backend-go/internal/model/specialist.go b/eai_agentplatform/backend-go/internal/model/specialist.go
index 83f054a..bf5c0e8 100644
--- a/eai_agentplatform/backend-go/internal/model/specialist.go
+++ b/eai_agentplatform/backend-go/internal/model/specialist.go
@@ -4,39 +4,39 @@ import "time"
// Specialist 数字员工专员目录
type Specialist struct {
- ID uint `gorm:"primaryKey" json:"id"`
- Key string `gorm:"size:64;uniqueIndex;not null" json:"key"`
- Label string `gorm:"size:128;not null" json:"label"`
- DisplayCode string `gorm:"column:display_code;size:16;default:'';index" json:"display_code"`
- EAILogicCode string `gorm:"column:eailogic_code;size:32;default:'';index" json:"eailogic_code"`
- Tier string `gorm:"size:16;not null;index" json:"tier"` // generic / industry
- WorkerType string `gorm:"size:16;not null;default:dw;index" json:"worker_type"`
- ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;not null;default:''" json:"object_entry_route"`
- Summary string `gorm:"type:text" json:"summary"`
- RoleCardJSON string `gorm:"type:text" json:"role_card_json"`
- WorkStatus string `gorm:"size:64;default:''" json:"work_status"`
- RiskLabel string `gorm:"size:64;default:''" json:"risk_label"`
- Color string `gorm:"size:16;default:''" json:"color"`
- Stage string `gorm:"size:64;default:''" json:"stage"`
- Progress int `gorm:"not null;default:0" json:"progress"`
- MarketTag string `gorm:"size:32;not null;default:installed;index" json:"market_tag"` // 已安装 / 可升级 / 试用
- Version string `gorm:"size:32;default:''" json:"version"`
- ConnectorScope string `gorm:"type:text" json:"connector_scope"`
- PermissionScope string `gorm:"type:text" json:"permission_scope"`
- ResourceBindings string `gorm:"type:text" json:"resource_bindings"`
- InfoSources string `gorm:"type:text" json:"info_sources"`
- BaseSkills string `gorm:"type:text" json:"base_skills"`
- AIAssistance string `gorm:"type:text" json:"ai_assistance"`
- GeneratedSkills string `gorm:"type:text" json:"generated_skills"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ Key string `gorm:"size:64;uniqueIndex;not null" json:"key"`
+ Label string `gorm:"size:128;not null" json:"label"`
+ DisplayCode string `gorm:"column:display_code;size:16;default:'';index" json:"display_code"`
+ EAILogicCode string `gorm:"column:eailogic_code;size:32;default:'';index" json:"eailogic_code"`
+ Tier string `gorm:"size:16;not null;index" json:"tier"` // generic / industry
+ WorkerType string `gorm:"size:16;not null;default:dw;index" json:"worker_type"`
+ ObjectEntryRoute string `gorm:"column:object_entry_route;size:128;not null;default:''" json:"object_entry_route"`
+ Summary string `gorm:"type:text" json:"summary"`
+ InteractionCardJSON string `gorm:"column:interaction_card_json;type:text" json:"interaction_card_json"`
+ WorkStatus string `gorm:"size:64;default:''" json:"work_status"`
+ RiskLabel string `gorm:"size:64;default:''" json:"risk_label"`
+ Color string `gorm:"size:16;default:''" json:"color"`
+ Stage string `gorm:"size:64;default:''" json:"stage"`
+ Progress int `gorm:"not null;default:0" json:"progress"`
+ MarketTag string `gorm:"size:32;not null;default:installed;index" json:"market_tag"` // 已安装 / 可升级 / 试用
+ Version string `gorm:"size:32;default:''" json:"version"`
+ ConnectorScope string `gorm:"type:text" json:"connector_scope"`
+ PermissionScope string `gorm:"type:text" json:"permission_scope"`
+ ResourceBindings string `gorm:"type:text" json:"resource_bindings"`
+ InfoSources string `gorm:"type:text" json:"info_sources"`
+ BaseSkills string `gorm:"type:text" json:"base_skills"`
+ AIAssistance string `gorm:"type:text" json:"ai_assistance"`
+ GeneratedSkills string `gorm:"type:text" json:"generated_skills"`
// RuleFileMarkdown 岗位说明书正文(Markdown 全文)。
// 会话创建时注入 System Prompt,决定这个专员怎么说话、怎么推进、什么时候调用哪个技能。
// 参考 AionCore builtin-assistants 的 rule_file;命名对齐 SY23。
RuleFileMarkdown string `gorm:"type:text" json:"rule_file_markdown"`
// AllowedSkills 绑定技能 key 列表,JSON 字符串数组,顺序即优先级(首个为主技能)。
- // 元素取值须通过 model.ValidSkillKeys 校验,对齐前端 availableSkills[].key。
+ // 元素取值须通过 model.ValidSkillKeys 校验,对齐前端 staticSkillCatalog[].key。
// 字段名对齐 SY22 §6.1 对象 manifest 的 allowed_skills。
AllowedSkills string `gorm:"type:text" json:"allowed_skills"`
- InputsRecordsJSON string `gorm:"column:source_records_json;type:text" json:"inputs_records_json"`
+ InputsRecordsJSON string `gorm:"column:inputs_records_json;type:text" json:"inputs_records_json"`
PermissionRecordsJSON string `gorm:"type:text" json:"permission_records_json"`
ActionRecordsJSON string `gorm:"type:text" json:"action_records_json"`
ResultRecordsJSON string `gorm:"type:text" json:"result_records_json"`
diff --git a/eai_agentplatform/backend-go/internal/store/db.go b/eai_agentplatform/backend-go/internal/store/db.go
index efa5146..311a9a2 100644
--- a/eai_agentplatform/backend-go/internal/store/db.go
+++ b/eai_agentplatform/backend-go/internal/store/db.go
@@ -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 {
diff --git a/eai_agentplatform/backend-go/internal/store/db_migration_test.go b/eai_agentplatform/backend-go/internal/store/db_migration_test.go
index ca8f9a4..4424464 100644
--- a/eai_agentplatform/backend-go/internal/store/db_migration_test.go
+++ b/eai_agentplatform/backend-go/internal/store/db_migration_test.go
@@ -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")
+ }
}
diff --git a/eai_agentplatform/backend-go/internal/store/seed.go b/eai_agentplatform/backend-go/internal/store/seed.go
index 4a507e2..5ed2f34 100644
--- a/eai_agentplatform/backend-go/internal/store/seed.go
+++ b/eai_agentplatform/backend-go/internal/store/seed.go
@@ -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 {
diff --git a/eai_agentplatform/backend-go/internal/store/seed_specialist_rules.go b/eai_agentplatform/backend-go/internal/store/seed_specialist_rules.go
index 3453ea4..1228f82 100644
--- a/eai_agentplatform/backend-go/internal/store/seed_specialist_rules.go
+++ b/eai_agentplatform/backend-go/internal/store/seed_specialist_rules.go
@@ -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"},
diff --git a/eai_agentplatform/backend-go/internal/store/seed_specialist_rules_test.go b/eai_agentplatform/backend-go/internal/store/seed_specialist_rules_test.go
index ddebf89..3c08edd 100644
--- a/eai_agentplatform/backend-go/internal/store/seed_specialist_rules_test.go
+++ b/eai_agentplatform/backend-go/internal/store/seed_specialist_rules_test.go
@@ -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
diff --git a/eai_agentplatform/backend-go/internal/store/specialist_records.go b/eai_agentplatform/backend-go/internal/store/specialist_records.go
index 12e2d8d..fc22c96 100644
--- a/eai_agentplatform/backend-go/internal/store/specialist_records.go
+++ b/eai_agentplatform/backend-go/internal/store/specialist_records.go
@@ -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 {
diff --git a/eai_agentplatform/frontend/src/api/appDefinition.js b/eai_agentplatform/frontend/src/api/appDefinition.js
new file mode 100644
index 0000000..da18037
--- /dev/null
+++ b/eai_agentplatform/frontend/src/api/appDefinition.js
@@ -0,0 +1,9 @@
+import http from './http'
+
+export function listAppDefinitions(params) {
+ return http.get('/apps', { params })
+}
+
+export function getAppDefinitionByKey(key) {
+ return http.get(`/apps/by-key/${key}`)
+}
diff --git a/eai_agentplatform/frontend/src/api/capability.js b/eai_agentplatform/frontend/src/api/capability.js
deleted file mode 100644
index 1e1faab..0000000
--- a/eai_agentplatform/frontend/src/api/capability.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import http from './http'
-
-export function getSkillDefinitionByKey(key) {
- return http.get(`/skills/by-key/${key}`)
-}
diff --git a/eai_agentplatform/frontend/src/api/assistant.js b/eai_agentplatform/frontend/src/api/chat.js
similarity index 59%
rename from eai_agentplatform/frontend/src/api/assistant.js
rename to eai_agentplatform/frontend/src/api/chat.js
index 2a56cc2..c5a6113 100644
--- a/eai_agentplatform/frontend/src/api/assistant.js
+++ b/eai_agentplatform/frontend/src/api/chat.js
@@ -1,4 +1,5 @@
import http from './http'
-export function chatWithAssistant(data) {
+
+export function sendAssistantChatMessage(data) {
return http.post('/assistant/chat', data)
}
diff --git a/eai_agentplatform/frontend/src/api/skill.js b/eai_agentplatform/frontend/src/api/skill.js
new file mode 100644
index 0000000..b73acea
--- /dev/null
+++ b/eai_agentplatform/frontend/src/api/skill.js
@@ -0,0 +1,9 @@
+import http from './http'
+
+export function listSkills(params) {
+ return http.get('/skills', { params })
+}
+
+export function getSkillByKey(key) {
+ return http.get(`/skills/by-key/${key}`)
+}
diff --git a/eai_agentplatform/frontend/src/components/chat/AppChatRail.vue b/eai_agentplatform/frontend/src/components/chat/AppChatRail.vue
index 04d52a4..d493d00 100644
--- a/eai_agentplatform/frontend/src/components/chat/AppChatRail.vue
+++ b/eai_agentplatform/frontend/src/components/chat/AppChatRail.vue
@@ -65,7 +65,7 @@