refactor: 4层架构重构 + 饮食VLM接入 + 多项修复
- 后端: remaining_endpoints拆分为6个独立文件 - 后端: AI Agent Handler从ai_chat_endpoints抽取为7个独立处理器 - 后端: 食物识别prompt改为输出结构化JSON - 前端: 饮食识别从Mock替换为真实VLM API调用 - 前端: 首页图片上传URL修复(/api/upload→/api/files/upload) - 前端: 拍饮食按钮导航到独立DietCapturePage - 前端: 删除无用agent_bar.dart - 前端: 修复widget_test.dart过期属性名 - 前端: 恢复ServicePackageCard和详情页 - 新增6份实施文档(情况/问诊/报告/建档/日历/视觉统一)
This commit is contained in:
329
健康管家-首次建档引导实施文档.md
Normal file
329
健康管家-首次建档引导实施文档.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# AI 首次建档引导 — 实施文档
|
||||
|
||||
## 一、触发条件
|
||||
|
||||
新用户登录后同时满足以下条件时触发:
|
||||
|
||||
1. `HealthArchive` 所有字段为空(Diagnosis、SurgeryType、Allergies 等全空)
|
||||
2. 当前对话是新会话(无历史消息)
|
||||
3. 用户尚未主动输入过内容
|
||||
|
||||
## 二、交互流程
|
||||
|
||||
```
|
||||
用户登录 → 首页空白对话页
|
||||
│
|
||||
▼
|
||||
AI 主动发送引导消息(约 2 秒延迟后出现):
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 🤖 您好,我是您的AI健康管家! │
|
||||
│ │
|
||||
│ 为了给您更精准的健康建议,我需要了解一些 │
|
||||
│ 基本信息。不会太久,大约 2-3 分钟即可完成。 │
|
||||
│ │
|
||||
│ 您可以随时说"跳过"或"以后再说"。 │
|
||||
│ │
|
||||
│ [开始建档] [以后再说] │
|
||||
└──────────────────────────────────────────────┘
|
||||
│
|
||||
├── 点"以后再说" → 引导卡片消失,正常对话
|
||||
│
|
||||
└── 点"开始建档" → 进入分步引导
|
||||
│
|
||||
├── Q1: "您的主要诊断是什么?"
|
||||
│ 选项: [冠心病] [高血压] [糖尿病] [其他]
|
||||
│ → 用户选择/输入 → 调用 check_archive → 存 diagnosis
|
||||
│
|
||||
├── Q2: "您做过什么手术吗?"
|
||||
│ 选项: [PCI支架植入术] [心脏搭桥] [其他手术] [没有]
|
||||
│ 追问: "什么时候做的手术?" → 存 surgeryType + surgeryDate
|
||||
│
|
||||
├── Q3: "您对哪些药物或食物过敏?"
|
||||
│ 选项: [青霉素] [头孢] [海鲜] [鸡蛋] [无过敏]
|
||||
│ 多选 → 存 allergies
|
||||
│
|
||||
├── Q4: "您有其他慢性病史吗?"
|
||||
│ 选项: [高血脂] [糖尿病] [痛风] [无]
|
||||
│ 多选 → 存 chronicDiseases
|
||||
│
|
||||
├── Q5: "您有饮食方面的限制吗?"
|
||||
│ 选项: [低盐] [低脂] [低糖] [无限制]
|
||||
│ 多选 → 存 dietRestrictions
|
||||
│
|
||||
└── 完成 → AI 总结卡片
|
||||
┌──────────────────────────────────────┐
|
||||
│ ✅ 健康档案已建立 │
|
||||
│ │
|
||||
│ 诊断:冠心病 │
|
||||
│ 手术:PCI支架植入术 (2026-03) │
|
||||
│ 过敏:青霉素 │
|
||||
│ 慢病:高血脂 │
|
||||
│ 饮食:低盐、低脂 │
|
||||
│ │
|
||||
│ 随时可以跟我说"修改档案"来更新这些 │
|
||||
│ 信息。接下来,有什么可以帮您的? │
|
||||
│ │
|
||||
│ [查看档案] [开始记录健康数据] │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 三、后端改动
|
||||
|
||||
### 3.1 新增建档专用 Agent
|
||||
|
||||
在 `PromptManager.cs` 新增 System Prompt:
|
||||
|
||||
```csharp
|
||||
private const string OnboardingPrompt = """
|
||||
你是一个健康管家助手,正在帮助新用户建立健康档案。
|
||||
|
||||
必须严格遵守以下流程,一次只问一个问题:
|
||||
|
||||
步骤1:询问主要诊断(冠心病/高血压/糖尿病/其他)
|
||||
步骤2:询问手术史(PCI支架植入/心脏搭桥/其他/没有)→ 追问手术日期
|
||||
步骤3:询问过敏史(青霉素/头孢/海鲜/鸡蛋/无过敏)→ 可多选
|
||||
步骤4:询问慢性病史(高血脂/糖尿病/痛风/无)
|
||||
步骤5:询问饮食限制(低盐/低脂/低糖/无限制)
|
||||
|
||||
规则:
|
||||
- 每次只问一个问题,给出 2-4 个快捷选项
|
||||
- 用户回答后,调用 manage_archive 工具保存
|
||||
- 用户说"跳过"/"以后再说"/"再说"→ 礼貌结束建档
|
||||
- 完成后生成结构化总结卡片
|
||||
- 语气温暖、像朋友一样
|
||||
""";
|
||||
```
|
||||
|
||||
同时在 `GetSystemPrompt` 的 switch 中加入:
|
||||
```csharp
|
||||
AgentType.Onboarding => OnboardingPrompt,
|
||||
```
|
||||
|
||||
### 3.2 AgentType 枚举
|
||||
|
||||
`health_enums.cs` 的 `AgentType` 新增:
|
||||
```csharp
|
||||
Onboarding, // 建档引导
|
||||
```
|
||||
|
||||
### 3.3 新增 manage_archive Tool
|
||||
|
||||
在 `CommonAgentHandler.cs` 新增:
|
||||
|
||||
```csharp
|
||||
public static readonly ToolDefinition ManageArchiveTool = new()
|
||||
{
|
||||
Function = new()
|
||||
{
|
||||
Name = "manage_archive",
|
||||
Description = "管理用户健康档案(更新诊断/手术/过敏/慢性病/饮食限制)",
|
||||
Parameters = new
|
||||
{
|
||||
type = "object",
|
||||
properties = new
|
||||
{
|
||||
action = new { type = "string", description = "update_diagnosis / update_surgery / update_allergies / update_chronic_diseases / update_diet_restrictions / query" },
|
||||
diagnosis = new { type = "string" },
|
||||
surgery_type = new { type = "string" },
|
||||
surgery_date = new { type = "string" },
|
||||
allergies = new { type = "array", items = new { type = "string" } },
|
||||
chronic_diseases = new { type = "array", items = new { type = "string" } },
|
||||
diet_restrictions = new { type = "array", items = new { type = "string" } },
|
||||
},
|
||||
required = new[] { "action" }
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
执行函数:根据 action 字段更新 `HealthArchive` 对应字段。
|
||||
|
||||
### 3.4 ai_chat_endpoints.cs 改动
|
||||
|
||||
```csharp
|
||||
// GetToolsForAgent 新增:
|
||||
AgentType.Onboarding => [CommonAgentHandler.ManageArchiveTool, CommonAgentHandler.CheckArchiveTool],
|
||||
|
||||
// ExecuteToolCall 新增:
|
||||
"manage_archive" => CommonAgentHandler.Execute(toolName, root, db, userId),
|
||||
```
|
||||
|
||||
## 四、前端改动
|
||||
|
||||
### 4.1 检测是否新用户
|
||||
|
||||
HomePage 初始化后,首次插入引导消息:
|
||||
|
||||
```dart
|
||||
// 在 ChatNotifier.build() 或 init 中
|
||||
void _checkOnboarding() {
|
||||
// 如果用户档案为空且第一次打开
|
||||
final archive = ref.read(healthArchiveProvider);
|
||||
// 插入引导消息
|
||||
state = state.copyWith(messages: [
|
||||
...state.messages,
|
||||
ChatMessage(
|
||||
id: 'onboarding_greeting',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: DateTime.now(),
|
||||
type: MessageType.onboarding,
|
||||
),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 新增 MessageType.onboarding
|
||||
|
||||
`chat_provider.dart` 枚举新增:
|
||||
```dart
|
||||
enum MessageType { ..., onboarding }
|
||||
```
|
||||
|
||||
### 4.3 新增 OnboardingCard 组件
|
||||
|
||||
在 `chat_messages_view.dart` 新增渲染分支:
|
||||
|
||||
```dart
|
||||
case MessageType.onboarding:
|
||||
return _buildOnboardingCard(context, ref, msg);
|
||||
```
|
||||
|
||||
引导卡片 UI:
|
||||
```dart
|
||||
Widget _buildOnboardingCard(BuildContext context, WidgetRef ref, ChatMessage msg) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 12),
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.88),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [BoxShadow(
|
||||
color: Color(0xFF8B9CF7).withAlpha(25),
|
||||
blurRadius: 14, offset: Offset(0, 4),
|
||||
)],
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 渐变色头部
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.fromLTRB(20, 24, 20, 16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFF8B9CF7), Color(0xFFA78BFA)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Container(
|
||||
width: 48, height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(30),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(Icons.health_and_safety, size: 28, color: Colors.white),
|
||||
),
|
||||
SizedBox(width: 14),
|
||||
Text('欢迎来到健康管家!', style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
]),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'我是您的AI健康管家。为了给您更精准的建议,\n我先了解一些基本信息好吗?大约 2-3 分钟。',
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withAlpha(220), height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 按钮
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(18, 18, 18, 20),
|
||||
child: Column(children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// 切换到 Onboarding Agent,开始 SSE 对话
|
||||
ref.read(chatProvider.notifier)
|
||||
.setAgent(ActiveAgent.onboarding);
|
||||
ref.read(chatProvider.notifier)
|
||||
.sendToOnboarding();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF8B9CF7),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
padding: EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: Text('开始建档', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(chatProvider.notifier).dismissOnboarding();
|
||||
},
|
||||
child: Text('以后再说', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 建档完成后的总结卡片
|
||||
|
||||
复用现有的 `MessageType.dataConfirm` 样式,增加 `MessageType.onboardingComplete`,展示结构化档案总结 + "[查看档案] [开始记录]" 按钮。
|
||||
|
||||
### 4.5 ActiveAgent 枚举新增
|
||||
|
||||
`chat_provider.dart`:
|
||||
```dart
|
||||
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise, onboarding }
|
||||
```
|
||||
|
||||
Agent 栏不显示 onboarding,它是对话内触发的。
|
||||
|
||||
## 五、后端-前端交互方式
|
||||
|
||||
建档过程直接用 **SSE 对话** 驱动:
|
||||
|
||||
1. 前端插入 OnboardingCard
|
||||
2. 用户点击"开始建档" → 前端调 `sendMessage("开始建档")`,agentType 设为 `onboarding`
|
||||
3. 后端用 `OnboardingPrompt` + `manage_archive` tool 走 SSE 流程
|
||||
4. AI 分步提问,用户回复,AI 调 tool 存入 HealthArchive
|
||||
5. 每步保存完成后前端刷新侧边栏数据
|
||||
6. 建档完成后返回总结卡片,Agent 切回 Default
|
||||
|
||||
## 六、跳过逻辑
|
||||
|
||||
- 点"以后再说" → 卡片消失,保存标记(LocalDatabase 记录 `onboarding_skipped_at`)
|
||||
- 用户说"跳过"/"以后再说" → AI 回复"好的,随时可以跟我说'完善档案'来继续"
|
||||
- 7 天内不重复弹出
|
||||
|
||||
## 七、文件改动清单
|
||||
|
||||
```
|
||||
后端:
|
||||
修改: Enums/health_enums.cs (+ Onboarding 枚举, 1行)
|
||||
修改: AI/prompt_manager.cs (+ OnboardingPrompt, ~30行)
|
||||
修改: AI/AgentHandlers/common_agent_handler.cs (+ ManageArchiveTool + Execute, ~80行)
|
||||
修改: Endpoints/ai_chat_endpoints.cs (+ Onboarding 路由, ~5行)
|
||||
|
||||
前端:
|
||||
修改: providers/chat_provider.dart (+ ActiveAgent.onboarding, + onboarding 方法, ~40行)
|
||||
修改: pages/home/widgets/chat_messages_view.dart (+ OnboardingCard 渲染, ~100行)
|
||||
修改: pages/home/home_page.dart (+ 检测触发逻辑, ~10行)
|
||||
```
|
||||
Reference in New Issue
Block a user