日期:2026-09-10 状态:已批准
报告详情页(report-detail.vue)目前只有被动的数据展示,用户无法针对报告内容主动提问。本功能提供「快速分析」入口,让用户输入问题,AI 基于当前报告内容做初步解读。
免费版价值主张:免费用户也能体验 AI 报告解读(每日 1 次),作为免费版的核心功能之一,同时引导升级付费版。
pages/ai/chat.vue 的能力范围之外)chat_graph(零 Python 改动)复用现有 AI 链路,最小新增。核心思路:后端加一个端点 + Redis 计数,前端加按钮和浮层,LangGraph 零改动。
report-detail.vue(底部按钮 + 浮层)
│ POST /api/ai/report/analyze { query, reportId, conversationId?, memberId? }
▼
AIChatController 新增端点 /report/analyze
│ 1. 查 User.memberLevel(FREE/FAMILY/PROVIDER)
│ 2. FREE → Redis INCR dailyKey,>1 拒绝
│ 3. familyContextService.buildContext(userId, reportId) 组装报告上下文
│ 4. AiGateway.chat() → LangGraph /api/v1/chat
▼
LangGraph chat_graph(现有,零改动)
classify → load_context → llm_call → answer
POST /api/ai/report/analyzefamilyContextService.buildContext(userId, reportId) 已支持报告注入)AiGateway.chat() → LangGraph /api/v1/chatchat_graph 现有流程返回 answerchat_graph 已支持 context.report_id 注入(chat_graph.py 129-137 行:report_id → 家庭上下文 SystemMessage)familyContextService.buildContext(userId, reportId) 已组装报告相关上下文(health_reports + indicators)AiGateway.chat(query, userId, conversationId, inputs) 已透传 context结论:后端一个端点 + 前端浮层即完成,LangGraph 零改动。
/api/ai/report/analyze(而非复用 /chat/send)/chat/send 承载家庭助手通用聊天,职责过重sendMessage 内部已有的上下文组装逻辑:在 AIChatController 内抽出私有方法 buildChatInputs(userId, params) 供 /chat/send 和 /report/analyze 共同调用// 在 AIChatController.reportAnalyze() 内
String key = "report:analyze:free:" + userId + ":" + LocalDate.now();
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) redisTemplate.expire(key, Duration.ofDays(1));
if (count > 1) return Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多");
log.warnlocalhost:6379,生产由运维提供连接信息)conversationId → LangGraph 保持上下文 → 可追问conversationId(不传则 LangGraph 每次新建会话)→ 天然单次report-detail.vue 底部新增「🎯 快速分析」悬浮按钮(仅 view 模式显示)conversationId 回传),无按钮禁用逻辑| 文件 | 改动 |
|---|---|
pom.xml |
新增 spring-boot-starter-data-redis 依赖 |
application.yml |
Redis 连接配置 |
RedisConfig.java(新增 config/) |
RedisTemplate bean,String 序列化 |
AIChatController.java |
新增 POST /report/analyze 端点:会员检查 + Redis 计数 + 上下文组装 + 调 AiGateway。复用 sendMessage 的上下文组装逻辑(抽出私有方法) |
端点签名:
POST /api/ai/report/analyze
Request: { "query": "..." , "reportId": 123, "conversationId": "可选", "memberId": "可选" }
Response: Result<{ answer, conversationId }>
| 文件 | 改动 |
|---|---|
report-detail.vue |
新增底部「🎯 快速分析」按钮 + 浮层组件(消息区/输入框/快捷问题/加载态/禁用态) |
api.js |
新增 reportQuickAnalyze(query, reportId, conversationId) 封装 |
User.memberLevel(FREE/FAMILY/PROVIDER)userId,端点内 userService.getUserInfo(userId) 取 memberLevelgetMyMembership() 预判(仅用于 UI 展示,最终以后端校验为准)| 场景 | 行为 |
|---|---|
| query 为空 | Result.error("消息不能为空") |
| reportId 为空 | Result.error("报告ID不能为空") |
| 免费版超限 | Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多") |
| Redis 异常(免费计数失败) | fail-open 放行 + log.warn("Redis 计数失败,放行: {}", e.getMessage()) |
| AiGateway 返回 null(熔断/超时) | Result.error("AI 服务暂不可用,请稍后重试") |
| LangGraph 调用异常 | 同上,由 AiGateway 捕获后返回 null,端点统一处理 |
cd cfc-backend && mvn clean compile
| 场景 | 预期 |
|---|---|
| 免费版第 1 次提问 | 正常返回 answer |
| 免费版第 2 次提问 | 返回错误「免费版每日仅限 1 次…」 |
| 免费版次日 | 计数重置,可再问 |
| 付费版多轮 | 追问时带 conversationId,AI 记得上文 |
| 报告上下文注入 | 回答体现报告内容(如指标/菌群) |
| Redis 停掉 | 免费版仍可用(fail-open),记 warn 日志 |
redis.host 指向测试环境familyContextService.buildContext 已做截断控制(沿 sendMessage 既有逻辑,无需新增)