refactor: 专员目录收敛为 6 个,重整 AI 路由与系统管理页
一次性提交当天全部改动(110 个文件)。 **未按 TOP_CODING_RULES.md G14.5「一次提交只装一件事」拆分** —— 用户明确要求 单一提交,此处如实记录,不静默忽略该冲突。 提交前验证:后端 go build / go vet / go test ./... 全绿,前端 npm run build exit 0,/api/health 与管理员 login 均返回 200。 - 专员目录收敛为 6 个:下线 knowledge-operations / process-coordination / presentation-briefing / report-generation 四个专员(前后端 manifest 与 seed 同步删除),新增 general-assistant。专员的归属关系(拥有哪些技能定义、 哪些目录项、提示词与绑定从哪来)改由 specialists/core 的 ownership.go、 prompt_provider.go、binding_provider.go 统一提供,seed 与 admin_handlers 随之内置化,卸载路径统一走 uninstall.go。 - 技能:补齐 text-to-speech 的前端 manifest(后端包在 HEAD 已存在), skillcore/ownership.go 提供与专员对称的归属查询。 - AI 路由:ai_config.json 由 OpenRouter/Ollama 切到 LMUAI / SiliconFlow / llama.cpp 本地路由,ai_secrets.example.json 与部署 env 样例同步新增 SILICONFLOW_API_KEY。 - 系统管理页重整:新增 AiAdminPage、OrganizationManagementPage,删除 AdminOverviewPage、CompanyConfigPage,SystemConfigPage 精简,nav / router / config/workbench.js 同步调整。依 G05.5,开发阶段直接收口到新结构,不留旧路由。 - 新增 internal/objectrefs:统一统计对象(专员 / 技能 / xapp)的运行时引用 (被多少 xapp、项目、任务引用),供管理页做删除前的影响面判断。 - 公众号创作专员:新增 OfficialAccountSpecialistPanel,workflow 与投递链路调整。 - XApp:考试 / 培训 Shell 扩展,XAppDirectoryPage 与 xappDefinition 同步。 - 文档:新增 GW01–GW05 工作台演进系列与 AR12 对话驱动与结构化交互架构; 同步 AR05 / SY17 / SY23 / SY25 / PL04;TOP_CODING_RULES.md 增补 G05.5。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"eai_agentplatform/backend/internal/dal"
|
||||
"eai_agentplatform/backend/internal/middleware"
|
||||
"eai_agentplatform/backend/internal/model"
|
||||
specialistruntime "eai_agentplatform/backend/internal/specialists/runtime"
|
||||
@@ -89,6 +90,10 @@ func CreateProject(c *gin.Context) {
|
||||
web.Fail(c, web.NewNotFoundError(err.Error()))
|
||||
return
|
||||
}
|
||||
if err := validateSkillKeys(req.SkillKeys); err != nil {
|
||||
web.Fail(c, web.NewNotFoundError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
instruction := ""
|
||||
if req.Instruction != nil {
|
||||
@@ -163,6 +168,10 @@ func UpdateProject(c *gin.Context) {
|
||||
project.SpecialistKeys = encodeKeys(req.SpecialistKeys)
|
||||
}
|
||||
if req.SkillKeys != nil {
|
||||
if err := validateSkillKeys(req.SkillKeys); err != nil {
|
||||
web.Fail(c, web.NewNotFoundError(err.Error()))
|
||||
return
|
||||
}
|
||||
project.SkillKeys = encodeKeys(req.SkillKeys)
|
||||
}
|
||||
if req.ConnectorKeys != nil {
|
||||
@@ -246,3 +255,17 @@ func validateSpecialistKeys(keys []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSkillKeys(keys []string) error {
|
||||
dao := dal.SkillDefinitionDAO{}
|
||||
for _, key := range keys {
|
||||
trimmed := strings.TrimSpace(key)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
if _, found := dao.GetByKey(trimmed); !found {
|
||||
return errors.New("技能不存在:" + trimmed)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"eai_agentplatform/backend/internal/middleware"
|
||||
skillapi "eai_agentplatform/backend/internal/skills/api"
|
||||
specialistapi "eai_agentplatform/backend/internal/specialists/api"
|
||||
"eai_agentplatform/backend/internal/specialists/runtime"
|
||||
wechatofficialaccountapi "eai_agentplatform/backend/internal/specialists/packages/wechat_official_account/api"
|
||||
"eai_agentplatform/backend/internal/specialists/runtime"
|
||||
xappapi "eai_agentplatform/backend/internal/xapps/api"
|
||||
)
|
||||
|
||||
@@ -206,12 +206,15 @@ func RegisterRoutes(r *gin.Engine, cfg *config.Config) {
|
||||
admin.DELETE("/positions/:id", DeletePosition)
|
||||
admin.POST("/specialists", specialistapi.CreateSpecialist)
|
||||
admin.PUT("/specialists/:id", specialistapi.UpdateSpecialist)
|
||||
admin.GET("/specialists/:id/remove-plan", specialistapi.PreviewSpecialistRemovePlan)
|
||||
admin.DELETE("/specialists/:id", specialistapi.DeleteSpecialist)
|
||||
admin.POST("/skills", skillapi.CreateSkillDefinition)
|
||||
admin.PUT("/skills/:id", skillapi.UpdateSkillDefinition)
|
||||
admin.GET("/skills/:id/remove-plan", skillapi.PreviewSkillRemovePlan)
|
||||
admin.DELETE("/skills/:id", skillapi.DeleteSkillDefinition)
|
||||
admin.POST("/xapps", xappapi.CreateXAppDefinition)
|
||||
admin.PUT("/xapps/:id", xappapi.UpdateXAppDefinition)
|
||||
admin.GET("/xapps/:id/remove-plan", xappapi.PreviewXAppRemovePlan)
|
||||
admin.DELETE("/xapps/:id", xappapi.DeleteXAppDefinition)
|
||||
admin.POST("/actions", CreateActionDefinition)
|
||||
admin.PUT("/actions/:id", UpdateActionDefinition)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
skillcore "eai_agentplatform/backend/internal/skills/core"
|
||||
specialistcore "eai_agentplatform/backend/internal/specialists/core"
|
||||
specialistmodel "eai_agentplatform/backend/internal/specialists/model"
|
||||
)
|
||||
|
||||
@@ -63,7 +63,8 @@ func resolveSpecialist(req ChatMessageRequest) *specialistmodel.Specialist {
|
||||
// 专员为 nil 或什么都没配时返回空串,调用方不做任何拼接——
|
||||
// 这样「没选专员」与「选了但没配说明书」都不改变原有行为。
|
||||
func specialistPromptSection(s *specialistmodel.Specialist) string {
|
||||
if s == nil {
|
||||
ctx := specialistcore.PromptContextFor(s)
|
||||
if ctx.Key == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -71,27 +72,27 @@ func specialistPromptSection(s *specialistmodel.Specialist) string {
|
||||
|
||||
// 身份
|
||||
b.WriteString("【当前专员】\n")
|
||||
label := strings.TrimSpace(s.Label)
|
||||
label := ctx.Label
|
||||
if label == "" {
|
||||
label = s.Key
|
||||
label = ctx.Key
|
||||
}
|
||||
fmt.Fprintf(&b, "%s(%s)\n", label, s.Key)
|
||||
if summary := strings.TrimSpace(s.Summary); summary != "" {
|
||||
fmt.Fprintf(&b, "%s(%s)\n", label, ctx.Key)
|
||||
if summary := ctx.Summary; summary != "" {
|
||||
b.WriteString(summary + "\n")
|
||||
}
|
||||
if workStatus := strings.TrimSpace(s.WorkStatus); workStatus != "" {
|
||||
if workStatus := ctx.WorkStatus; workStatus != "" {
|
||||
b.WriteString("当前状态:" + workStatus + "\n")
|
||||
}
|
||||
|
||||
// 可用技能
|
||||
if skills := skillcore.ParseAllowedSkills(s.AllowedSkills); len(skills) > 0 {
|
||||
if len(ctx.AllowedSkills) > 0 {
|
||||
b.WriteString("\n【可用技能】\n")
|
||||
b.WriteString(strings.Join(skills, "、"))
|
||||
b.WriteString(strings.Join(ctx.AllowedSkills, "、"))
|
||||
b.WriteString("\n首个是主技能。只在这些技能范围内承诺能力,范围外的需求如实说做不了。\n")
|
||||
}
|
||||
|
||||
// 岗位说明书
|
||||
if rule := strings.TrimSpace(s.RuleFileMarkdown); rule != "" {
|
||||
if rule := ctx.RuleFileMarkdown; rule != "" {
|
||||
b.WriteString("\n【岗位说明书】\n")
|
||||
b.WriteString(rule)
|
||||
b.WriteString("\n")
|
||||
|
||||
@@ -133,7 +133,7 @@ func setupAPITestDB(t *testing.T) {
|
||||
|
||||
for _, s := range []specialistmodel.Specialist{
|
||||
{Key: "contract-review", Label: "合同审查专员", State: "active", Tier: "industry", ObjectEntryRoute: "/apps/contract-review"},
|
||||
{Key: "report-generation", Label: "报告生成专员", State: "active", Tier: "generic", ObjectEntryRoute: "/apps/report-generation"},
|
||||
{Key: "wechat-official-account", Label: "公众号创作专员", State: "active", Tier: "generic", ObjectEntryRoute: "/apps/wechat-official-account"},
|
||||
{Key: "retired-one", Label: "已下线专员", State: "inactive", Tier: "generic", ObjectEntryRoute: "/apps/retired"},
|
||||
{Key: "general-assistant", Label: "通用助手", State: "system", Tier: "generic", ObjectEntryRoute: "/home"},
|
||||
} {
|
||||
@@ -168,7 +168,7 @@ func TestResolveSpecialistTaskWinsOverRequest(t *testing.T) {
|
||||
t.Fatalf("建任务失败: %v", err)
|
||||
}
|
||||
|
||||
got := resolveSpecialist(ChatMessageRequest{TaskID: task.ID, SpecialistKey: "report-generation"})
|
||||
got := resolveSpecialist(ChatMessageRequest{TaskID: task.ID, SpecialistKey: "wechat-official-account"})
|
||||
if got == nil || got.Key != "contract-review" {
|
||||
t.Fatalf("任务上的专员应优先,实际 %v", got)
|
||||
}
|
||||
@@ -178,8 +178,8 @@ func TestResolveSpecialistTaskWinsOverRequest(t *testing.T) {
|
||||
func TestResolveSpecialistFallsBackToRequest(t *testing.T) {
|
||||
setupAPITestDB(t)
|
||||
|
||||
got := resolveSpecialist(ChatMessageRequest{SpecialistKey: "report-generation"})
|
||||
if got == nil || got.Key != "report-generation" {
|
||||
got := resolveSpecialist(ChatMessageRequest{SpecialistKey: "wechat-official-account"})
|
||||
if got == nil || got.Key != "wechat-official-account" {
|
||||
t.Fatalf("应回退到请求里的 specialist_key,实际 %v", got)
|
||||
}
|
||||
}
|
||||
@@ -228,8 +228,8 @@ func TestResolveSpecialistTaskWithoutSpecialistFallsBack(t *testing.T) {
|
||||
t.Fatalf("建任务失败: %v", err)
|
||||
}
|
||||
|
||||
got := resolveSpecialist(ChatMessageRequest{TaskID: task.ID, SpecialistKey: "report-generation"})
|
||||
if got == nil || got.Key != "report-generation" {
|
||||
got := resolveSpecialist(ChatMessageRequest{TaskID: task.ID, SpecialistKey: "wechat-official-account"})
|
||||
if got == nil || got.Key != "wechat-official-account" {
|
||||
t.Fatalf("任务专员失效时应回退到请求里的 key,实际 %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user