feat: 问诊对话+建档引导+报告+日历+趋势页重写+视觉统一

- 问诊对话页: 新建consultation_provider, 完整AI分身聊天UI
- 首次建档引导: OnboardingPrompt+ManageArchiveTool+前端卡片
- 报告模块: POST端点 + report_agent_handler接VLM
- 健康日历: GET /api/calendar端点 + 前端接API
- 趋势页重写: 删除Mock数据 + 真实API + 手动录入
- 饮食保存: submit按钮接后端API
- 侧边栏: 健康概览横排 + 统一紫色配色
- 运动计划: 修复JSON反序列化(record→手动解析)
- VLM: prompt优化 + 模型切qwen3-vl-plus + 1280px压缩
- UI颜色: 加深主色 8B9CF7→6C5CE7
- 个人信息页精简 + 健康档案可编辑重设计
- 保洁: 删除无用agent_bar + 删除意见反馈/关于我们
- 新增AI提示词文档
This commit is contained in:
MingNian
2026-06-04 13:49:43 +08:00
parent c2399b952f
commit 0e0e8ce72b
29 changed files with 1621 additions and 3361 deletions

View File

@@ -5,7 +5,7 @@ import 'auth_provider.dart';
import 'data_providers.dart';
import '../utils/sse_handler.dart';
enum MessageType { text, dataConfirm, medicationConfirm, dietAnalysis, reportAnalysis, quickOptions, agentWelcome, taskCard }
enum MessageType { text, dataConfirm, medicationConfirm, dietAnalysis, reportAnalysis, quickOptions, agentWelcome, taskCard, onboarding }
class ChatMessage {
final String id;
@@ -25,7 +25,7 @@ class ChatMessage {
bool get isUser => role == 'user';
}
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise }
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise, onboarding }
class ChatState {
final ActiveAgent activeAgent;
@@ -116,11 +116,51 @@ class ChatNotifier extends Notifier<ChatState> {
@override
ChatState build() {
// 首次加载时插入今日任务卡片作为第一条消息
Future.microtask(() => insertTaskCard());
Future.microtask(() {
insertTaskCard();
_checkOnboarding();
});
return const ChatState();
}
void _checkOnboarding() async {
try {
final api = ref.read(apiClientProvider);
final res = await api.get('/api/user/health-archive');
final archive = res.data['data'];
// 档案全空 → 触发建档引导
final isEmpty = archive == null ||
(archive['diagnosis'] == null && archive['surgeryType'] == null &&
(archive['allergies'] == null || (archive['allergies'] as List).isEmpty) &&
(archive['chronicDiseases'] == null || (archive['chronicDiseases'] as List).isEmpty) &&
(archive['dietRestrictions'] == null || (archive['dietRestrictions'] as List).isEmpty));
if (isEmpty && !state.messages.any((m) => m.type == MessageType.onboarding)) {
final db = ref.read(localDbProvider);
final skipped = await db.read('onboarding_skipped');
if (skipped == null) {
state = state.copyWith(messages: [
...state.messages,
ChatMessage(id: 'onboarding_card', role: 'assistant', content: '', createdAt: DateTime.now(), type: MessageType.onboarding),
]);
}
}
} catch (_) {}
}
void startOnboarding() {
setAgent(ActiveAgent.onboarding);
// Remove onboarding card
state = state.copyWith(messages: state.messages.where((m) => m.type != MessageType.onboarding).toList());
// Send trigger message
Future.microtask(() => _sendToAI('开始建档'));
}
void dismissOnboarding() async {
final db = ref.read(localDbProvider);
await db.write('onboarding_skipped', DateTime.now().toIso8601String());
state = state.copyWith(messages: state.messages.where((m) => m.type != MessageType.onboarding).toList());
}
void insertTaskCard() {
if (state.messages.any((m) => m.type == MessageType.taskCard)) return;
state = state.copyWith(messages: [ChatMessage(