|
|
@ -2,18 +2,17 @@ import 'dart:async'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:flutter/widgets.dart'; |
|
|
import 'package:flutter/widgets.dart'; |
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
|
|
|
import 'package:chat_storage/chat_storage.dart'; |
|
|
import '../../../data/services/ai_service.dart'; |
|
|
import '../../../data/services/ai_service.dart'; |
|
|
import '../../../data/services/deapsound_ai_service.dart'; |
|
|
import '../../../data/services/deapsound_ai_service.dart'; |
|
|
import '../../../data/services/kouzi_ai_service.dart'; |
|
|
import '../../../data/services/kouzi_ai_service.dart'; |
|
|
import '../../../data/services/speech_impl/voice_clone_tts_service.dart'; |
|
|
import '../../../data/services/speech_impl/voice_clone_tts_service.dart'; |
|
|
import '../models/message_model.dart'; |
|
|
import '../models/message_model.dart'; |
|
|
import '../../../core/utils/logger.dart'; |
|
|
import '../../../core/utils/logger.dart'; |
|
|
import '../../../data/services/volcano_ai_service.dart'; |
|
|
|
|
|
import '../../../data/services/tts_service.dart'; |
|
|
import '../../../data/services/tts_service.dart'; |
|
|
import '../../../data/providers/agent_provider.dart'; |
|
|
import '../../../data/providers/agent_provider.dart'; |
|
|
import '../../../data/models/agent.dart'; |
|
|
import '../../../data/models/agent.dart'; |
|
|
import '../../../data/services/asr_service.dart'; |
|
|
import '../../../data/services/asr_service.dart'; |
|
|
import '../../../data/services/chat_history_service.dart'; |
|
|
|
|
|
import '../../../data/models/events/voice_interaction_event.dart'; |
|
|
import '../../../data/models/events/voice_interaction_event.dart'; |
|
|
import '../../../data/services/voice_interaction_service.dart'; |
|
|
import '../../../data/services/voice_interaction_service.dart'; |
|
|
import '../../../data/services/open_ai_service_adapter.dart'; |
|
|
import '../../../data/services/open_ai_service_adapter.dart'; |
|
|
@ -23,7 +22,6 @@ class ChatController extends GetxController { |
|
|
late AiService _aiService; |
|
|
late AiService _aiService; |
|
|
late TtsService _ttsService; |
|
|
late TtsService _ttsService; |
|
|
final AsrService _asrService = Get.find<AsrService>(); |
|
|
final AsrService _asrService = Get.find<AsrService>(); |
|
|
final ChatHistoryService _historyService = Get.find<ChatHistoryService>(); |
|
|
|
|
|
final VoiceInteractionService _voiceInteractionService = |
|
|
final VoiceInteractionService _voiceInteractionService = |
|
|
Get.find<VoiceInteractionService>(); |
|
|
Get.find<VoiceInteractionService>(); |
|
|
|
|
|
|
|
|
@ -45,6 +43,13 @@ class ChatController extends GetxController { |
|
|
final RxBool isTtsEnabled = true.obs; |
|
|
final RxBool isTtsEnabled = true.obs; |
|
|
final RxBool isVoiceRecognitionActive = false.obs; |
|
|
final RxBool isVoiceRecognitionActive = false.obs; |
|
|
|
|
|
|
|
|
|
|
|
// 分页加载状态 |
|
|
|
|
|
final RxInt _currentPage = 1.obs; |
|
|
|
|
|
final RxInt _totalPages = 1.obs; |
|
|
|
|
|
final RxBool _isLoadingMore = false.obs; |
|
|
|
|
|
final RxBool _hasMoreHistory = true.obs; |
|
|
|
|
|
static const int _pageSize = 20; // 每页加载的消息数量 |
|
|
|
|
|
|
|
|
// 语音交互模式 |
|
|
// 语音交互模式 |
|
|
bool _isVoiceInteractionMode = false; |
|
|
bool _isVoiceInteractionMode = false; |
|
|
|
|
|
|
|
|
@ -53,9 +58,6 @@ class ChatController extends GetxController { |
|
|
final RxBool isListening = false.obs; |
|
|
final RxBool isListening = false.obs; |
|
|
final RxBool isSpeechDetected = false.obs; |
|
|
final RxBool isSpeechDetected = false.obs; |
|
|
|
|
|
|
|
|
// 临时消息索引,用于更新临时消息 |
|
|
|
|
|
int _tempMessageIndex = -1; |
|
|
|
|
|
|
|
|
|
|
|
// 防抖计时器 |
|
|
// 防抖计时器 |
|
|
Timer? _scrollDebounceTimer; |
|
|
Timer? _scrollDebounceTimer; |
|
|
|
|
|
|
|
|
@ -107,14 +109,9 @@ class ChatController extends GetxController { |
|
|
break; |
|
|
break; |
|
|
default: |
|
|
default: |
|
|
// 默认使用OpenAIServiceAdapter |
|
|
// 默认使用OpenAIServiceAdapter |
|
|
try { |
|
|
|
|
|
_aiService = Get.find<OpenAIServiceAdapter>(); |
|
|
_aiService = Get.find<OpenAIServiceAdapter>(); |
|
|
Logger.info('使用OpenAIServiceAdapter'); |
|
|
Logger.info('使用OpenAIServiceAdapter'); |
|
|
} catch (e) { |
|
|
|
|
|
// 如果找不到OpenAIServiceAdapter,则回退到VolcanoAIService |
|
|
|
|
|
Logger.info('未找到OpenAIServiceAdapter,回退使用VolcanoAIService: $e'); |
|
|
|
|
|
_aiService = Get.find<VolcanoAIService>(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 使用克隆音色语音合成 |
|
|
// 使用克隆音色语音合成 |
|
|
@ -136,8 +133,11 @@ class ChatController extends GetxController { |
|
|
// 设置TTS服务 |
|
|
// 设置TTS服务 |
|
|
await _ttsService.setVoice(agent.voice); |
|
|
await _ttsService.setVoice(agent.voice); |
|
|
|
|
|
|
|
|
// 加载聊天历史 |
|
|
// 添加滚动监听器,用于触发加载更多 |
|
|
_loadChatHistory(); |
|
|
scrollController.addListener(_scrollListener); |
|
|
|
|
|
|
|
|
|
|
|
// 加载聊天历史 (加载第一页) |
|
|
|
|
|
await _loadChatHistory(); |
|
|
|
|
|
|
|
|
// 如果没有历史记录,添加欢迎消息 |
|
|
// 如果没有历史记录,添加欢迎消息 |
|
|
if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) { |
|
|
if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) { |
|
|
@ -213,7 +213,9 @@ class ChatController extends GetxController { |
|
|
messages.add(userMsg); |
|
|
messages.add(userMsg); |
|
|
messages.add(assistantMsg); |
|
|
messages.add(assistantMsg); |
|
|
} |
|
|
} |
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
|
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 停止当前交互 |
|
|
/// 停止当前交互 |
|
|
@ -239,6 +241,7 @@ class ChatController extends GetxController { |
|
|
Logger.info('ChatController onClose'); |
|
|
Logger.info('ChatController onClose'); |
|
|
|
|
|
|
|
|
// 清理资源 |
|
|
// 清理资源 |
|
|
|
|
|
scrollController.removeListener(_scrollListener); // 移除监听器 |
|
|
scrollController.dispose(); |
|
|
scrollController.dispose(); |
|
|
_aiResponseSubscription?.cancel(); |
|
|
_aiResponseSubscription?.cancel(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
@ -255,7 +258,7 @@ class ChatController extends GetxController { |
|
|
|
|
|
|
|
|
// 保存当前聊天记录 |
|
|
// 保存当前聊天记录 |
|
|
if (agent.id != null) { |
|
|
if (agent.id != null) { |
|
|
_historyService.saveHistory(agent.id!, messages.toList()); |
|
|
// _historyService.saveHistory(agent.id!, messages.toList()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
super.onClose(); |
|
|
super.onClose(); |
|
|
@ -289,11 +292,11 @@ class ChatController extends GetxController { |
|
|
_aiResponseSubscription!.cancel(); |
|
|
_aiResponseSubscription!.cancel(); |
|
|
_aiResponseSubscription = null; |
|
|
_aiResponseSubscription = null; |
|
|
|
|
|
|
|
|
// 将加载中的消息转换为正常消息,保留已输出的内容 |
|
|
// 将AI中间消息转换为正常消息,保留已输出的内容 |
|
|
final index = messages.indexWhere((m) => m.isLoading); |
|
|
final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); |
|
|
if (index != -1) { |
|
|
if (index != -1) { |
|
|
messages[index] = messages[index].copyWith( |
|
|
messages[index] = messages[index].copyWith( |
|
|
isLoading: false, |
|
|
isIntermediate: false, |
|
|
); |
|
|
); |
|
|
messages.refresh(); |
|
|
messages.refresh(); |
|
|
} |
|
|
} |
|
|
@ -323,10 +326,10 @@ class ChatController extends GetxController { |
|
|
// 清空输入框 |
|
|
// 清空输入框 |
|
|
inputText.value = ''; |
|
|
inputText.value = ''; |
|
|
|
|
|
|
|
|
// 添加加载中的消息 |
|
|
// 添加中间状态的AI消息 |
|
|
final loadingMessage = Message.loading(); |
|
|
final intermediateMessage = Message.intermediate('assistant'); |
|
|
messages.add(loadingMessage); |
|
|
messages.add(intermediateMessage); |
|
|
|
|
|
messages.refresh(); |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
}); |
|
|
}); |
|
|
@ -355,8 +358,8 @@ class ChatController extends GetxController { |
|
|
|
|
|
|
|
|
fullResponse += chunk; |
|
|
fullResponse += chunk; |
|
|
|
|
|
|
|
|
// 更新加载中的消息内容 |
|
|
// 更新中间状态的消息内容 |
|
|
_updateLoadingMessage(fullResponse); |
|
|
_updateIntermediateMessage(fullResponse); |
|
|
|
|
|
|
|
|
// 如果TTS启用,使用流式处理发送到TTS |
|
|
// 如果TTS启用,使用流式处理发送到TTS |
|
|
if (isTtsEnabled.value) { |
|
|
if (isTtsEnabled.value) { |
|
|
@ -366,21 +369,18 @@ class ChatController extends GetxController { |
|
|
onDone: () { |
|
|
onDone: () { |
|
|
if (_shouldCancelAiResponse) return; |
|
|
if (_shouldCancelAiResponse) return; |
|
|
|
|
|
|
|
|
// 完成响应,将isLoading设为false |
|
|
// 完成响应,将isIntermediate设为false |
|
|
_finalizeResponse(fullResponse); |
|
|
_finalizeResponse(fullResponse); |
|
|
|
|
|
|
|
|
// 如果TTS启用,处理缓冲区中的剩余文本 |
|
|
// 如果TTS启用,处理缓冲区中的剩余文本 |
|
|
if (isTtsEnabled.value) { |
|
|
if (isTtsEnabled.value) { |
|
|
_ttsService.flushStream(); |
|
|
_ttsService.flushStream(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 保存聊天历史 |
|
|
|
|
|
_saveChatHistory(); |
|
|
|
|
|
}, |
|
|
}, |
|
|
onError: (error) { |
|
|
onError: (error) { |
|
|
Logger.error('AI响应错误', error); |
|
|
Logger.error('AI响应错误', error); |
|
|
|
|
|
|
|
|
// 更新加载中的消息为错误消息 |
|
|
// 更新中间状态的消息为错误消息 |
|
|
_handleResponseError(); |
|
|
_handleResponseError(); |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
|
@ -396,7 +396,7 @@ class ChatController extends GetxController { |
|
|
final List<Map<String, String>> messageHistory = []; |
|
|
final List<Map<String, String>> messageHistory = []; |
|
|
|
|
|
|
|
|
// 只取最近的10条消息作为上下文 |
|
|
// 只取最近的10条消息作为上下文 |
|
|
final recentMessages = messages.where((m) => !m.isLoading).toList(); |
|
|
final recentMessages = messages.where((m) => !m.isIntermediate).toList(); |
|
|
final startIndex = |
|
|
final startIndex = |
|
|
recentMessages.length > 10 ? recentMessages.length - 10 : 0; |
|
|
recentMessages.length > 10 ? recentMessages.length - 10 : 0; |
|
|
|
|
|
|
|
|
@ -411,42 +411,61 @@ class ChatController extends GetxController { |
|
|
return messageHistory; |
|
|
return messageHistory; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 更新加载中的消息 |
|
|
// 更新中间状态的消息 |
|
|
void _updateLoadingMessage(String content) { |
|
|
void _updateIntermediateMessage(String content) { |
|
|
final index = messages.indexWhere((m) => m.isLoading); |
|
|
final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); |
|
|
if (index != -1) { |
|
|
if (index != -1) { |
|
|
messages[index] = messages[index].copyWith( |
|
|
messages[index] = messages[index].copyWith( |
|
|
content: content, |
|
|
content: content, |
|
|
isLoading: true, |
|
|
|
|
|
); |
|
|
); |
|
|
messages.refresh(); |
|
|
messages.refresh(); |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 完成响应 |
|
|
// 更新或添加最终响应 |
|
|
void _finalizeResponse(String content) { |
|
|
void _finalizeResponse(String content) { |
|
|
final index = messages.indexWhere((m) => m.isLoading); |
|
|
// 查找AI的中间状态消息 |
|
|
|
|
|
final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); |
|
|
if (index != -1) { |
|
|
if (index != -1) { |
|
|
|
|
|
// 更新中间状态消息为最终状态 |
|
|
messages[index] = messages[index].copyWith( |
|
|
messages[index] = messages[index].copyWith( |
|
|
content: content, |
|
|
content: content, |
|
|
isLoading: false, |
|
|
isIntermediate: false, |
|
|
|
|
|
timestamp: DateTime.now(), // 更新时间戳为最终时间 |
|
|
); |
|
|
); |
|
|
messages.refresh(); |
|
|
// 保存最终的助手消息到数据库 |
|
|
_scrollToBottom(); |
|
|
_saveMessageToDb(messages[index]); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果没有中间状态消息,直接添加新消息 (理论上不应发生) |
|
|
|
|
|
final assistantMsg = Message( |
|
|
|
|
|
role: 'assistant', |
|
|
|
|
|
content: content, |
|
|
|
|
|
timestamp: DateTime.now(), |
|
|
|
|
|
); |
|
|
|
|
|
messages.add(assistantMsg); |
|
|
|
|
|
_saveMessageToDb(assistantMsg); // 保存到数据库 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isLoading.value = false; |
|
|
isSending.value = false; |
|
|
isSending.value = false; |
|
|
_aiResponseSubscription = null; |
|
|
_aiResponseSubscription = null; // 清理订阅 |
|
|
|
|
|
_shouldCancelAiResponse = false; // 重置取消标志 |
|
|
|
|
|
|
|
|
|
|
|
// 响应完成后,重新启动语音识别的自动停止计时器 |
|
|
|
|
|
if (_isVoiceInteractionMode) { |
|
|
|
|
|
_startAutoStopTimer(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 处理响应错误 |
|
|
// 处理响应错误 |
|
|
void _handleResponseError() { |
|
|
void _handleResponseError() { |
|
|
final index = messages.indexWhere((m) => m.isLoading); |
|
|
// 查找AI的中间状态消息 |
|
|
|
|
|
final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); |
|
|
if (index != -1) { |
|
|
if (index != -1) { |
|
|
messages[index] = messages[index].copyWith( |
|
|
messages[index] = messages[index].copyWith( |
|
|
content: '抱歉,我遇到了一些问题,无法回应您的请求。', |
|
|
content: '抱歉,我遇到了一些问题,无法回应您的请求。', |
|
|
isLoading: false, |
|
|
isIntermediate: false, |
|
|
); |
|
|
); |
|
|
messages.refresh(); |
|
|
messages.refresh(); |
|
|
} |
|
|
} |
|
|
@ -479,9 +498,6 @@ class ChatController extends GetxController { |
|
|
isListening.value = true; |
|
|
isListening.value = true; |
|
|
recognizedText.value = ''; |
|
|
recognizedText.value = ''; |
|
|
|
|
|
|
|
|
// 重置临时消息索引 |
|
|
|
|
|
_tempMessageIndex = -1; |
|
|
|
|
|
|
|
|
|
|
|
// 启动自动停止计时器 |
|
|
// 启动自动停止计时器 |
|
|
_startAutoStopTimer(); |
|
|
_startAutoStopTimer(); |
|
|
|
|
|
|
|
|
@ -573,24 +589,23 @@ class ChatController extends GetxController { |
|
|
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); |
|
|
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 更新或创建临时消息 |
|
|
// 查找或创建用户的中间状态消息 |
|
|
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { |
|
|
final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); |
|
|
// 更新已有的临时消息 |
|
|
|
|
|
messages[_tempMessageIndex] = messages[_tempMessageIndex].copyWith( |
|
|
if (intermediateIndex >= 0) { |
|
|
|
|
|
// 更新已有的中间状态消息 |
|
|
|
|
|
messages[intermediateIndex] = messages[intermediateIndex].copyWith( |
|
|
content: text, |
|
|
content: text, |
|
|
); |
|
|
); |
|
|
messages.refresh(); |
|
|
messages.refresh(); |
|
|
} else { |
|
|
} else { |
|
|
// 添加新的临时消息 |
|
|
// 添加新的中间状态消息 |
|
|
final tempMessage = Message( |
|
|
var intermediateMessage = Message.intermediate('user'); |
|
|
role: 'user', |
|
|
intermediateMessage = intermediateMessage.copyWith(content: text); |
|
|
content: text, |
|
|
messages.add(intermediateMessage); |
|
|
timestamp: DateTime.now(), |
|
|
|
|
|
); |
|
|
|
|
|
messages.add(tempMessage); |
|
|
|
|
|
_tempMessageIndex = messages.length - 1; |
|
|
|
|
|
messages.refresh(); |
|
|
messages.refresh(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
}); |
|
|
}); |
|
|
@ -606,14 +621,12 @@ class ChatController extends GetxController { |
|
|
// 更新活动时间 |
|
|
// 更新活动时间 |
|
|
_updateActivityTime(); |
|
|
_updateActivityTime(); |
|
|
|
|
|
|
|
|
// 查找并移除临时消息 |
|
|
// 查找并移除用户的中间状态消息 |
|
|
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { |
|
|
final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); |
|
|
messages.removeAt(_tempMessageIndex); |
|
|
if (intermediateIndex >= 0) { |
|
|
|
|
|
messages.removeAt(intermediateIndex); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重置临时消息索引 |
|
|
|
|
|
_tempMessageIndex = -1; |
|
|
|
|
|
|
|
|
|
|
|
// 发送识别到的文本 |
|
|
// 发送识别到的文本 |
|
|
sendMessage(text); |
|
|
sendMessage(text); |
|
|
|
|
|
|
|
|
@ -640,16 +653,20 @@ class ChatController extends GetxController { |
|
|
|
|
|
|
|
|
// 清理语音识别资源 |
|
|
// 清理语音识别资源 |
|
|
void _cleanupRecognition() { |
|
|
void _cleanupRecognition() { |
|
|
// 查找并移除临时消息 |
|
|
// 查找并移除用户的中间状态消息 |
|
|
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { |
|
|
final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); |
|
|
if (messages[_tempMessageIndex].content.trim().isEmpty) { |
|
|
if (intermediateIndex >= 0) { |
|
|
messages.removeAt(_tempMessageIndex); |
|
|
if (messages[intermediateIndex].content.trim().isEmpty) { |
|
|
|
|
|
messages.removeAt(intermediateIndex); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果中间状态消息有内容,将其转换为正式消息 |
|
|
|
|
|
messages[intermediateIndex] = messages[intermediateIndex].copyWith( |
|
|
|
|
|
isIntermediate: false, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
messages.refresh(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重置临时消息索引 |
|
|
|
|
|
_tempMessageIndex = -1; |
|
|
|
|
|
|
|
|
|
|
|
// 停止语音识别 |
|
|
// 停止语音识别 |
|
|
isVoiceRecognitionActive.value = false; |
|
|
isVoiceRecognitionActive.value = false; |
|
|
isListening.value = false; |
|
|
isListening.value = false; |
|
|
@ -671,15 +688,20 @@ class ChatController extends GetxController { |
|
|
recognizedText.value = ''; |
|
|
recognizedText.value = ''; |
|
|
isSpeechDetected.value = false; |
|
|
isSpeechDetected.value = false; |
|
|
|
|
|
|
|
|
// 查找并移除临时消息 |
|
|
// 查找并处理用户的中间状态消息 |
|
|
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { |
|
|
final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); |
|
|
if (messages[_tempMessageIndex].content.trim().isEmpty) { |
|
|
if (intermediateIndex >= 0) { |
|
|
messages.removeAt(_tempMessageIndex); |
|
|
if (messages[intermediateIndex].content.trim().isEmpty) { |
|
|
|
|
|
// 如果没有内容则移除 |
|
|
|
|
|
messages.removeAt(intermediateIndex); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 有内容则转为正式消息 |
|
|
|
|
|
messages[intermediateIndex] = messages[intermediateIndex].copyWith( |
|
|
|
|
|
isIntermediate: false, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
messages.refresh(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重置临时消息索引 |
|
|
|
|
|
_tempMessageIndex = -1; |
|
|
|
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('停止语音识别失败', e); |
|
|
Logger.error('停止语音识别失败', e); |
|
|
} |
|
|
} |
|
|
@ -688,7 +710,7 @@ class ChatController extends GetxController { |
|
|
// 清空聊天记录 |
|
|
// 清空聊天记录 |
|
|
void clearChat() { |
|
|
void clearChat() { |
|
|
messages.clear(); |
|
|
messages.clear(); |
|
|
|
|
|
_ttsService.stop(); |
|
|
// 添加欢迎消息 |
|
|
// 添加欢迎消息 |
|
|
if (agent.welcomeMessage.isNotEmpty) { |
|
|
if (agent.welcomeMessage.isNotEmpty) { |
|
|
messages.add(Message( |
|
|
messages.add(Message( |
|
|
@ -697,31 +719,119 @@ class ChatController extends GetxController { |
|
|
timestamp: DateTime.now(), |
|
|
timestamp: DateTime.now(), |
|
|
)); |
|
|
)); |
|
|
} |
|
|
} |
|
|
|
|
|
// 删除数据库中的历史记录 |
|
|
|
|
|
ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) { |
|
|
|
|
|
Logger.error('清空数据库聊天记录失败: $e'); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 删除存储的历史记录 |
|
|
// 滚动监听,用于触发加载更多历史记录 |
|
|
_historyService.clearHistory(agent.id); |
|
|
void _scrollListener() { |
|
|
|
|
|
// 当滚动到顶部并且有更多历史记录且不在加载中时,加载更多 |
|
|
|
|
|
if (scrollController.position.pixels == scrollController.position.minScrollExtent && |
|
|
|
|
|
_hasMoreHistory.value && |
|
|
|
|
|
!_isLoadingMore.value) { |
|
|
|
|
|
_loadMoreHistory(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 加载聊天历史 |
|
|
// 加载聊天历史 (首次加载或刷新) |
|
|
void _loadChatHistory() { |
|
|
Future<void> _loadChatHistory() async { |
|
|
// 从历史服务加载 |
|
|
isLoading.value = true; |
|
|
final history = _historyService.loadHistory(agent.id); |
|
|
_currentPage.value = 1; // 重置为第一页 |
|
|
if (history.isNotEmpty) { |
|
|
_hasMoreHistory.value = true; // 假设有更多 |
|
|
messages.clear(); |
|
|
|
|
|
messages.addAll(history); |
|
|
try { |
|
|
|
|
|
final pagedResult = await ChatStorage.getMessages( |
|
|
|
|
|
sessionId: agent.id, |
|
|
|
|
|
page: _currentPage.value, |
|
|
|
|
|
pageSize: _pageSize, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_totalPages.value = pagedResult.totalPages; |
|
|
|
|
|
_hasMoreHistory.value = _currentPage.value < _totalPages.value; |
|
|
|
|
|
|
|
|
|
|
|
final historyMessages = pagedResult.messages.map((chatMsg) { |
|
|
|
|
|
return Message( |
|
|
|
|
|
role: chatMsg.sender, |
|
|
|
|
|
content: chatMsg.message, |
|
|
|
|
|
timestamp: DateTime.parse(chatMsg.timestamp), |
|
|
|
|
|
); |
|
|
|
|
|
}).toList(); |
|
|
|
|
|
|
|
|
|
|
|
messages.assignAll(historyMessages.reversed); // 最新的在底部,所以要反转 |
|
|
|
|
|
|
|
|
|
|
|
// 滚动到底部 (如果是首次加载) |
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
|
|
|
if (scrollController.hasClients) { |
|
|
|
|
|
scrollController.jumpTo(scrollController.position.maxScrollExtent); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('加载聊天历史失败', e); |
|
|
|
|
|
} finally { |
|
|
|
|
|
isLoading.value = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 保存聊天历史 |
|
|
// 加载更多历史记录 |
|
|
void _saveChatHistory() { |
|
|
Future<void> _loadMoreHistory() async { |
|
|
_historyService.saveHistory(agent.id, messages); |
|
|
if (_isLoadingMore.value || !_hasMoreHistory.value) return; |
|
|
|
|
|
|
|
|
|
|
|
_isLoadingMore.value = true; |
|
|
|
|
|
_currentPage.value++; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
final pagedResult = await ChatStorage.getMessages( |
|
|
|
|
|
sessionId: agent.id, |
|
|
|
|
|
page: _currentPage.value, |
|
|
|
|
|
pageSize: _pageSize, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_totalPages.value = pagedResult.totalPages; |
|
|
|
|
|
_hasMoreHistory.value = _currentPage.value < _totalPages.value; |
|
|
|
|
|
|
|
|
|
|
|
final olderMessages = pagedResult.messages.map((chatMsg) { |
|
|
|
|
|
return Message( |
|
|
|
|
|
role: chatMsg.sender, |
|
|
|
|
|
content: chatMsg.message, |
|
|
|
|
|
timestamp: DateTime.parse(chatMsg.timestamp), |
|
|
|
|
|
); |
|
|
|
|
|
}).toList(); |
|
|
|
|
|
|
|
|
|
|
|
// 将旧消息插入到列表顶部 |
|
|
|
|
|
messages.insertAll(0, olderMessages.reversed); |
|
|
|
|
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('加载更多聊天历史失败', e); |
|
|
|
|
|
_currentPage.value--; // 加载失败,恢复页码 |
|
|
|
|
|
} finally { |
|
|
|
|
|
_isLoadingMore.value = false; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 添加消息,供外部调用 |
|
|
// 保存单条聊天消息到数据库 |
|
|
|
|
|
Future<void> _saveMessageToDb(Message message) async { |
|
|
|
|
|
if (message.isIntermediate) return; // 不保存中间状态的消息 |
|
|
|
|
|
try { |
|
|
|
|
|
await ChatStorage.saveMessage( |
|
|
|
|
|
sessionId: agent.id, |
|
|
|
|
|
message: message.content, |
|
|
|
|
|
sender: message.role, |
|
|
|
|
|
); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('保存消息到数据库失败', e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 添加消息,供外部调用 (如用户发送消息、AI回复) |
|
|
void addMessage(Message message) { |
|
|
void addMessage(Message message) { |
|
|
messages.add(message); |
|
|
messages.add(message); |
|
|
_saveChatHistory(); |
|
|
_saveMessageToDb(message); // 保存到数据库 |
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
|
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void _stopVoiceRecognitionAutomatically() { |
|
|
void _stopVoiceRecognitionAutomatically() { |
|
|
|