|
|
@@ -0,0 +1,388 @@
|
|
|
+# 数据与 AI 结合 — 实现计划
|
|
|
+
|
|
|
+## 依赖关系图
|
|
|
+
|
|
|
+```
|
|
|
+Phase 1.1 (DB迁移) ──→ 1.3 (createDynamicTask) ──→ 1.4 (Controller改造)
|
|
|
+ │
|
|
|
+Phase 1.2 (TaskParseService) ──────────────────────────┘
|
|
|
+ │
|
|
|
+Phase 1.8 (FamilyContext增强) ─────────────────────────┘
|
|
|
+ ↓
|
|
|
+ 1.9 (编译验证)
|
|
|
+ │
|
|
|
+ ┌───────────────────────────────────────┘
|
|
|
+ ↓
|
|
|
+Phase 1.5 (AITaskCard组件) ──→ 1.6 (chat.vue改造) ──→ 1.7 (报告详情页入口)
|
|
|
+ │
|
|
|
+ ↓
|
|
|
+ Phase 2.x (持续增强)
|
|
|
+```
|
|
|
+
|
|
|
+## Phase 1: 基础能力 — 2周
|
|
|
+
|
|
|
+### 1.1 数据库迁移 — growth_tasks 表扩展
|
|
|
+
|
|
|
+**目标:** 支持 AI_GENERATED 类型任务,新增维度跟踪字段
|
|
|
+
|
|
|
+**文件:**
|
|
|
+- `cfc-backend/src/main/resources/schema.sql` — 同步建表语句
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` — 迁移代码
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthTask.java` — 新增字段
|
|
|
+
|
|
|
+**迁移内容:**
|
|
|
+```sql
|
|
|
+-- migration N: growth_tasks 支持 AI 生成任务
|
|
|
+ALTER TABLE growth_tasks MODIFY COLUMN type
|
|
|
+ ENUM('DAILY','NEWBIE','AI_GENERATED') NOT NULL DEFAULT 'DAILY';
|
|
|
+ALTER TABLE growth_tasks ADD COLUMN dimension VARCHAR(20) NULL COMMENT '五维维度';
|
|
|
+ALTER TABLE growth_tasks ADD COLUMN source_conversation_id VARCHAR(100) NULL COMMENT '来源AI会话ID';
|
|
|
+```
|
|
|
+
|
|
|
+**实体类新增字段:**
|
|
|
+```java
|
|
|
+@TableField("dimension")
|
|
|
+private String dimension;
|
|
|
+
|
|
|
+@TableField("source_conversation_id")
|
|
|
+private String sourceConversationId;
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** `mvn clean compile` 通过,迁移可重复执行不报错
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.2 TaskParseService — [TASK] 标记解析
|
|
|
+
|
|
|
+**目标:** 新建服务解析 Dify 返回的 `[TASK:{...}]` 标记,提取任务参数
|
|
|
+
|
|
|
+**文件:** 新建 `cfc-backend/src/main/java/com/etotem/cfc/service/TaskParseService.java`
|
|
|
+
|
|
|
+```
|
|
|
+1. TASK: 解析 Dify answer 中的 [TASK:{"title":"...","description":"...",...}] 标记
|
|
|
+2. EXPECTED OUTCOME: 返回解析后的 TaskParseResult 对象列表
|
|
|
+3. REQUIRED TOOLS: 正则 Pattern + Jackson ObjectMapper
|
|
|
+4. MUST DO:
|
|
|
+ - 正则 `\[TASK:\s*(\{.*?\})\](?:\s*\[TASK:...)*` 提取所有 TASK 标记
|
|
|
+ - 解析 JSON → TaskParseResult 对象
|
|
|
+ - 校验 title 必填
|
|
|
+ - 返回解析结果列表
|
|
|
+ - 从原 answer 中移除所有 TASK 标记
|
|
|
+5. MUST NOT DO: 不写入数据库(由调用方控制)
|
|
|
+```
|
|
|
+
|
|
|
+**TaskParseResult DTO:**
|
|
|
+```java
|
|
|
+public class TaskParseResult {
|
|
|
+ private String title; // 必填
|
|
|
+ private String description; // 选填
|
|
|
+ private String deadline; // 选填 yyyy-MM-dd
|
|
|
+ private String dimension; // 选填 body/mind/wisdom/action/wealth
|
|
|
+ private Integer rewardPoints; // 选填
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** `"[TASK:{\"title\":\"读书30分钟\",\"dimension\":\"wisdom\"}]"` → 解析出 title=读书30分钟, dimension=wisdom
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.3 GrowthTaskService.createDynamicTask()
|
|
|
+
|
|
|
+**目标:** 新增方法,根据 AI 生成的任务参数创建 growth_tasks + growth_task_log
|
|
|
+
|
|
|
+**文件:** `cfc-backend/src/main/java/com/etotem/cfc/service/GrowthTaskService.java`
|
|
|
+
|
|
|
+**新增方法:**
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 创建 AI 动态生成任务
|
|
|
+ * @param userId 目标用户ID
|
|
|
+ * @param task 解析后的任务参数
|
|
|
+ * @param conversationId 来源AI会话ID
|
|
|
+ * @return 创建的任务ID
|
|
|
+ */
|
|
|
+@Transactional
|
|
|
+public Long createDynamicTask(Long userId, TaskParseResult task, String conversationId) {
|
|
|
+ // 1. 插入 growth_tasks (type=AI_GENERATED, enabled=1)
|
|
|
+ // 2. 插入 growth_task_log (progress=0, completed=0, claimed=0)
|
|
|
+ // 3. 返回 taskId
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** 调用后 growth_tasks 和 growth_task_log 各多一条记录
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.4 AIChatController 通用化改造
|
|
|
+
|
|
|
+**目标:** 统一处理 `[RECOMMEND]` 和 `[TASK]` 标记,全模式支持推荐
|
|
|
+
|
|
|
+**文件:** `cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java`
|
|
|
+
|
|
|
+**改动点:**
|
|
|
+
|
|
|
+1. **sendMessage() 方法改造:**
|
|
|
+ - 将原来只在 sendNutritionMessage() 中的 RECOMMEND 解析提取为通用方法
|
|
|
+ - 在 sendMessage() 返回前统一调用 `parseAndHandleMarkers(answer)`
|
|
|
+ - 返回值结构扩展:`{ answer, conversationId, recommendations, tasks }`
|
|
|
+
|
|
|
+2. **parseAndHandleMarkers() 新增方法:**
|
|
|
+ ```java
|
|
|
+ private Map<String, Object> parseAndHandleMarkers(String answer, Long userId, String conversationId) {
|
|
|
+ // 1. 解析 [RECOMMEND:...] → RecommendationService.search
|
|
|
+ // 2. 解析 [TASK:...] → TaskParseService → GrowthTaskService.createDynamicTask
|
|
|
+ // 3. 返回 { cleanAnswer, recommendations, tasks }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+3. **sendNutritionMessage() 简化:** 复用 parseAndHandleMarkers() 避免重复
|
|
|
+
|
|
|
+**验收:**
|
|
|
+- 普通对话输入"帮我推荐一些健康食品" → response 含 recommendations 列表
|
|
|
+- 普通对话输入"给我安排一个每天读书的任务" → response 含 tasks 列表
|
|
|
+- 旧功能不受影响(回归:营养对话仍能正常推荐)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.5 AITaskCard 组件
|
|
|
+
|
|
|
+**目标:** 可复用的任务卡片组件,展示任务信息 + 接受按钮
|
|
|
+
|
|
|
+**文件:** 新建 `cfc-frontend/components/AITaskCard.vue`
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <view class="ai-task-card">
|
|
|
+ <view class="task-header">
|
|
|
+ <text class="task-icon">📋</text>
|
|
|
+ <text class="task-title">{{ task.title }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="task-desc" v-if="task.description">{{ task.description }}</text>
|
|
|
+ <view class="task-footer">
|
|
|
+ <text class="task-dimension" v-if="task.dimension">维度:{{ dimLabel }}</text>
|
|
|
+ <text class="task-deadline" v-if="task.deadline">截止:{{ task.deadline }}</text>
|
|
|
+ </view>
|
|
|
+ <button class="task-accept-btn" :loading="accepting" @click="handleAccept">接受任务</button>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ task: Object,
|
|
|
+ conversationId: String
|
|
|
+ },
|
|
|
+ data() { return { accepting: false } },
|
|
|
+ methods: {
|
|
|
+ async handleAccept() {
|
|
|
+ // 调用 POST /api/tasks/accept-dynamic
|
|
|
+ // 参数: { taskTitle, taskDescription, deadline, dimension, conversationId }
|
|
|
+ this.accepting = true
|
|
|
+ try {
|
|
|
+ const res = await api.acceptDynamicTask({...})
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '任务已接受' })
|
|
|
+ this.$emit('accepted', res.data)
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ this.accepting = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** 组件在 chat.vue 中导入后可正常渲染和点击接受
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.6 chat.vue 改造
|
|
|
+
|
|
|
+**目标:** 支持任务卡片渲染、全模式推荐展示、报告解读模式
|
|
|
+
|
|
|
+**文件:** `cfc-frontend/pages/ai/chat.vue`
|
|
|
+
|
|
|
+**改动点:**
|
|
|
+
|
|
|
+1. **消息结构扩展** — 在 data 中支持 tasks 字段
|
|
|
+2. **模板新增任务卡片区域** — messages 循环中插入 AITaskCard
|
|
|
+3. **推荐卡片区域增强** — 从 only nutrition 模式扩展到所有模式
|
|
|
+4. **新模版:`mode=report`** — 接收 reportId 参数,sendMessage 时自动传入
|
|
|
+5. **消息气泡标注来源** — 如果是报告解读,气泡上显示"基于健康报告"标签
|
|
|
+
|
|
|
+```
|
|
|
+改动区段:
|
|
|
+- <template> 中 #80~90 行,推荐卡片区域改为无条件渲染
|
|
|
+- <template> 消息气泡内新增任务卡片区域
|
|
|
+- <script> data() 中新增支持任务字段
|
|
|
+- <script> sendMessage() 中参数扩展:reportId
|
|
|
+- <script> onLoad() 中新增 mode=report 处理
|
|
|
+```
|
|
|
+
|
|
|
+**验收:**
|
|
|
+- 普通对话返回推荐数据 → 显示推荐卡片
|
|
|
+- AI 返回任务 → 显示任务卡片 + 可点击接受
|
|
|
+- 跳转 `chat.vue?mode=report&reportId=123` → 发消息时自动带 reportId
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.7 报告详情页 AI 解读入口
|
|
|
+
|
|
|
+**目标:** 各报告详情页底部增加"让浠宝/福宝解读"入口
|
|
|
+
|
|
|
+**文件:**
|
|
|
+- `cfc-frontend/pages/body-detail/health-report.vue`
|
|
|
+- `cfc-frontend/pages/health/gut-index.vue`
|
|
|
+- `cfc-frontend/pages/health/tongue-index.vue`
|
|
|
+- `cfc-frontend/pages/health/report-upload.vue`
|
|
|
+- `cfc-frontend/pages/body-detail/health-report.vue`
|
|
|
+
|
|
|
+**实现方式:** 各页面的模板底部新增:
|
|
|
+```vue
|
|
|
+<view class="ai-interpret-entry" @click="goAIChat">
|
|
|
+ <image class="ai-mascot-avatar" :src="mascotAvatar" />
|
|
|
+ <view class="ai-entry-text">
|
|
|
+ <text class="ai-entry-title">让 {{ mascotName }} 帮你解读</text>
|
|
|
+ <text class="ai-entry-desc">基于这份报告,{{ mascotName }} 会给出个建议</text>
|
|
|
+ </view>
|
|
|
+ <text class="ai-entry-arrow">›</text>
|
|
|
+</view>
|
|
|
+```
|
|
|
+
|
|
|
+**goAIChat 方法:**
|
|
|
+```javascript
|
|
|
+goAIChat() {
|
|
|
+ const userInfo = uni.getStorageSync('userInfo')
|
|
|
+ const mascot = userInfo && userInfo.mascot === 'fubao' ? '福宝' : '浠宝'
|
|
|
+ uni.navigateTo({
|
|
|
+ url: '/pages/ai/chat?mode=report&reportId=' + this.reportId + '&firstMessage='
|
|
|
+ + encodeURIComponent('你好' + mascot + ',帮我看看这份报告有什么需要注意的地方?')
|
|
|
+ })
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** 每个报告详情页底部可见 AI 入口,点击后跳转 chat.vue 并自动发送解读请求
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.8 FamilyContextService 任务上下文注入
|
|
|
+
|
|
|
+**目标:** 在 AI 上下文中注入用户当前任务状态,让 Dify 感知任务进度
|
|
|
+
|
|
|
+**文件:** `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java`
|
|
|
+
|
|
|
+**改动点:** `buildContext()` 末尾增加:
|
|
|
+```java
|
|
|
+// 6. 当前未完成任务列表
|
|
|
+List<Map<String, Object>> pendingTasks = getPendingTasks(userId);
|
|
|
+if (!pendingTasks.isEmpty()) {
|
|
|
+ ctx.put("pendingTasks", pendingTasks);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```java
|
|
|
+private List<Map<String, Object>> getPendingTasks(Long userId) {
|
|
|
+ // 查询当前用户未完成的 growth_task_log 关联的 growth_tasks
|
|
|
+ // 返回 title, type, dimension 等信息
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** 用户有未完成任务时,Dify inputs 中包含 `pendingTasks` 字段
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 1.9 编译验证
|
|
|
+
|
|
|
+**目标:** 确保 Phase 1 所有后端改动编译通过,无路由冲突
|
|
|
+
|
|
|
+```bash
|
|
|
+cd cfc-backend && mvn clean compile
|
|
|
+grep -rn '@Mapping' src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | grep -E '(task|ai)'
|
|
|
+```
|
|
|
+
|
|
|
+**验收:** `mvn clean compile` exit 0,新增路由无重复
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Phase 2: 智能增强 — 2周
|
|
|
+
|
|
|
+### 2.1 Dify Workflow 合并
|
|
|
+
|
|
|
+**平台配置(在 Dify 平台操作,非代码):**
|
|
|
+1. 在 Dify 中创建新的 App(含 Workflow)
|
|
|
+2. 配置 Classifier Node,基于 query+inputs 做场景分类
|
|
|
+3. 配置 Report Analysis Agent / Nutrition Advisor Agent / Task Manager Agent / Family Assistant Agent
|
|
|
+4. 配置 Post Processor 格式化输出(合并 RECOMMEND / TASK 标记)
|
|
|
+5. 配置回调 URL 指向 `/api/ai/context`(保留原有记忆逻辑)
|
|
|
+6. 生成新的 API Key
|
|
|
+
|
|
|
+**后端适配:**
|
|
|
+- `AIService.java`:统一使用新 API Key,删除 nutritionApiKey/tongueApiKey
|
|
|
+- 响应兼容处理(workflow 返回格式可能与 chat-messages 有差异)
|
|
|
+
|
|
|
+**验收:** 现有所有 AI 功能在新 App 上正常运作
|
|
|
+
|
|
|
+### 2.2 推荐排序优化
|
|
|
+
|
|
|
+**文件:** `cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java`
|
|
|
+
|
|
|
+**改动点:**
|
|
|
+1. `search()` 方法中增加排序逻辑:
|
|
|
+ - 标签匹配度排序(匹配标签数多的优先)
|
|
|
+ - 已购商品降权(`ProductOrder` 中已购买过的商品排后)
|
|
|
+ - 维度匹配加分(与用户当前关注维度对齐的优先)
|
|
|
+ - 按 `createdAt` 时效性排序
|
|
|
+2. 前端推荐卡片增加"已购买"标记
|
|
|
+
|
|
|
+### 2.3 任务展示与接受完整流程
|
|
|
+
|
|
|
+**后端:** 新建 `POST /api/tasks/accept-dynamic`
|
|
|
+```java
|
|
|
+@PostMapping("/api/tasks/accept-dynamic")
|
|
|
+public Result<Long> acceptDynamicTask(@RequestAttribute("userId") Long userId,
|
|
|
+ @RequestBody Map<String, Object> params) {
|
|
|
+ // 参数:title, description, deadline, dimension, conversationId
|
|
|
+ // 调用 growthTaskService.createDynamicTask()
|
|
|
+ // 返回 taskId
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**前端:** AITaskCard 组件点击"接受任务" → 调用此接口 → 显示成功提示
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Phase 3: 体验优化 — 持续
|
|
|
+
|
|
|
+### 3.1 Mascot 动效与个性化
|
|
|
+
|
|
|
+- 浠宝(🌟):暖橙色系动画,温柔语音提示
|
|
|
+- 福宝(💪):金色系动画,活泼语气
|
|
|
+- 表情随对话内容变化(开心/思考/鼓励)
|
|
|
+
|
|
|
+### 3.2 A/B 测试
|
|
|
+
|
|
|
+- 对照组:无 AI 解读入口的报告详情页
|
|
|
+- 实验组:有 AI 解读入口
|
|
|
+- 指标:解读点击率、任务接受率、次日回访率
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 团队任务分配建议
|
|
|
+
|
|
|
+| 开发 | 任务 |
|
|
|
+|------|------|
|
|
|
+| 后端 A | 1.1 DB迁移 + 1.2 TaskParseService + 1.3 createDynamicTask |
|
|
|
+| 后端 B | 1.4 Controller改造 + 1.8 FamilyContext增强 + 1.9 编译验证 |
|
|
|
+| 前端 A | 1.5 AITaskCard组件 + 1.6 chat.vue改造 |
|
|
|
+| 前端 B | 1.7 各报告详情页AI入口 |
|
|
|
+| 并行启动 | 1.1/1.2 可先启动(无依赖),1.5 可先启动(无依赖) |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 回滚策略
|
|
|
+
|
|
|
+- 数据库迁移:`ALTER TABLE ... MODIFY COLUMN` 改回原 ENUM,`DROP COLUMN` 新字段
|
|
|
+- 后端:回滚 Git 提交
|
|
|
+- 前端:`git revert` 对应提交
|
|
|
+- Dify:保留旧 App 配置,新 App 验证通过后再切换
|