一次性提交当天全部改动(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>
210 lines
5.2 KiB
Go
210 lines
5.2 KiB
Go
package objectrefs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
|
|
"eai_agentplatform/backend/internal/dal"
|
|
"eai_agentplatform/backend/internal/model"
|
|
specialistmodel "eai_agentplatform/backend/internal/specialists/model"
|
|
xappmodel "eai_agentplatform/backend/internal/xapps/model"
|
|
)
|
|
|
|
type SpecialistRuntimeRefs struct {
|
|
XAppKeys []string
|
|
ProjectIDs []uint
|
|
TaskCount int64
|
|
TaskRunCount int64
|
|
ArtifactCount int64
|
|
AICallCount int64
|
|
}
|
|
|
|
type SkillRuntimeRefs struct {
|
|
SpecialistKeys []string
|
|
XAppKeys []string
|
|
ProjectIDs []uint
|
|
}
|
|
|
|
type XAppRuntimeRefs struct {
|
|
FavoriteUserIDs []uint
|
|
RecentUserIDs []uint
|
|
CustomUserIDs []uint
|
|
}
|
|
|
|
func ParseKeyList(raw string) []string {
|
|
trimmed := strings.TrimSpace(raw)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
var keys []string
|
|
if err := json.Unmarshal([]byte(trimmed), &keys); err != nil {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(keys))
|
|
seen := make(map[string]bool, len(keys))
|
|
for _, key := range keys {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" || seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, key)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func listProjectIDsByKeyField(field string, key string) []uint {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" || dal.DB == nil {
|
|
return nil
|
|
}
|
|
var projects []model.Project
|
|
if err := dal.DB.Select("id, " + field).Find(&projects).Error; err != nil {
|
|
return nil
|
|
}
|
|
ids := make([]uint, 0)
|
|
for _, project := range projects {
|
|
var raw string
|
|
switch field {
|
|
case "specialist_keys":
|
|
raw = project.SpecialistKeys
|
|
case "skill_keys":
|
|
raw = project.SkillKeys
|
|
default:
|
|
continue
|
|
}
|
|
for _, item := range ParseKeyList(raw) {
|
|
if item == key {
|
|
ids = append(ids, project.ID)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
return ids
|
|
}
|
|
|
|
func listXAppKeysByField(field string, key string) []string {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" || dal.DB == nil {
|
|
return nil
|
|
}
|
|
var items []xappmodel.XAppDefinition
|
|
if err := dal.DB.Select("key, "+field).Where(field+" = ?", key).Find(&items).Error; err != nil {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(items))
|
|
for _, item := range items {
|
|
if trimmed := strings.TrimSpace(item.Key); trimmed != "" {
|
|
out = append(out, trimmed)
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func listSpecialistKeysBindingSkill(skillKey string) []string {
|
|
skillKey = strings.TrimSpace(skillKey)
|
|
if skillKey == "" || dal.DB == nil {
|
|
return nil
|
|
}
|
|
var items []specialistmodel.Specialist
|
|
if err := dal.DB.Select("key, allowed_skills").Find(&items).Error; err != nil {
|
|
return nil
|
|
}
|
|
out := make([]string, 0)
|
|
for _, item := range items {
|
|
for _, key := range ParseKeyList(item.AllowedSkills) {
|
|
if key == skillKey {
|
|
out = append(out, strings.TrimSpace(item.Key))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func CollectSpecialistRuntimeRefs(key string) SpecialistRuntimeRefs {
|
|
key = strings.TrimSpace(key)
|
|
refs := SpecialistRuntimeRefs{
|
|
XAppKeys: listXAppKeysByField("specialist_key", key),
|
|
ProjectIDs: listProjectIDsByKeyField("specialist_keys", key),
|
|
}
|
|
if key == "" || dal.DB == nil {
|
|
return refs
|
|
}
|
|
dal.DB.Model(&model.TaskRecord{}).Where("specialist_key = ?", key).Count(&refs.TaskCount)
|
|
dal.DB.Model(&model.TaskRun{}).Where("specialist_key = ?", key).Count(&refs.TaskRunCount)
|
|
dal.DB.Model(&model.TaskArtifact{}).Where("specialist_key = ?", key).Count(&refs.ArtifactCount)
|
|
dal.DB.Model(&model.AiCallLog{}).Where("specialist_key = ?", key).Count(&refs.AICallCount)
|
|
return refs
|
|
}
|
|
|
|
func CollectSkillRuntimeRefs(key string) SkillRuntimeRefs {
|
|
key = strings.TrimSpace(key)
|
|
return SkillRuntimeRefs{
|
|
SpecialistKeys: listSpecialistKeysBindingSkill(key),
|
|
XAppKeys: listXAppKeysByField("skill_key", key),
|
|
ProjectIDs: listProjectIDsByKeyField("skill_keys", key),
|
|
}
|
|
}
|
|
|
|
func listUserIDsByXAppCenterField(field string, key string) []uint {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" || dal.DB == nil {
|
|
return nil
|
|
}
|
|
var rows []xappmodel.UserXAppCenter
|
|
if err := dal.DB.Select("user_id, favorite_keys, recent_keys, custom_xapps").Find(&rows).Error; err != nil {
|
|
return nil
|
|
}
|
|
userIDs := make([]uint, 0)
|
|
for _, row := range rows {
|
|
var matched bool
|
|
switch field {
|
|
case "favorite_keys":
|
|
for _, item := range ParseKeyList(row.FavoriteKeys) {
|
|
if item == key {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
case "recent_keys":
|
|
for _, item := range ParseKeyList(row.RecentKeys) {
|
|
if item == key {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
case "custom_xapps":
|
|
var items []struct {
|
|
Key string `json:"key"`
|
|
}
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(row.CustomXApps)), &items); err == nil {
|
|
for _, item := range items {
|
|
if strings.TrimSpace(item.Key) == key {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if matched {
|
|
userIDs = append(userIDs, row.UserID)
|
|
}
|
|
}
|
|
sort.Slice(userIDs, func(i, j int) bool { return userIDs[i] < userIDs[j] })
|
|
return userIDs
|
|
}
|
|
|
|
func CollectXAppRuntimeRefs(key string) XAppRuntimeRefs {
|
|
key = strings.TrimSpace(key)
|
|
return XAppRuntimeRefs{
|
|
FavoriteUserIDs: listUserIDsByXAppCenterField("favorite_keys", key),
|
|
RecentUserIDs: listUserIDsByXAppCenterField("recent_keys", key),
|
|
CustomUserIDs: listUserIDsByXAppCenterField("custom_xapps", key),
|
|
}
|
|
}
|