Files
AI-Health/健康管家-首次建档引导实施文档.md
MingNian c2399b952f 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份实施文档(情况/问诊/报告/建档/日历/视觉统一)
2026-06-03 23:17:37 +08:00

14 KiB
Raw Blame History

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

private const string OnboardingPrompt = """
    你是一个健康管家助手,正在帮助新用户建立健康档案。

    必须严格遵守以下流程,一次只问一个问题:

    步骤1:询问主要诊断(冠心病/高血压/糖尿病/其他)
    步骤2:询问手术史(PCI支架植入/心脏搭桥/其他/没有)→ 追问手术日期
    步骤3:询问过敏史(青霉素/头孢/海鲜/鸡蛋/无过敏)→ 可多选
    步骤4:询问慢性病史(高血脂/糖尿病/痛风/无)
    步骤5:询问饮食限制(低盐/低脂/低糖/无限制)

    规则:
    - 每次只问一个问题,给出 2-4 个快捷选项
    - 用户回答后,调用 manage_archive 工具保存
    - 用户说"跳过"/"以后再说"/"再说" 礼貌结束建档
    - 完成后生成结构化总结卡片
    - 语气温暖、像朋友一样
    """;

同时在 GetSystemPrompt 的 switch 中加入:

AgentType.Onboarding => OnboardingPrompt,

3.2 AgentType 枚举

health_enums.csAgentType 新增:

Onboarding,  // 建档引导

3.3 新增 manage_archive Tool

CommonAgentHandler.cs 新增:

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 改动

// GetToolsForAgent 新增:
AgentType.Onboarding => [CommonAgentHandler.ManageArchiveTool, CommonAgentHandler.CheckArchiveTool],

// ExecuteToolCall 新增:
"manage_archive" => CommonAgentHandler.Execute(toolName, root, db, userId),

四、前端改动

4.1 检测是否新用户

HomePage 初始化后,首次插入引导消息:

// 在 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 枚举新增:

enum MessageType { ..., onboarding }

4.3 新增 OnboardingCard 组件

chat_messages_view.dart 新增渲染分支:

case MessageType.onboarding:
    return _buildOnboardingCard(context, ref, msg);

引导卡片 UI

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

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