fix: 任务卡片移入对话流 + 综合信息卡片分组修复
- 新增 taskCard 消息类型,作为对话第一条消息 - 今日任务卡片从独立区域移至聊天流内 - AgentWelcomeCard 从 metadata 读 agent 不再全局共享 - 切换胶囊不会影响已发过的卡片
This commit is contained in:
@@ -92,10 +92,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
// ── 顶部栏 ──
|
||||
_buildHeader(user),
|
||||
|
||||
// ── 今日任务(可折叠) ──
|
||||
_buildTaskCardsArea(),
|
||||
|
||||
// ── 聊天区域(弹性填充剩余空间) ──
|
||||
// ── 聊天区域(今日任务已移入对话流第一条消息) ──
|
||||
Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)),
|
||||
|
||||
// ── 底部合并区:智能体栏 + 操作面板 + 输入框(固定高度) ──
|
||||
|
||||
@@ -63,7 +63,10 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
|
||||
switch (msg.type) {
|
||||
case MessageType.agentWelcome:
|
||||
return _buildAgentWelcomeCard(context, ref, msg, chatState.activeAgent);
|
||||
final storedAgent = _parseAgentFromName(msg.metadata?['agent'] as String?);
|
||||
return _buildAgentWelcomeCard(context, ref, msg, storedAgent);
|
||||
case MessageType.taskCard:
|
||||
return _buildTaskCardInChat(context, ref);
|
||||
case MessageType.dataConfirm:
|
||||
return _buildDataConfirmCard(context, msg);
|
||||
case MessageType.medicationConfirm:
|
||||
@@ -1254,4 +1257,69 @@ class _ExpandableAdviceState extends State<_ExpandableAdvice> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ActiveAgent _parseAgentFromName(String? name) {
|
||||
switch (name) {
|
||||
case 'consultation': return ActiveAgent.consultation;
|
||||
case 'health': return ActiveAgent.health;
|
||||
case 'diet': return ActiveAgent.diet;
|
||||
case 'medication': return ActiveAgent.medication;
|
||||
case 'report': return ActiveAgent.report;
|
||||
case 'exercise': return ActiveAgent.exercise;
|
||||
default: return ActiveAgent.default_;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTaskCardInChat(BuildContext context, WidgetRef ref) {
|
||||
final health = ref.watch(latestHealthProvider);
|
||||
return health.when(
|
||||
data: (data) => _taskCardBubble(context, ref, data),
|
||||
loading: () => _taskCardBubble(context, ref, {}),
|
||||
error: (_, __) => _taskCardBubble(context, ref, {}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _taskCardBubble(BuildContext context, WidgetRef ref, Map<String, dynamic> data) {
|
||||
final bp = data['BloodPressure'];
|
||||
final bpText = bp is Map ? '${bp['systolic'] ?? '--'}/${bp['diastolic'] ?? '--'}' : '--';
|
||||
final hr = data['HeartRate'];
|
||||
final hrText = hr is Map && hr['value'] != null ? '${hr['value']}' : '--';
|
||||
final gl = data['Glucose'];
|
||||
final glText = gl is Map && gl['value'] != null ? '${gl['value']}' : '--';
|
||||
final sp = data['SpO2'];
|
||||
final spText = sp is Map && sp['value'] != null ? '${sp['value']}' : '--';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(children: [
|
||||
Icon(Icons.today, size: 18, color: const Color(0xFF635BFF)),
|
||||
const SizedBox(width: 8),
|
||||
const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
|
||||
]),
|
||||
const SizedBox(height: 10),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
_miniMetric('血压', bpText, Icons.favorite, ref),
|
||||
_miniMetric('心率', hrText, Icons.monitor_heart, ref),
|
||||
_miniMetric('血糖', glText, Icons.bloodtype, ref),
|
||||
_miniMetric('血氧', spText, Icons.air, ref),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _miniMetric(String label, String value, IconData icon, WidgetRef ref) {
|
||||
return Column(children: [
|
||||
Icon(icon, size: 20, color: const Color(0xFF635BFF)),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))),
|
||||
Text(label, style: const TextStyle(fontSize: 10, color: Color(0xFF999999))),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
enum MessageType { text, dataConfirm, medicationConfirm, dietAnalysis, reportAnalysis, quickOptions, agentWelcome, taskCard }
|
||||
|
||||
class ChatMessage {
|
||||
final String id;
|
||||
@@ -115,7 +115,22 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
StreamSubscription<Map<String, dynamic>>? _subscription;
|
||||
|
||||
@override
|
||||
ChatState build() => const ChatState();
|
||||
ChatState build() {
|
||||
// 首次加载时插入今日任务卡片作为第一条消息
|
||||
Future.microtask(() => insertTaskCard());
|
||||
return const ChatState();
|
||||
}
|
||||
|
||||
void insertTaskCard() {
|
||||
if (state.messages.any((m) => m.type == MessageType.taskCard)) return;
|
||||
state = state.copyWith(messages: [ChatMessage(
|
||||
id: 'task_card',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: DateTime.now(),
|
||||
type: MessageType.taskCard,
|
||||
), ...state.messages]);
|
||||
}
|
||||
|
||||
void setAgent(ActiveAgent a) {
|
||||
_subscription?.cancel();
|
||||
|
||||
Reference in New Issue
Block a user