|
|
@@ -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 文档同步 → 端到端验证
|