Browse Source

feat(聊天记录): 添加分页加载历史消息功能

实现聊天记录的分页加载功能,当用户滚动到顶部时自动加载更多历史消息。新增加载状态指示器,包括加载中、加载失败和默认提示三种状态。优化消息列表性能,避免一次性加载所有历史记录。
weicu
lxm 1 year ago
parent
commit
fd4c43c510
  1. 189
      lib/modules/agent/controllers/agent_controller.dart
  2. 83
      lib/modules/agent/views/agent_view.dart

189
lib/modules/agent/controllers/agent_controller.dart

@ -120,6 +120,13 @@ class AgentController extends GetxController {
// 会话ID // 会话ID
final String sessionId = 'default_agent'; final String sessionId = 'default_agent';
// 分页加载相关状态
final isLoadingMore = false.obs; // 是否正在加载更多消息
final hasMoreMessages = true.obs; // 是否还有更多消息
final currentPage = 1.obs; // 当前页码
final pageSize = 20; // 每页消息数量
final isLoadingMoreError = false.obs; // 加载更多时是否出错
// 是否已加载聊天记录 // 是否已加载聊天记录
final isHistoryLoaded = false.obs; final isHistoryLoaded = false.obs;
@ -137,6 +144,7 @@ class AgentController extends GetxController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
_loadTtsPreference(); //加载TTS偏好设置 _loadTtsPreference(); //加载TTS偏好设置
_setupScrollListener(); //设置滚动监听器
_loadChatHistory(); //加载聊天历史记录 _loadChatHistory(); //加载聊天历史记录
_subscribeToEvents(); //订阅代理服务事件 _subscribeToEvents(); //订阅代理服务事件
_initMusicStatus(); //初始化音乐状态 _initMusicStatus(); //初始化音乐状态
@ -270,63 +278,33 @@ class AgentController extends GetxController {
final result = await ChatStorage.getMessages( final result = await ChatStorage.getMessages(
sessionId: sessionId, //会话ID sessionId: sessionId, //会话ID
page: 1, //从第一页开始 page: 1, //从第一页开始
pageSize: 20, //每页20条记录 pageSize: pageSize, //每页20条记录
); );
if (result.messages.isNotEmpty) { if (result.messages.isNotEmpty) {
// 转换并添加消息到列表 // 转换并添加消息到列表
final historyMessages = result.messages.map((msg) { final historyMessages = _convertChatMessagesToMessages(result.messages);
final meta = msg.metadata != null && msg.metadata != ""
? jsonDecode(msg.metadata!)
: null; // 如果为空,使用空对象
// 从元数据中提取图片信息
final hasImage = meta != null && meta.containsKey('hasImage')
? meta['hasImage'] as bool
: false;
final imagePath = meta != null && meta.containsKey('imagePath')
? meta['imagePath'] as String?
: null;
return Message(
isUser: msg.sender == 'user',
text: msg.message,
meta: meta,
timestamp: DateTime.parse(msg.timestamp),
hasImage: hasImage,
imagePath: imagePath,
);
}).toList();
// 消息按时间排序,最新的消息在底部 // 消息按时间排序,最新的消息在底部
historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
messages.assignAll(historyMessages); messages.assignAll(historyMessages);
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录'); // 更新分页状态
// 详细打印每条消息的内容和属性,便于调试 currentPage.value = 1;
Logger.d(TAG, '===== 历史消息详情开始 ====='); hasMoreMessages.value = result.totalPages > 1;
for (int i = 0; i < historyMessages.length; i++) {
final msg = historyMessages[i]; Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}');
Logger.d( _logMessageDetails(historyMessages);
TAG,
'消息[$i]: {'
'isUser: ${msg.isUser}, '
'text: "${msg.text.length > 50 ? msg.text.substring(0, 50) + "..." : msg.text}", '
'time: ${msg.timestamp}, '
'hasImage: ${msg.hasImage}, '
'imagePath: ${msg.imagePath ?? "无"}, '
'meta: ${msg.meta != null ? "有" : "无"}'
'}');
}
Logger.d(TAG, '===== 历史消息详情结束 =====');
} else { } else {
// 没有历史记录时添加欢迎消息 // 没有历史记录时添加欢迎消息
_addWelcomeMessage(); _addWelcomeMessage();
hasMoreMessages.value = false;
Logger.i(TAG, '没有历史记录,添加欢迎消息'); Logger.i(TAG, '没有历史记录,添加欢迎消息');
} }
} catch (e) { } catch (e) {
Logger.e(TAG, '加载聊天历史失败: $e'); Logger.e(TAG, '加载聊天历史失败: $e');
_addWelcomeMessage(); _addWelcomeMessage();
hasMoreMessages.value = false;
} finally { } finally {
isHistoryLoaded.value = true; isHistoryLoaded.value = true;
messages.refresh(); messages.refresh();
@ -334,6 +312,137 @@ class AgentController extends GetxController {
} }
} }
// 加载更多历史消息(分页加载)
Future<void> loadMoreMessages() async {
// 防止重复加载
if (isLoadingMore.value || !hasMoreMessages.value) {
Logger.d(TAG, '跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreMessages=${hasMoreMessages.value}');
return;
}
try {
isLoadingMore.value = true;
isLoadingMoreError.value = false;
final nextPage = currentPage.value + 1;
Logger.i(TAG, '正在加载第 $nextPage 页历史消息...');
final result = await ChatStorage.getMessages(
sessionId: sessionId,
page: nextPage,
pageSize: pageSize,
);
if (result.messages.isNotEmpty) {
// 转换新消息
final newMessages = _convertChatMessagesToMessages(result.messages);
// 按时间排序新消息
newMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
// 添加短暂延迟,让用户感知到加载过程
await Future.delayed(const Duration(milliseconds: 300));
// 将新消息插入到现有消息列表的开头(因为是历史消息)
final updatedMessages = [...newMessages, ...messages];
messages.assignAll(updatedMessages);
// 更新分页状态
currentPage.value = nextPage;
hasMoreMessages.value = nextPage < result.totalPages;
Logger.i(TAG, '成功加载 ${newMessages.length} 条历史消息,当前页: $nextPage/${result.totalPages}');
} else {
// 没有更多消息
hasMoreMessages.value = false;
Logger.i(TAG, '没有更多历史消息');
}
} catch (e) {
Logger.e(TAG, '加载更多消息失败: $e');
isLoadingMoreError.value = true;
} finally {
isLoadingMore.value = false;
}
}
// 转换ChatMessage到Message的通用方法
List<Message> _convertChatMessagesToMessages(List<ChatMessage> chatMessages) {
return chatMessages.map((msg) {
final meta = msg.metadata != null && msg.metadata != ""
? jsonDecode(msg.metadata!)
: null; // 如果为空,使用空对象
// 从元数据中提取图片信息
final hasImage = meta != null && meta.containsKey('hasImage')
? meta['hasImage'] as bool
: false;
final imagePath = meta != null && meta.containsKey('imagePath')
? meta['imagePath'] as String?
: null;
return Message(
isUser: msg.sender == 'user',
text: msg.message,
meta: meta,
timestamp: DateTime.parse(msg.timestamp),
hasImage: hasImage,
imagePath: imagePath,
);
}).toList();
}
// 打印消息详情的通用方法
void _logMessageDetails(List<Message> messageList) {
Logger.d(TAG, '===== 历史消息详情开始 =====');
for (int i = 0; i < messageList.length; i++) {
final msg = messageList[i];
Logger.d(
TAG,
'消息[$i]: {'
'isUser: ${msg.isUser}, '
'text: "${msg.text.length > 50 ? msg.text.substring(0, 50) + "..." : msg.text}", '
'time: ${msg.timestamp}, '
'hasImage: ${msg.hasImage}, '
'imagePath: ${msg.imagePath ?? "无"}, '
'meta: ${msg.meta != null ? "有" : "无"}'
'}');
}
Logger.d(TAG, '===== 历史消息详情结束 =====');
}
// 设置滚动监听器
void _setupScrollListener() {
scrollController.addListener(() {
// 检查是否滚动到顶部(因为使用了reverse: true,顶部实际是最大滚动位置)
if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) {
// 当距离顶部100像素时触发加载更多
if (hasMoreMessages.value && !isLoadingMore.value) {
Logger.d(TAG, '触发加载更多历史消息');
loadMoreMessages();
}
}
});
}
// 手动触发加载更多(用于测试或手动调用)
void triggerLoadMore() {
if (hasMoreMessages.value && !isLoadingMore.value) {
Logger.i(TAG, '手动触发加载更多历史消息');
loadMoreMessages();
} else {
Logger.w(TAG, '无法加载更多:hasMoreMessages=${hasMoreMessages.value}, isLoadingMore=${isLoadingMore.value}');
}
}
// 重置分页状态(用于刷新聊天记录)
void resetPagination() {
currentPage.value = 1;
hasMoreMessages.value = true;
isLoadingMore.value = false;
isLoadingMoreError.value = false;
Logger.i(TAG, '分页状态已重置');
}
// 添加初始欢迎消息 // 添加初始欢迎消息
void _addWelcomeMessage() { void _addWelcomeMessage() {
messages.add(Message( messages.add(Message(

83
lib/modules/agent/views/agent_view.dart

@ -200,8 +200,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
reverse: true, reverse: true,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 16, horizontal: 16), vertical: 16, horizontal: 16),
itemCount: controller.messages.length, itemCount: controller.messages.length + (controller.hasMoreMessages.value ? 1 : 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
// 如果是最后一项且有更多消息,显示加载指示器
if (index == controller.messages.length && controller.hasMoreMessages.value) {
return _buildLoadMoreIndicator();
}
final message = controller final message = controller
.messages[controller.messages.length - 1 - index]; .messages[controller.messages.length - 1 - index];
return Padding( return Padding(
@ -1122,4 +1127,80 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
); );
} }
// 构建加载更多指示器
Widget _buildLoadMoreIndicator() {
return Obx(() {
if (controller.isLoadingMore.value) {
// 正在加载状态
return Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: const Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
SizedBox(width: 8),
Text(
'加载更多消息...',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
);
} else if (controller.isLoadingMoreError.value) {
// 加载失败状态
return Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: GestureDetector(
onTap: () => controller.loadMoreMessages(),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh,
size: 16,
color: Colors.grey,
),
SizedBox(width: 8),
Text(
'加载失败,点击重试',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
),
);
} else {
// 默认状态(有更多消息但未加载)
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: const Center(
child: Text(
'向上滑动查看更多',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
),
);
}
});
}
} }

Loading…
Cancel
Save