Преглед изворни кода

docs: 新增AI健康教练人格分化与管家自助选择设计规格

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
iwt пре 3 недеља
родитељ
комит
5bcc6cb26d
1 измењених фајлова са 184 додато и 0 уклоњено
  1. 184 0
      docs/superpowers/specs/2026-08-23-coach-butler-design.md

+ 184 - 0
docs/superpowers/specs/2026-08-23-coach-butler-design.md

@@ -0,0 +1,184 @@
+# AI 健康教练人格分化 与 管家自助选择 — 设计规格
+
+> 状态:待用户审查 · 日期:2026-08-23 · 作者:Sisyphus (brainstorming 产出)
+> 关联:`docs/mascot-design.md`(IP 官方命名)、`docs/function-analysis-by-membership.md`、会员体系 `docs/superpowers/specs/2026-07-09-family-membership-design.md`
+
+## 一、背景与目标
+
+两条需求线:
+
+1. **AI 健康教练人格化**:普通家庭用户的 AI 健康教练提供两位人格可选——浠宝(温暖知心)/福宝(活泼行动派),话术差异化、共享健康知识库。
+2. **管家自助选择**:L2(久久一生)订阅家庭除 AI 教练外享一对一真人管家,可浏览已认证管家并自助绑定/更换;Web 管理端增量补齐分配管理与停接开关。
+
+## 二、已确认决策(澄清问答记录)
+
+| # | 决策点 | 结论 |
+|---|--------|------|
+| 1 | 管家选择模式 | 自助直选:浏览→直接绑定,系统校验等级+容量,可随时更换;无推送通知 |
+| 2 | 高级档位判定 | 仅 L2 有效订阅家庭可自选管家;L1 仅 AI 教练 |
+| 3 | 人格差异方式 | 性格话术差异化:独立 system prompt,共享知识库与工具链 |
+| 4 | 选择粒度 | 用户级偏好(users.mascot 已按 user 隔离) |
+| 5 | 管理端范围 | 增量补齐:分配管理视图 + 强制解绑 + 管家停接新单开关;保留现有审核/结算 |
+| 6 | 存储方案修订 | 初选"新建 coach_personas 表",发现现有吉祥物管线后改为**复用**(users.mascot + MascotEnum + system_prompts),不建重复表 |
+| 7 | 命名 | 维持现名浠宝/福宝(IP 设计书 v2.2 官方命名),code 保持 xibao/fubao |
+
+## 三、现状盘点(复用资产清单)
+
+### 吉祥物管线(已存在,覆盖教练需求约 80%)
+
+| 层 | 资产 | 说明 |
+|----|------|------|
+| DB | `users.mascot VARCHAR(20)` | 用户级形象选择,值 xibao/fubao,schema.sql:34 |
+| Java 枚举 | `MascotEnum`(enums/MascotEnum.java) | XIBAO("xibao","女","温柔可爱的浠宝小女孩") displayName 浠宝;FUBAO("fubao","男","聪明活泼的福宝小男孩") displayName 福宝 |
+| 注入 | `AIChatController./chat/send`(:76-109)、`/nutrition/send`(:245-272) | 读 `user.getMascot()` → inputs 放入 mascot_name/mascot_gender/mascot_persona |
+| **缺口①** | `AIChatController./health-coach/send`(:420-447) | **漏注入**,需补齐 |
+| LangGraph 模型 | `app/models/common.py:11` `mascot_code: Optional[str]` | 字段已预留 |
+| **缺口②** | `app/graphs/health_coach_graph.py:89` | 只挂单一 `get_prompt("health_coach")`,无人格路由 |
+| 提示词服务 | `app/prompt_service.py` | 从 system_prompts 表按 prompt_key 加载(经 Java `/api/system-prompts/get`,TTL 缓存),DEFAULT_PROMPTS 兜底 |
+| 管理端 | `cfc-web/src/views/admin/SystemPromptManagement.vue` + `system_prompts` 表(schema.sql:4990) | prompt 内容后台可配,零发版改话术 |
+| 选择入口 | `cfc-frontend/pages/user-edit/user-edit.vue` | mascot 切换 UI 已有 |
+| 渲染 | `cfc-frontend/pages/ai/chat.vue`(944行) | mascotCode/mascotName/mascotIcon 主题化渲染已有 |
+
+### 管家体系(已存在,自选为唯一新能力)
+
+| 资产 | 说明 |
+|------|------|
+| `butler_profiles`(schema.sql:2626) | tier/status/member_count/max_members/annual_fee_l1,l2/commission_rate_l1,l2/certificates/description |
+| `butler_assignments`(schema.sql:2690) | family_id UNIQUE(uk_family_assignment) ↔ butler_user_id + subscription_id + level + assigned_at;一家一管家 |
+| 订阅激活自动分配 | `MemberSubscriptionService.activateSubscription`:165 → `ButlerService.assignMemberToButler(familyId,subscriptionId,level,amount)`:298:approved+tier=level+member_count<max_members 按 created_at 轮询取首个;可能找不到(无分配行) |
+| 家庭端控制器 | `controller/butler/ButlerController.java` `/api/butler/*`(apply/profile/update-profile/members/service-records/commissions)——**均为管家自身视角** |
+| 管理端控制器 | `AdminButlerController` `/api/admin/butler/list\|review\|assign-member\|service-records\|settlements`——assign-member 为管理员手动指派 |
+| 订阅校验 | `MemberSubscriptionService.getActiveSubscription(familyId)`:54 / `hasActiveSubscription`:78 |
+| Web 管理端 | `views/admin/review/ButlerAudit.vue`、`ReviewCenter.vue`、`ServiceRoleApplications.vue`、`api/butler.js`、`permissions.js` |
+| 小程序 | `pages/butler/apply.vue`(管家申请入驻页) |
+
+## 四、§1 数据模型与迁移
+
+仅 1 处变更:
+
+```sql
+-- butler_profiles 增加(DatabaseInitializer.ensureColumn 迁移 + schema.sql 同步)
+ALTER TABLE butler_profiles ADD COLUMN accepting TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否接受新家庭绑定 0=停接';
+```
+
+迁移要求:
+- 入口唯一:`config/DatabaseInitializer.runMigrations()` 内 `ensureColumn("butler_profiles", "accepting", ...)`,幂等
+- 同步更新 `schema.sql` 的 butler_profiles CREATE TABLE(:2626 起)
+- `mvn clean compile` 验证
+
+其余全部复用:`users.mascot` 不动;`butler_assignments` 换绑走 UPDATE;无新表。
+
+## 五、§2 后端接口(统一 @PostMapping,Result<T>)
+
+### 5.1 家庭端(新增控制器类 `FamilyButlerSelectController`,放 `controller/butler/` 包;Bean 名 familyButlerSelectController 与现有 butlerController 无冲突;业务方法加入现有 `ButlerService`)
+
+familyId 推导方式与 MembershipController 一致:`userMapper.selectById(userId).getFamilyId()`;familyId 为空 → error("未找到家庭信息")。
+
+| 接口 | 校验/逻辑 | 返回 |
+|------|-----------|------|
+| `POST /api/butler/available-list` | 查询条件:status='approved' AND tier='L2' AND accepting=1 AND member_count<max_members。**浏览不校验 L2**(非 L2 可看列表,绑定才校验)。脱敏:昵称/头像/简介/证书数/剩余名额,不含联系方式 | `Result<List<Map>>` |
+| `POST /api/butler/my-butler` | 按 familyId(取自 userId 归属家庭)查 assignments 行 + 关联管家摘要 + 当前订阅等级 | 有绑定返回详情;无绑定 data=null |
+| `POST /api/butler/select` `{butlerUserId}` | 事务内依次:①`getActiveSubscription` 且 level=='L2',否则 error("该功能面向久久一生会员")②目标管家 approved+tier='L2'+accepting=1③条件增容 `UPDATE butler_profiles SET member_count=member_count+1 WHERE id=? AND status='approved' AND accepting=1 AND member_count<max_members`,影响行数=0 → error("该管家名额已满") 并终止④assignments 存在行则 UPDATE butler_user_id(旧管家 member_count-1),不存在则 INSERT(family_id 冲突时按唯一键重试 UPDATE 路径)⑤记录 assigned_at 刷新 | success 后返回新管家摘要 |
+
+并发安全:容量扣减用条件 UPDATE 原子完成;解绑旧管家计数在成功换绑后执行;整体 `@Transactional(rollbackFor = Exception.class)`。
+
+### 5.2 AI 端(改现有 `AIChatController`)
+
+| 位置 | 变更 |
+|------|------|
+| `/health-coach/send` | 补 mascot 注入,与 `/chat/send` 对齐:inputs 增加 `mascot_name`/`mascot_gender`/`mascot_persona` + **`coach_id` = mascotCode 原始值**(xibao/fubao/null);缺省时 inputs.put("coach_id","") 由 LangGraph 兜底 |
+
+`UserController` 无改动(mascot 更新已有 updateProfile 路径);AiGateway.chat 签名不变(inputs 透传)。
+
+实现注意(已核实):`AiGateway.chat` 将 inputs 中 String/Number/Boolean 键值写入请求体 `context` 对象(AiGateway.java:162-168),LangGraph 侧从 `state["context"]` 读取——故 `coach_id` 放入 inputs 即可透传,**无需**修改 common.py 传输模型(其 `mascot_code` 字段与本次路由无关)。PII 脱敏 sanitizeContext 对 'xibao'/'fubao' 短码无影响。
+
+### 5.3 管理端(`AdminButlerController` 增量,沿用 /api/admin/butler/* 前缀与角色校验风格)
+
+| 接口 | 逻辑 |
+|------|------|
+| `POST /api/admin/butler/assignments` | 分页列表:联 users(家长昵称/头像)、butler_assignments、users(管家昵称)、member_subscription.level;筛选:管家ID/级别/关键字 |
+| `POST /api/admin/butler/unassign` `{assignmentId}` | 强制解绑:物理删除 assignments 行(服务留痕在 butler_service_records,无需保留分配行)+ 对应管家 member_count-1,事务 |
+| `POST /api/admin/butler/toggle-accepting` `{profileId, accepting}` | 更新 butler_profiles.accepting;仅影响新选择,不动存量绑定 |
+
+新增接口须同步登记 `docs/superpowers/api/API_REFERENCE.md`。
+
+## 六、§3 LangGraph(cfc-langgraph)
+
+1. `coach_id` 传输路径:Java inputs → AiGateway `body.context`(已核实)→ FastAPI → graph state 的 `context` 字典,**无需**改 common.py 传输模型;`generate_answer` 从 `state["context"]` 取值
+2. `generate_answer` 中提示词路由:
+
+```python
+ctx = state.get("context") or {}
+coach_id = ctx.get("coach_id")
+if coach_id not in ("xibao", "fubao"):
+    coach_id = None
+system_prompt = (
+    await get_prompt(f"health_coach_{coach_id}") if coach_id else None
+) or await get_prompt("health_coach") or DEFAULT_COACH_PROMPT
+```
+
+   即按人格 key 优先,逐级回退到全局 `health_coach`,再回退内置默认——任何一环缺失都不报错。
+3. 两段新 prompt 由运营在 SystemPromptManagement 页录入:
+   - `health_coach_xibao`:温柔知心风(倾听共情、温和建议、多用鼓励性语言)
+   - `health_coach_fubao`:活泼行动派(直接给行动清单、打卡式督促、轻快语气)
+   - 共享同一知识检索节点(health_retrieve 不动)、记忆节点不动
+
+## 七、§4 小程序前端(cfc-frontend,Vue2 Options API)
+
+| 文件 | 变更 |
+|------|------|
+| `pages/ai/chat.vue` | 微调欢迎语:isHealthCoach 分支由固定"你好!我是健康教练"改为拼接当前教练名(如"你好!我是{{mascotName}},你的健康教练");消息头像/导航主题已按 mascot 渲染不动 |
+| `pages/user-edit/user-edit.vue` | 已有 mascot 选择项;核对选项描述文案体现"AI 健康教练"语境,不改逻辑 |
+| **新页** `pages/butler/select.vue` | ①管家卡片列表(头像/昵称/简介/证书数/剩余名额徽标)+ 点击出确认弹窗(uni.showModal)②顶部状态区三态:未绑定(去选择)/已绑定(管家摘要卡+"更换管家"按钮)/L2 未订阅或过期(升级引导条跳会员购买页)③非 L2 用户可浏览列表但点选择时报错 toast(后端兜底)④遵守小程序约束:无 `?.`、flexbox、`:key="getItemKey(item)"` 方法调用、日期 `parseDate()`、禁止 CSS Grid |
+| 会员中心相关页 | 新增「我的管家」入口卡片:同上三态摘要;点击进 select 页 |
+| `pages.json` | 注册 select 页面路径与标题 |
+
+API 封装加至小程序 `utils/api.js`:getAvailableButlers / getMyButler / selectButler。
+
+## 八、§5 Web 管理端(cfc-web,Vue2 + Element UI)
+
+| 文件 | 变更 |
+|------|------|
+| **新视图** `views/admin/ButlerAssignments.vue` | el-table 分配关系:家庭ID/家长昵称/管家昵称/订阅级别/绑定时间;筛选栏(管家下拉+关键字);操作列「强制解绑」→ elMessageBox.confirm → unassign 接口 → 刷新 |
+| `views/admin/review/ButlerAudit.vue`(管家档案所在列表) | 新列「接单中」el-switch 绑 accepting,change 时调 toggle-accepting;accepting=0 行灰色标识"已停接" |
+| `api/butler.js` | 新增 getAssignments / unassignAssignment / toggleAccepting 封装 |
+| 路由表 + `permissions.js` | 注册 ButlerAssignments 页面与权限码 |
+
+## 九、§6 边界与错误处理
+
+| 场景 | 处理 |
+|------|------|
+| L2 到期仍有绑定 | 不自动解绑;家庭端入口显示"订阅已到期";管理员可强制解绑;无定时任务 |
+| 并发选满 | 条件 UPDATE 原子扣容(§5.1 步骤③),0 行受影响即"名额已满"回滚 |
+| 换绑频率 | 不限次数,每次完整校验 |
+| coach_id 缺失/非法 | 三级回退:人格 key → 全局 health_coach → DEFAULT_COACH_PROMPT,不报错 |
+| LangGraph 不可用 | AiGateway 熔断+Fallback 已覆盖,不动 |
+| 管家停接 | accepting=0 仅拦截新绑定(available-list 过滤 + select 校验双重拦截);存量家庭不受影响,其会话照常 |
+| 强制解绑后 | 家庭立即可重新自选任意合格管家 |
+| 首次选择 vs 换绑 | 订阅激活自动分配可能未产生 assignments 行(当时无合格管家):select 接口 INSERT/UPDATE 双路径兼容(§5.1 步骤④) |
+| 旧管家计数一致性 | 换绑/解绑均同步 member_count ±1;存量数据漂移校正不在本期范围(member_count 一律以条件更新维护) |
+
+## 十、不做的事(Out of Scope)
+
+- ❌ 小浠/小福更名(维持浠宝/福宝官方 IP 名)
+- ❌ coach_personas 数据库配置表(复用吉祥物管线,方案 B 原表设计作废)
+- ❌ 推送通知(项目推送通道未启用)
+- ❌ L1 家庭自选管家
+- ❌ 管家聊天功能扩展(`/api/ai/butler/*` 已有)
+- ❌ 教练人格的管理端 CRUD 元数据维护(prompt 文案走 SystemPromptManagement 即可)
+- ❌ 到期自动解绑定时任务
+
+## 十一、验收标准
+
+1. `cd cfc-backend && mvn clean compile` 通过(EXIT 0)
+2. 改动的小程序 vue 文件提取 script 块 `node --check` 通过(不做整包 build)
+3. L1 用户调 select → 明确错误文案;L2 用户全流程可用:浏览→选择→更换→解绑后重选
+4. 容量满的管家不出现在 available-list;select 直呼其 id 也被拒
+5. accepting=0 管家不可被新选,存量绑定家庭对话不受影响
+6. 切换 mascot 后 `/health-coach/send` 对话风格随 prompt key 生效;mascot 未设置时回退默认 prompt 正常对话
+7. 管理端可查看全部分配关系并可强制解绑;停接开关即时生效
+8. `API_REFERENCE.md` 已登记三个家庭端接口与三个管理端接口
+
+## 十二、实施顺序建议(供 writing-plans 参考)
+
+后端迁移+接口 → LangGraph prompt 路由(可与后端并行)→ 小程序页面 → Web 管理端 → API 文档同步 → 端到端验证