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:
eaiadmin
2026-09-23 09:01:28 +08:00
co-authored by Claude Code
parent 7fc2903827
commit 593323a934
110 changed files with 8295 additions and 1715 deletions
@@ -1,40 +1,95 @@
const GENERIC_ROUTE_NAMES = new Set([
'默认',
'均衡',
'全能',
'推理',
'本地',
'快聊',
'绘图',
'OpenRouter',
'OpenAI',
'Ollama',
])
function normalizeProviderLabel(provider) {
const value = String(provider || '').trim().toLowerCase()
if (!value) return ''
if (value === 'ollama') return 'Ollama'
if (value === 'llamacpp') return 'llama.cpp'
if (value === 'lmuai') return 'LMUAI'
if (value === 'siliconflow') return 'SiliconFlow'
if (value === 'openrouter') return 'OpenRouter'
if (value === 'openai') return 'OpenAI'
return provider
}
function normalizeModelLabel(model) {
return String(model || '')
.trim()
.split('/')
.pop()
.replace(/text-embedding-3-small/gi, 'text-embedding-3-small')
.replace(/deepseek-v4-flash-0731/gi, 'DeepSeek V4 Flash 0731')
.replace(/deepseek-v4-flash/gi, 'DeepSeek V4 Flash')
.replace(/deepseek-v3.2/gi, 'DeepSeek V3.2')
.replace(/deepseek-flash/gi, 'DeepSeek Flash')
.replace(/gpt-5.4-image-2/gi, 'GPT 5.4 Image 2')
.replace(/gpt-5.4/gi, 'GPT 5.4')
.replace(/grok-4.20/gi, 'Grok 4.20')
.replace(/qwen3\.5-4b-q4_k_m\.gguf/gi, 'Qwen3.5 4B')
.replace(/qwen2.5:7b/gi, 'Qwen2.5 7B')
.replace(/bge-m3/gi, 'BGE-M3')
.replace(/llava/gi, 'LLaVA')
.replace(/flux-1.1-pro/gi, 'FLUX 1.1 Pro')
.trim()
}
function routeUsageLabel(aiRoute) {
const routeId = String(aiRoute?.ai_route_id || '').trim()
if (routeId === 'chat_route_ollama_qwen') return '本地对话'
if (routeId === 'chat_route_ollama_qwen_fast') return '本地快聊'
if (routeId === 'embed_route_ollama_bge_m3') return '本地向量'
const shortRouteName = String(aiRoute?.short_route_name || '').trim()
if (!shortRouteName || GENERIC_ROUTE_NAMES.has(shortRouteName)) return ''
return shortRouteName
}
function routeModelLabel(aiRoute) {
const fromModel = normalizeModelLabel(aiRoute?.model)
if (fromModel) return fromModel
return normalizeModelLabel(aiRoute?.short_model_name)
}
export function formatAiRouteShortLabel(aiRoute) {
if (!aiRoute) return '未设置'
if (aiRoute.ai_route_id === 'chat_route_auto' || aiRoute.ai_route_id === 'embed_route_auto') {
return '自动'
}
const shortRouteName = (aiRoute.short_route_name || '').trim()
const shortModelName = (aiRoute.short_model_name || '').trim()
if (shortRouteName && shortModelName) {
return `${shortRouteName}/${shortModelName}`
}
if (shortModelName) return shortModelName
if (shortRouteName) return shortRouteName
const model = (aiRoute.model || '').split('/').pop() || ''
const modelShort = model
.replace(/text-embedding-3-small/gi, 'Emb3')
.replace(/deepseek-v4-flash/gi, 'DS4F')
.replace(/deepseek-v3.2/gi, 'DSV3')
.replace(/gpt-5.4-image-2/gi, 'G图2')
.replace(/gpt-5.4/gi, 'GPT5')
.replace(/grok-4.20/gi, 'Grok4')
.replace(/qwen2.5:7b/gi, 'Qwen')
.replace(/bge-m3/gi, 'BGE')
.trim()
return modelShort || aiRoute.description || aiRoute.ai_route_id || '未设置'
const usage = routeUsageLabel(aiRoute)
const model = routeModelLabel(aiRoute)
if (usage && model) return `${usage} · ${model}`
if (model) return model
if (usage) return usage
return aiRoute.description || aiRoute.ai_route_id || '未设置'
}
export function formatAiRouteTitle(aiRoute) {
if (!aiRoute) return '未设置'
const provider = normalizeProviderLabel(aiRoute.provider)
const model = routeModelLabel(aiRoute)
if (aiRoute.ai_route_id === 'chat_route_auto' || aiRoute.ai_route_id === 'embed_route_auto') {
const detail = [provider, model].filter(Boolean).join(' / ')
const resolved = aiRoute.resolved_ai_route_id || ''
const detail = aiRoute.description || resolved || '自动选择当前最优可用模型'
return `自动 · ${detail}`
const suffix = detail || resolved || '自动选择当前最优可用模型'
return `自动 · 当前 ${suffix}`
}
const shortLabel = formatAiRouteShortLabel(aiRoute)
const detail = aiRoute.description || [aiRoute.provider, aiRoute.model].filter(Boolean).join(' / ') || aiRoute.ai_route_id
return shortLabel === detail ? detail : `${shortLabel} · ${detail}`
const usage = routeUsageLabel(aiRoute)
if (usage && provider && model) return `${usage} · ${provider} / ${model}`
if (usage && model) return `${usage} · ${model}`
if (provider && model) return `${model} · ${provider}`
return aiRoute.description || aiRoute.ai_route_id || '未设置'
}