fix: 数据确认面板修复 - 显示实际数值+确认关闭+侧边栏刷新+清理旧数据
- Handler返回value/unit/isAbnormal到metadata - DataConfirmCard显示实际数值和单位 - 确认按钮关闭卡片+刷新健康概览 - 健康Agent欢迎卡简化为'查看趋势' - 清空87条旧健康测试数据
This commit is contained in:
@@ -74,6 +74,11 @@ public static class HealthDataAgentHandler
|
|||||||
|
|
||||||
db.HealthRecords.Add(record);
|
db.HealthRecords.Add(record);
|
||||||
await db.SaveChangesAsync();
|
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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,8 +408,10 @@ public static class AiChatEndpoints
|
|||||||
messageType = "data_confirm";
|
messageType = "data_confirm";
|
||||||
if (toolResult is IDictionary<string, object> resultDict)
|
if (toolResult is IDictionary<string, object> resultDict)
|
||||||
{
|
{
|
||||||
if (resultDict.TryGetValue("type", out var type))
|
if (resultDict.TryGetValue("type", out var type)) metadata["type"] = type.ToString();
|
||||||
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)
|
if (resultDict.TryGetValue("success", out var success) && success is bool b && b)
|
||||||
metadata["success"] = true;
|
metadata["success"] = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ChatMessagesView extends ConsumerWidget {
|
|||||||
case MessageType.taskCard:
|
case MessageType.taskCard:
|
||||||
return _buildTaskCardInChat(context, ref);
|
return _buildTaskCardInChat(context, ref);
|
||||||
case MessageType.dataConfirm:
|
case MessageType.dataConfirm:
|
||||||
return _buildDataConfirmCard(context, msg);
|
return _buildDataConfirmCard(context, ref, msg);
|
||||||
case MessageType.medicationConfirm:
|
case MessageType.medicationConfirm:
|
||||||
return _buildMedicationConfirmCard(context, msg);
|
return _buildMedicationConfirmCard(context, msg);
|
||||||
case MessageType.dietAnalysis:
|
case MessageType.dietAnalysis:
|
||||||
@@ -269,14 +269,14 @@ class ChatMessagesView extends ConsumerWidget {
|
|||||||
// 2. DataConfirmCard — 增强版数据确认卡片
|
// 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 meta = msg.metadata;
|
||||||
final metricType = meta?['type'] as String? ?? '';
|
final metricType = meta?['type'] as String? ?? '';
|
||||||
final value = meta?['value'] as String? ?? '';
|
final value = meta?['value'] as String? ?? '';
|
||||||
final abnormal = meta?['abnormal'] as bool? ?? false;
|
final abnormal = meta?['abnormal'] as bool? ?? false;
|
||||||
final recordTime = meta?['recordTime'] as String? ?? '';
|
final recordTime = meta?['recordTime'] as String? ?? '';
|
||||||
final unit = meta?['unit'] as String? ?? _getMetricUnit(metricType);
|
final unit = meta?['unit'] as String? ?? _getMetricUnit(metricType);
|
||||||
final trend = meta?['trend'] as List? ?? [0.6, 0.8, 0.5];
|
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
@@ -286,154 +286,55 @@ class ChatMessagesView extends ConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
borderRadius: BorderRadius.circular(20),
|
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,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Column(
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Container(
|
||||||
children: [
|
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(
|
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),
|
padding: const EdgeInsets.all(18),
|
||||||
child: Column(
|
decoration: BoxDecoration(color: const Color(0xFFF9F8FF), borderRadius: BorderRadius.circular(16)),
|
||||||
children: [
|
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)))),
|
||||||
Align(
|
const SizedBox(width: 14),
|
||||||
alignment: Alignment.centerLeft,
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
child: Text(recordTime.isNotEmpty ? recordTime : _formatTime(msg.createdAt), style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E))),
|
Text(_getMetricName(metricType), style: const TextStyle(fontSize: 13, color: Color(0xFF888888))),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(height: 14),
|
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))),
|
||||||
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)),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
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(
|
return ElevatedButton(
|
||||||
onPressed: () {},
|
onPressed: onTap ?? () {},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF8B9CF7),
|
backgroundColor: const Color(0xFF6C5CE7),
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
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(
|
return OutlinedButton(
|
||||||
onPressed: () {},
|
onPressed: onTap ?? () {},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFF8B9CF7),
|
foregroundColor: const Color(0xFF6C5CE7),
|
||||||
side: const BorderSide(color: Color(0xFF8B9CF7), width: 1.2),
|
side: const BorderSide(color: Color(0xFF6C5CE7), width: 1.2),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 11),
|
padding: const EdgeInsets.symmetric(vertical: 11),
|
||||||
),
|
),
|
||||||
@@ -1451,11 +1352,7 @@ class _AgentAction {
|
|||||||
|
|
||||||
final _agentActions = <ActiveAgent, List<_AgentAction>>{
|
final _agentActions = <ActiveAgent, List<_AgentAction>>{
|
||||||
ActiveAgent.health: [
|
ActiveAgent.health: [
|
||||||
_AgentAction(label: '录入血压', icon: Icons.monitor_heart_outlined, route: 'trend'),
|
_AgentAction(label: '查看趋势', icon: Icons.trending_up, isWide: true, 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'),
|
|
||||||
],
|
],
|
||||||
ActiveAgent.diet: [
|
ActiveAgent.diet: [
|
||||||
_AgentAction(label: '拍照识别', icon: Icons.camera_alt_outlined, isWide: true, route: 'dietCapture'),
|
_AgentAction(label: '拍照识别', icon: Icons.camera_alt_outlined, isWide: true, route: 'dietCapture'),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class ChatMessage {
|
|||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
MessageType type;
|
MessageType type;
|
||||||
final Map<String, dynamic>? metadata;
|
final Map<String, dynamic>? metadata;
|
||||||
|
bool confirmed;
|
||||||
ChatMessage({
|
ChatMessage({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.role,
|
required this.role,
|
||||||
@@ -21,6 +22,7 @@ class ChatMessage {
|
|||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
this.type = MessageType.text,
|
this.type = MessageType.text,
|
||||||
this.metadata,
|
this.metadata,
|
||||||
|
this.confirmed = false,
|
||||||
});
|
});
|
||||||
bool get isUser => role == 'user';
|
bool get isUser => role == 'user';
|
||||||
}
|
}
|
||||||
@@ -114,6 +116,8 @@ ActiveAgent _parseAgent(String? type) {
|
|||||||
class ChatNotifier extends Notifier<ChatState> {
|
class ChatNotifier extends Notifier<ChatState> {
|
||||||
StreamSubscription<Map<String, dynamic>>? _subscription;
|
StreamSubscription<Map<String, dynamic>>? _subscription;
|
||||||
|
|
||||||
|
void markNeedsRebuild() => state = state.copyWith();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ChatState build() {
|
ChatState build() {
|
||||||
Future.microtask(() {
|
Future.microtask(() {
|
||||||
|
|||||||
Reference in New Issue
Block a user