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
@@ -66,9 +66,9 @@ func SeedDefaults() error {
{"password_min_length", "6", "密码最小长度"},
{"session_timeout_minutes", "480", "会话超时分钟数(不活动自动退出)"},
// 7.2.4 AI 配置
{"llm_base_url", "http://localhost:11434/v1", "LLM 服务地址"},
{"llm_api_key", "sk-xxx", "LLM API Key(敏感信息,建议写入 ai_secrets.json)"},
{"llm_model", "qwen2.5:7b", "LLM 模型名"},
{"llm_base_url", "http://localhost:8080/v1", "LLM 服务地址"},
{"llm_api_key", "local", "LLM API Key(本地服务可留占位值,远程服务建议写入 ai_secrets.json)"},
{"llm_model", "Qwen3.5-4B-Q4_K_M.gguf", "LLM 模型名"},
{"embed_model", "bge-m3", "Embedding 模型名"},
{"llm_max_tokens", "2048", "LLM 最大输出 Token 数"},
{"llm_temperature", "0.7", "LLM 温度参数(0.0~1.0)"},
@@ -91,6 +91,18 @@ func SeedDefaults() error {
if err := seedCoursesFromJSON(); err != nil {
return err
}
if err := seedProductsFromJSON(); err != nil {
return err
}
if err := seedCompanyIntroContent(); err != nil {
return err
}
if err := seedQuestionsFromJSON(); err != nil {
return err
}
if err := seedDefaultExamPapers(); err != nil {
return err
}
if err := seedTrainingMedia(); err != nil {
return err
}
@@ -135,6 +147,40 @@ type courseSeedFile struct {
Data []courseSeedItem `json:"data"`
}
type productSeedFile struct {
Data []productSeedItem `json:"data"`
}
type productSeedItem struct {
Code string `json:"code"`
Name string `json:"name"`
Category string `json:"category"`
Tags string `json:"tags"`
Description string `json:"description"`
Pricing string `json:"pricing"`
CommissionRecommend string `json:"commission_recommend"`
CommissionNegotiate string `json:"commission_negotiate"`
PublicCourseBonus string `json:"public_course_bonus"`
VersionRisk string `json:"version_risk"`
ReportRules string `json:"report_rules"`
Status string `json:"status"`
}
type questionSeedFile struct {
Data []questionSeedItem `json:"data"`
}
type questionSeedItem struct {
Domain string `json:"domain"`
CourseID *uint `json:"course_id"`
Type string `json:"type"`
Stem string `json:"stem"`
Options []model.QuestionOption `json:"options"`
Answer []string `json:"answer"`
Explanation string `json:"explanation"`
Status string `json:"status"`
}
type courseSeedItem struct {
Code string `json:"code"`
Name string `json:"name"`
@@ -197,6 +243,172 @@ func seedCoursesFromJSON() error {
return nil
}
func seedProductsFromJSON() error {
var count int64
DB.Model(&model.Product{}).Count(&count)
if count > 0 {
return nil
}
cfg := config.Load()
path := filepath.Join(cfg.TrainingMaterialsDir, "work", "products.json")
if _, err := os.Stat(path); err != nil {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
var payload productSeedFile
if err := json.Unmarshal(data, &payload); err != nil {
return err
}
for _, item := range payload.Data {
if item.Code == "" || item.Name == "" || item.Category == "" {
continue
}
status := item.Status
if strings.TrimSpace(status) == "" {
status = "active"
}
product := model.Product{
Code: item.Code,
Name: item.Name,
Category: item.Category,
Tags: item.Tags,
Description: item.Description,
Pricing: item.Pricing,
CommissionRecommend: item.CommissionRecommend,
CommissionNegotiate: item.CommissionNegotiate,
PublicCourseBonus: item.PublicCourseBonus,
VersionRisk: item.VersionRisk,
ReportRules: item.ReportRules,
Status: status,
}
if err := DB.Create(&product).Error; err != nil {
return err
}
}
log.Println("[OK] 产品知识种子已导入")
return nil
}
func seedCompanyIntroContent() error {
var cfg model.SystemConfig
if err := DB.Where("config_key = ?", "company_intro").First(&cfg).Error; err != nil {
return nil
}
if strings.TrimSpace(cfg.ConfigValue) != "" {
return nil
}
cfg.ConfigValue = strings.Join([]string{
"博昇,聚焦企业增长与 AI 落地的综合服务商。",
"",
"我们围绕企业从资本运作到资质认定,再到 AI 转型的完整成长链路,提供四大板块服务:",
"1. 资本咨询:股权规划、融资顾问、IPO 辅导,帮助企业走通资本路径。",
"2. 资质认定辅导:高新技术企业、专精特新、专精特新小巨人的申报陪跑。",
"3. AI 咨询与实施:AI 公开课、落地诊断、软件授权与定制开发。",
"4. 企业级 AI 工具与平台:按行业匹配知识库、办公智能体、工业/医疗 AI、内容生成与电商运营方案。",
"",
"本培训平台面向新员工入职培训与内部持续学习,统一承接公司认知、产品知识、销售方法与考试验收。",
}, "\n")
return DB.Save(&cfg).Error
}
func seedQuestionsFromJSON() error {
var count int64
DB.Model(&model.Question{}).Count(&count)
if count > 0 {
return nil
}
cfg := config.Load()
path := filepath.Join(cfg.TrainingMaterialsDir, "work", "questions.json")
if _, err := os.Stat(path); err != nil {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
var payload questionSeedFile
if err := json.Unmarshal(data, &payload); err != nil {
return err
}
for _, item := range payload.Data {
if item.Domain == "" || item.Type == "" || item.Stem == "" || len(item.Answer) == 0 {
continue
}
status := item.Status
if strings.TrimSpace(status) == "" {
status = "active"
}
question := model.Question{
Domain: item.Domain,
CourseID: item.CourseID,
Type: item.Type,
Stem: item.Stem,
Options: mustJSON(item.Options),
Answer: mustJSON(item.Answer),
Explanation: item.Explanation,
Status: status,
}
if err := DB.Create(&question).Error; err != nil {
return err
}
}
log.Println("[OK] 考试题库种子已导入")
return nil
}
func seedDefaultExamPapers() error {
var count int64
DB.Model(&model.ExamPaper{}).Count(&count)
if count > 0 {
return nil
}
papers := []model.ExamPaper{
{
Name: "内部培训综合自测",
Type: "self_test",
Domain: "company,product,sales",
QuestionCount: 20,
TotalScore: 100,
PassScore: 60,
DurationMinutes: 30,
Randomize: true,
Status: "active",
},
{
Name: "内部培训综合考试",
Type: "formal",
Domain: "company,product,sales",
QuestionCount: 30,
TotalScore: 100,
PassScore: 80,
DurationMinutes: 45,
Randomize: true,
Status: "active",
},
}
for _, paper := range papers {
if err := DB.Create(&paper).Error; err != nil {
return err
}
}
log.Println("[OK] 默认考试配置已导入")
return nil
}
type mediaSeed struct {
filename string
sourcePath string
@@ -345,8 +557,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "直接告诉我目标,或者把材料贴给我,我来帮你起步。",
"starter_prompts": []string{"帮我拆解这个任务,并列出下一步", "把这段产品介绍整理成正式文案", "先帮我判断这个问题该找哪个专员或技能"},
}),
State: "active",
SortOrder: 10,
State: "active",
SortOrder: 10,
},
{
Key: "document-translate",
@@ -373,8 +585,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "把原文贴给我,并说明目标语言、受众和语气要求;如果有术语表,也一起给我。",
"starter_prompts": []string{"把这段产品说明翻译成英文,保持专业语气", "把这封邮件翻译成日文,语气礼貌一点", "把这份中文文档整理成英文摘要"},
}),
State: "active",
SortOrder: 20,
State: "active",
SortOrder: 20,
},
{
Key: "copy-proofreading",
@@ -401,8 +613,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "把文案贴给我,并告诉我这段内容要发给谁、希望保留什么语气。",
"starter_prompts": []string{"检查这段销售话术里的错别字和语病", "把这段产品介绍润得更正式一点", "帮我校对这封对外邮件,保留原意"},
}),
State: "active",
SortOrder: 30,
State: "active",
SortOrder: 30,
},
{
Key: "audio-transcribe",
@@ -429,8 +641,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "把音频内容发给我,或者告诉我语言、场景和你最终想拿到什么结果。",
"starter_prompts": []string{"把这段会议录音转成文字,并分段整理", "先转写这段采访,再提取重点", "把语音内容整理成可发群里的纪要"},
}),
State: "active",
SortOrder: 40,
State: "active",
SortOrder: 40,
},
{
Key: "batch-extract",
@@ -457,8 +669,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "告诉我想抽哪些字段、输出成什么格式、哪些情况需要标记为异常。",
"starter_prompts": []string{"从这批简历里提取姓名、岗位和年限", "把这些合同里的付款条款统一抽出来", "帮我批量提取文章标题、作者和发布时间"},
}),
State: "active",
SortOrder: 50,
State: "active",
SortOrder: 50,
},
{
Key: "contract-review",
@@ -485,8 +697,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "把合同发给我,并告诉我这次重点想看哪些条款,或者你站在哪一方立场。",
"starter_prompts": []string{"审查这份采购合同的风险点", "重点看赔偿责任和终止条款", "帮我整理一版待人工确认的风险清单"},
}),
State: "active",
SortOrder: 60,
State: "active",
SortOrder: 60,
},
{
Key: "report-generation",
@@ -513,8 +725,8 @@ func seedSkillDefinitions() error {
"opening_prompt": "把材料给我,并告诉我报告类型、受众、周期和你最想突出哪些重点。",
"starter_prompts": []string{"把今天的工作记录整理成日报", "根据这些节点生成项目周报", "帮我把这次交付过程整理成复盘说明"},
}),
State: "active",
SortOrder: 70,
State: "active",
SortOrder: 70,
},
})
@@ -894,9 +1106,9 @@ func seedXAppDefinitions() error {
Color: "#4f46e5",
IconText: "P",
CoverTone: "linear-gradient(135deg, #4338ca 0%, #818cf8 100%)",
Summary: "直接进入汇报 PPT 应用,默认挂上汇报专员,快速拿到页面结构、讲稿备注和演示骨架。",
Description: "适合经营汇报、方案汇报、培训课件和项目复盘。打开就进入“汇报专员 + PPT 技能”的样板链路。",
SpecialistKey: "presentation-briefing",
Summary: "直接进入汇报 PPT 应用,快速拿到页面结构、讲稿备注和演示骨架。",
Description: "适合经营汇报、方案汇报、培训课件和项目复盘。",
SpecialistKey: "",
SkillKey: "ppt-generation",
DefaultPrompt: "帮我做一版 8 页管理层汇报 PPT",
PromptsJSON: mustJSON([]string{"帮我做一版 8 页管理层汇报 PPT", "按汇报对象输出页面大纲和讲稿备注", "把这批材料压成一版培训课件骨架"}),