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:
MingNian
2026-06-03 23:17:37 +08:00
parent 5bd0155e17
commit c2399b952f
33 changed files with 3311 additions and 660 deletions

View 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行)
```