Files
AI-Health/health_app/lib/pages/home/widgets/chat_messages_view.dart
MingNian 498708e568 fix: 修复 Flutter 前端多项功能 + 后端运动计划 API
- Android 添加相机/存储权限,拍照和相册功能可用
- AI 回复支持 Markdown 渲染(加粗/表格不再显示**乱码)
- 附件按钮接线,支持拍照/相册/文件选择
- 智能体面板按钮全部接线(拍照/上传/手动录入/导航)
- 侧边栏 AI 录入后自动刷新健康数据
- 运动计划页增加创建按钮 + 打卡功能
- 后端运动计划支持 AI 创建和打卡(Tool Calling)
- 修复 CreateExercisePlanRequest JSON 反序列化
2026-06-02 16:34:36 +08:00

97 lines
3.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../providers/chat_provider.dart';
/// 对话消息列表
class ChatMessagesView extends ConsumerWidget {
final ScrollController scrollCtrl;
final List<ChatMessage> messages;
const ChatMessagesView({super.key, required this.scrollCtrl, required this.messages});
@override
Widget build(BuildContext context, WidgetRef ref) {
final chatState = ref.watch(chatProvider);
if (messages.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.chat_bubble_outline, size: 48, color: Colors.grey[300]),
const SizedBox(height: 12),
Text('开始和 AI 健康管家对话吧', style: Theme.of(context).textTheme.bodyMedium),
],
),
);
}
return ListView.builder(
controller: scrollCtrl,
reverse: true,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: messages.length,
itemBuilder: (context, index) {
final msg = messages[messages.length - 1 - index];
return _buildMessageBubble(context, msg, chatState);
},
);
}
Widget _buildMessageBubble(BuildContext context, ChatMessage msg, ChatState chatState) {
final isUser = msg.isUser;
return Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.only(bottom: 12),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.78),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isUser ? const Color(0xFF635BFF) : Colors.white,
borderRadius: BorderRadius.circular(16),
border: isUser ? null : const Border(left: BorderSide(color: Color(0xFF635BFF), width: 3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isUser && chatState.isStreaming && msg.content.isEmpty)
_buildThinkingIndicator()
else if (isUser)
Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white))
else
MarkdownBody(
data: msg.content.isEmpty ? '...' : msg.content,
selectable: true,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A)),
h1: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
code: TextStyle(fontSize: 14, backgroundColor: Colors.grey[200]),
),
),
if (!isUser && msg.content.isNotEmpty && !chatState.isStreaming)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'AI 健康管家 · 仅供参考',
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
),
),
],
),
),
);
}
Widget _buildThinkingIndicator() {
return const Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2)),
SizedBox(width: 8),
Text('思考中...', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
],
);
}
}