diff --git a/backend/src/Health.Infrastructure/AI/AgentHandlers/health_data_agent_handler.cs b/backend/src/Health.Infrastructure/AI/AgentHandlers/health_data_agent_handler.cs index 046c045..0be8a77 100644 --- a/backend/src/Health.Infrastructure/AI/AgentHandlers/health_data_agent_handler.cs +++ b/backend/src/Health.Infrastructure/AI/AgentHandlers/health_data_agent_handler.cs @@ -74,6 +74,11 @@ public static class HealthDataAgentHandler db.HealthRecords.Add(record); await db.SaveChangesAsync(); - return new { success = true, record_id = record.Id, type = record.MetricType.ToString() }; + var valStr = record.MetricType switch + { + HealthMetricType.BloodPressure => $"{record.Systolic}/{record.Diastolic}", + _ => record.Value?.ToString() ?? "" + }; + return new { success = true, record_id = record.Id, type = record.MetricType.ToString(), value = valStr, unit = record.Unit, isAbnormal = record.IsAbnormal }; } } diff --git a/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs b/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs index cbb2958..b5cdfb3 100644 --- a/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs +++ b/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs @@ -408,8 +408,10 @@ public static class AiChatEndpoints messageType = "data_confirm"; if (toolResult is IDictionary resultDict) { - if (resultDict.TryGetValue("type", out var type)) - metadata["type"] = type.ToString(); + if (resultDict.TryGetValue("type", out var type)) metadata["type"] = type.ToString(); + if (resultDict.TryGetValue("value", out var val)) metadata["value"] = val.ToString(); + if (resultDict.TryGetValue("unit", out var unit)) metadata["unit"] = unit.ToString(); + if (resultDict.TryGetValue("isAbnormal", out var abn) && abn is bool b2) metadata["abnormal"] = b2; if (resultDict.TryGetValue("success", out var success) && success is bool b && b) metadata["success"] = true; } diff --git a/health_app/lib/pages/home/widgets/chat_messages_view.dart b/health_app/lib/pages/home/widgets/chat_messages_view.dart index f14ffb7..309642c 100644 --- a/health_app/lib/pages/home/widgets/chat_messages_view.dart +++ b/health_app/lib/pages/home/widgets/chat_messages_view.dart @@ -62,7 +62,7 @@ class ChatMessagesView extends ConsumerWidget { case MessageType.taskCard: return _buildTaskCardInChat(context, ref); case MessageType.dataConfirm: - return _buildDataConfirmCard(context, msg); + return _buildDataConfirmCard(context, ref, msg); case MessageType.medicationConfirm: return _buildMedicationConfirmCard(context, msg); case MessageType.dietAnalysis: @@ -269,14 +269,14 @@ class ChatMessagesView extends ConsumerWidget { // 2. DataConfirmCard — 增强版数据确认卡片 // ═══════════════════════════════════════════════════════════ - Widget _buildDataConfirmCard(BuildContext context, ChatMessage msg) { + Widget _buildDataConfirmCard(BuildContext context, WidgetRef ref, ChatMessage msg) { + if (msg.confirmed) return const SizedBox.shrink(); final meta = msg.metadata; final metricType = meta?['type'] as String? ?? ''; final value = meta?['value'] as String? ?? ''; final abnormal = meta?['abnormal'] as bool? ?? false; final recordTime = meta?['recordTime'] as String? ?? ''; final unit = meta?['unit'] as String? ?? _getMetricUnit(metricType); - final trend = meta?['trend'] as List? ?? [0.6, 0.8, 0.5]; return Align( alignment: Alignment.centerLeft, @@ -286,154 +286,55 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: const Color(0xFF6C5CE7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), clipBehavior: Clip.antiAlias, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // ── 绿色勾选条 ── + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Container( + width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10), + decoration: const BoxDecoration(gradient: LinearGradient(colors: [Color(0xFF4CAF50), Color(0xFF43A047)])), + child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.check_circle, size: 18, color: Colors.white), SizedBox(width: 6), + Text('数据已记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), + ]), + ), + Padding(padding: const EdgeInsets.all(18), child: Column(children: [ + Align(alignment: Alignment.centerLeft, child: Text(recordTime.isNotEmpty ? recordTime : _formatTime(msg.createdAt), style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)))), + const SizedBox(height: 14), Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: const BoxDecoration( - gradient: LinearGradient(colors: [Color(0xFF4CAF50), Color(0xFF43A047)]), - ), - child: const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.check_circle, size: 18, color: Colors.white), - SizedBox(width: 6), - Text('✓ 数据已记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), - ], - ), - ), - - Padding( padding: const EdgeInsets.all(18), - child: Column( - children: [ - // 记录时间 - Align( - alignment: Alignment.centerLeft, - child: Text(recordTime.isNotEmpty ? recordTime : _formatTime(msg.createdAt), style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E))), - ), - const SizedBox(height: 14), - - // 主要指标区域 - Container( - padding: const EdgeInsets.all(18), - decoration: BoxDecoration( - color: const Color(0xFFF9F8FF), - borderRadius: BorderRadius.circular(16), - ), - child: Row( - children: [ - Container( - width: 52, - height: 52, - decoration: BoxDecoration( - color: const Color(0xFFEDEAFF), - borderRadius: BorderRadius.circular(14), - ), - child: Center(child: Text(_getMetricIcon(metricType), style: const TextStyle(fontSize: 26))), - ), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(_getMetricName(metricType), style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), - const SizedBox(height: 4), - RichText( - text: TextSpan( - children: [ - TextSpan(text: value, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, color: abnormal ? const Color(0xFFE53935) : const Color(0xFF1A1A2E))), - TextSpan(text: ' $unit', style: TextStyle(fontSize: 14, color: abnormal ? const Color(0xFFE53970) : const Color(0xFF999999))), - ], - ), - ), - ], - ), - ), - ], - ), - ), - - // 异常警告条 - if (abnormal) ...[ - const SizedBox(height: 12), - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: const Color(0xFFFFF3F0), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFFFFDAD4), width: 1), - ), - child: const Row( - children: [ - Icon(Icons.warning_amber_rounded, size: 18, color: Color(0xFFE53935)), - SizedBox(width: 8), - Expanded(child: Text('⚠️ 数值偏高,建议关注', style: TextStyle(fontSize: 13, color: Color(0xFFE53935), fontWeight: FontWeight.w500))), - ], - ), - ), - ], - - // 迷你趋势图(最近3次) - const SizedBox(height: 16), - Row( - children: [ - const Text('近期趋势', style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), - const Spacer(), - const Text('最近3次', style: TextStyle(fontSize: 11, color: Color(0xFFCCCCCC))), - ], - ), - const SizedBox(height: 8), - SizedBox( - height: 36, - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: trend.asMap().entries.map((e) { - final h = (e.value * 32).clamp(6.0, 32.0); - return Padding( - padding: EdgeInsets.only(right: e.key < trend.length - 1 ? 10 : 0), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 22, - height: h, - decoration: BoxDecoration( - color: e.key == trend.length - 1 ? const Color(0xFF8B9CF7) : const Color(0xFFD5D0FF), - borderRadius: BorderRadius.circular(5), - ), - ), - const SizedBox(height: 4), - Text('${e.key + 1}', style: const TextStyle(fontSize: 9, color: Color(0xFFBBBBBB))), - ], - ), - ); - }).toList(), - ), - ), - - // 底部操作按钮 - const SizedBox(height: 18), - Row(children: [ - Expanded(child: _cardOutlineBtn('编辑', Icons.edit_outlined)), - const SizedBox(width: 8), - Expanded(child: _cardFilledBtn('确认', Icons.check)), - const SizedBox(width: 8), - Expanded(child: _cardOutlineBtn('查看详情', Icons.trending_up_outlined)), - ]), - ], - ), + decoration: BoxDecoration(color: const Color(0xFFF9F8FF), borderRadius: BorderRadius.circular(16)), + child: Row(children: [ + Container(width: 52, height: 52, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(14)), child: Center(child: Text(_getMetricIcon(metricType), style: const TextStyle(fontSize: 26)))), + const SizedBox(width: 14), + Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(_getMetricName(metricType), style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + const SizedBox(height: 4), + RichText(text: TextSpan(children: [ + TextSpan(text: value, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, color: abnormal ? const Color(0xFFE53935) : const Color(0xFF1A1A2E))), + TextSpan(text: ' $unit', style: TextStyle(fontSize: 14, color: abnormal ? const Color(0xFFE53970) : const Color(0xFF999999))), + ])), + ])), + ]), ), - ], - ), + if (abnormal) ...[ + const SizedBox(height: 12), + Container(width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration(color: const Color(0xFFFFF3F0), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFFFDAD4))), + child: const Row(children: [Icon(Icons.warning_amber_rounded, size: 18, color: Color(0xFFE53935)), SizedBox(width: 8), Expanded(child: Text('数值偏高,建议关注', style: TextStyle(fontSize: 13, color: Color(0xFFE53935), fontWeight: FontWeight.w500)))])), + ], + const SizedBox(height: 18), + Row(children: [ + Expanded(child: _cardFilledBtn('确认', Icons.check, onTap: () { + msg.confirmed = true; + ref.invalidate(latestHealthProvider); + final notifier = ref.read(chatProvider.notifier); + notifier.markNeedsRebuild(); + })), + const SizedBox(width: 8), + Expanded(child: _cardOutlineBtn('查看详情', Icons.trending_up_outlined, onTap: () => pushRoute(ref, 'trend', params: {'type': metricType}))), + ]), + ])), + ]), ), ); } @@ -1061,11 +962,11 @@ class ChatMessagesView extends ConsumerWidget { // 公共组件:通用按钮 // ═══════════════════════════════════════════════════════════ - Widget _cardFilledBtn(String label, IconData icon) { + Widget _cardFilledBtn(String label, IconData icon, {VoidCallback? onTap}) { return ElevatedButton( - onPressed: () {}, + onPressed: onTap ?? () {}, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8B9CF7), + backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), @@ -1079,12 +980,12 @@ class ChatMessagesView extends ConsumerWidget { ); } - Widget _cardOutlineBtn(String label, IconData icon) { + Widget _cardOutlineBtn(String label, IconData icon, {VoidCallback? onTap}) { return OutlinedButton( - onPressed: () {}, + onPressed: onTap ?? () {}, style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFF8B9CF7), - side: const BorderSide(color: Color(0xFF8B9CF7), width: 1.2), + foregroundColor: const Color(0xFF6C5CE7), + side: const BorderSide(color: Color(0xFF6C5CE7), width: 1.2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(vertical: 11), ), @@ -1451,11 +1352,7 @@ class _AgentAction { final _agentActions = >{ ActiveAgent.health: [ - _AgentAction(label: '录入血压', icon: Icons.monitor_heart_outlined, route: 'trend'), - _AgentAction(label: '录入血糖', icon: Icons.bloodtype_outlined, route: 'trend'), - _AgentAction(label: '录入心率', icon: Icons.favorite_border, route: 'trend'), - _AgentAction(label: '录入血氧', icon: Icons.air_outlined, route: 'trend'), - _AgentAction(label: '录入体重', icon: Icons.monitor_weight_outlined, route: 'trend'), + _AgentAction(label: '查看趋势', icon: Icons.trending_up, isWide: true, route: 'trend'), ], ActiveAgent.diet: [ _AgentAction(label: '拍照识别', icon: Icons.camera_alt_outlined, isWide: true, route: 'dietCapture'), diff --git a/health_app/lib/providers/chat_provider.dart b/health_app/lib/providers/chat_provider.dart index 3399c9c..75a3454 100644 --- a/health_app/lib/providers/chat_provider.dart +++ b/health_app/lib/providers/chat_provider.dart @@ -14,6 +14,7 @@ class ChatMessage { final DateTime createdAt; MessageType type; final Map? metadata; + bool confirmed; ChatMessage({ required this.id, required this.role, @@ -21,6 +22,7 @@ class ChatMessage { required this.createdAt, this.type = MessageType.text, this.metadata, + this.confirmed = false, }); bool get isUser => role == 'user'; } @@ -114,6 +116,8 @@ ActiveAgent _parseAgent(String? type) { class ChatNotifier extends Notifier { StreamSubscription>? _subscription; + void markNeedsRebuild() => state = state.copyWith(); + @override ChatState build() { Future.microtask(() {