import 'dart:async'; import 'package:deep_voice/data/services/spotify_service.dart'; import 'package:get/get.dart'; import 'package:flutter/widgets.dart'; import 'package:get_storage/get_storage.dart'; import 'package:just_audio/just_audio.dart'; import 'package:chat_storage/chat_storage.dart'; import 'package:path/path.dart'; import '../../../data/services/ai_service.dart'; import '../../../data/services/deapsound_ai_service.dart'; import '../../../data/services/kouzi_ai_service.dart'; import '../../../data/services/speech_impl/voice_clone_tts_service.dart'; import '../models/message_model.dart'; import '../../../core/utils/logger.dart'; import '../../../data/services/tts_service.dart'; import '../../../data/providers/agent_provider.dart'; import '../../../data/models/agent.dart'; import '../../../data/services/asr_service.dart'; import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/open_ai_service_adapter.dart'; class ChatController extends GetxController { // 服务 late AiService _aiService; late TtsService _ttsService; final AsrService _asrService = Get.find(); final VoiceInteractionService _voiceInteractionService = Get.find(); late SpotifService _spotifService; // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); // Agent 信息 late final Agent agent; // 消息列表 final RxList messages = [].obs; // 输入控制 final RxString inputText = ''.obs; // 状态控制 final RxBool isLoading = false.obs; final RxBool isSending = false.obs; final RxBool isTtsEnabled = true.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; // 语音识别状态 final RxString recognizedText = ''.obs; final RxBool isListening = false.obs; final RxBool isSpeechDetected = false.obs; // 防抖计时器 Timer? _scrollDebounceTimer; // 流式响应控制 StreamSubscription? _aiResponseSubscription; StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; // 取消标志 bool _shouldCancelAiResponse = false; // 自动停止语音识别计时器 Timer? _autoStopTimer; // 记录最后一次活动时间 DateTime _lastActivityTime = DateTime.now(); final player = AudioPlayer(); final RxString audiourl = ''.obs; final RxBool isPlay = false.obs; @override Future onInit() async { super.onInit(); Logger.info('ChatController onInit'); // 从路由参数获取Agent信息 final Map args = Get.arguments ?? {}; final String agentId = args['agent'] ?? args['agentId'] ?? 'personal_assistant'; // 获取Agent final Agent? foundAgent = AgentProvider.getAgentById(agentId); if (foundAgent == null) { Logger.error('无法找到Agent: $agentId'); Get.back(); return; } agent = foundAgent; // 根据Agent ID选择不同的AI服务 switch (agent.id) { case 'cyber_girlfriend': // 亲子陪伴 Get.put(KouziAIService()); _aiService = Get.find(); break; case 'health_advisor': // 多功能AI聊天 Get.put(DeapsoundAIService()); _aiService = Get.find(); break; default: // 默认使用OpenAIServiceAdapter _aiService = Get.find(); Logger.info('使用OpenAIServiceAdapter'); } Get.put(SpotifService()); _spotifService = Get.find(); // 使用克隆音色语音合成 final GetStorage storage = GetStorage(); String timbre = storage.read('timbre') ?? '1'; if (timbre == '1') { _ttsService = Get.find(); } else { Get.put(VoiceCloneTtsService()); _ttsService = Get.find(); } _ttsService.initialize(); _asrService.initialize(); // 设置语音交互模式 _isVoiceInteractionMode = args['isVoiceInteraction'] == true; // 设置TTS服务 await _ttsService.setVoice(agent.voice); // 添加滚动监听器,用于触发加载更多 scrollController.addListener(_scrollListener); // 加载聊天历史 (加载第一页) await _loadChatHistory(); // 如果没有历史记录,添加欢迎消息 if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) { messages.add(Message( role: 'assistant', content: agent.welcomeMessage, timestamp: DateTime.now(), )); // 如果启用了TTS,播放欢迎消息 // if (isTtsEnabled.value) { // _ttsService.speak(agent.welcomeMessage); // } } // 如果isVoiceInteraction为true,自动激活语音识别 if (args['isVoiceInteraction'] == true) { _ttsService.speakOnce(agent.voiceWelcomeMessage ?? agent.welcomeMessage); startVoiceRecognition(); // 自动停止计时器会在startVoiceRecognition中启动 } // 订阅VoiceInteractionService事件 _subscribeToVoiceInteractionEvents(); player.playerStateStream.listen((state) async { if (state.processingState == ProcessingState.completed) { await player.seek(Duration.zero); player.pause(); isPlay.value = false; } }); // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } /// 订阅VoiceInteractionService事件 void _subscribeToVoiceInteractionEvents() { if (_voiceInteractionService.eventStream != null) { _voiceInteractionSubscription = _voiceInteractionService.eventStream.listen((event) { if (event is RecognitionStartedEvent) { // 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS _stopCurrentInteraction(); } else if (event is ChatHistoryEvent) { // 收到聊天历史事件,添加到聊天记录 _processChatHistoryEvent(event); } }); } } /// 处理聊天历史事件 void _processChatHistoryEvent(ChatHistoryEvent event) { Logger.info('收到后台聊天历史事件: agentId=${event.agentId}'); // 如果消息不是空的且不是当前Agent的,不处理 if (event.userMessage.isEmpty || event.agentId != agent.id) { return; } // 添加用户消息 final userMsg = Message( role: 'user', content: event.userMessage, timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp), ); // 添加助手消息 final assistantMsg = Message( role: 'assistant', content: event.assistantMessage, timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒 ); // 防止消息重复 if (!_isDuplicateMessage(userMsg) && !_isDuplicateMessage(assistantMsg)) { messages.add(userMsg); messages.add(assistantMsg); } WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } /// 停止当前交互 void _stopCurrentInteraction() { Logger.info('收到后台语音识别开始事件,停止当前AI交互'); // 停止当前AI交互 if (isSending.value || _aiResponseSubscription != null) { _interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应"); } // 停止TTS _ttsService.stop(); // 停止语音识别 if (isVoiceRecognitionActive.value) { stopVoiceRecognition(); } } @override void onClose() { Logger.info('ChatController onClose'); // 清理资源 scrollController.removeListener(_scrollListener); // 移除监听器 scrollController.dispose(); _aiResponseSubscription?.cancel(); _recognitionSubscription?.cancel(); _voiceInteractionSubscription?.cancel(); _autoStopTimer?.cancel(); _scrollDebounceTimer?.cancel(); // 清理防抖计时器 // 停止TTS _ttsService.stop(); if (isVoiceRecognitionActive.value) { _asrService.stopContinuousRecognition(); } // 保存当前聊天记录 if (agent.id != null) { // _historyService.saveHistory(agent.id!, messages.toList()); } player.dispose(); super.onClose(); } /// 滚动到列表底部方法,添加防抖功能 void _scrollToBottom() { if (scrollController.hasClients && messages.isNotEmpty) { try { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, ); } catch (e) { Logger.error('滚动到底部失败', e); } } } /// 中断当前响应 void _interruptCurrentResponse(String reason) { Logger.info('${reason}'); // 停止TTS _ttsService.stop(); // 取消AI响应流订阅 if (_aiResponseSubscription != null) { _shouldCancelAiResponse = true; _aiResponseSubscription!.cancel(); _aiResponseSubscription = null; // 将AI中间消息转换为正常消息,保留已输出的内容 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { messages[index] = messages[index].copyWith( isIntermediate: false, ); messages.refresh(); } // 重置发送状态 isSending.value = false; } } // 发送消息 Future sendMessage(String text) async { if (text.trim().isEmpty) return; _voiceInteractionService.pauseVoiceInteraction(); // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); // 添加用户消息 final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); // 清空输入框 inputText.value = ''; // 添加中间状态的AI消息 final intermediateMessage = Message.intermediate('assistant'); messages.add(intermediateMessage); messages.refresh(); WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); isSending.value = true; try { // 构建消息历史 final List> messageHistory = _buildMessageHistory(); // 重置取消标志 _shouldCancelAiResponse = false; String fullResponse = ''; // 获取AI响应流 final responseStream = _aiService.sendMessageStream( messages: messageHistory, systemPrompt: agent.systemPrompt, ); // 订阅响应流 _aiResponseSubscription = responseStream.listen( (chunk) { if (_shouldCancelAiResponse) return; Logger.info('chat ${chunk}'); if (chunk is Map) { _updateIntermediateMessage(data: chunk); if (chunk['card_spotify'] != null) { play(chunk['card_spotify']); } } else { fullResponse += chunk; // 更新中间状态的消息内容 _updateIntermediateMessage(content: fullResponse); // 如果TTS启用,使用流式处理发送到TTS if (isTtsEnabled.value) { _ttsService.speakStream(chunk); } } }, onDone: () { if (_shouldCancelAiResponse) return; // 完成响应,将isIntermediate设为false _finalizeResponse(fullResponse); // 如果TTS启用,处理缓冲区中的剩余文本 if (isTtsEnabled.value) { _ttsService.flushStream(); } }, onError: (error) { Logger.error('AI响应错误', error); // 更新中间状态的消息为错误消息 _handleResponseError(); }, ); } catch (e) { Logger.error('发送消息失败', e); _handleResponseError(); } } // 构建消息历史 List> _buildMessageHistory() { final List> messageHistory = []; // 只取最近的10条消息作为上下文 final recentMessages = messages.where((m) => !m.isIntermediate).toList(); final startIndex = recentMessages.length > 10 ? recentMessages.length - 10 : 0; for (int i = startIndex; i < recentMessages.length; i++) { final m = recentMessages[i]; messageHistory.add({ 'role': m.role, 'content': m.content, }); } return messageHistory; } // 更新中间状态的消息 void _updateIntermediateMessage({Map? data, String? content}) { final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { if (data != null) { Map messagesData = messages[index].data ?? {}; messages[index] = messages[index].copyWith( data: {...messagesData, ...data}, ); } messages[index] = messages[index].copyWith( content: content, ); messages.refresh(); _scrollToBottom(); } } // 播放音乐 void play(Map card) async { try { await _spotifService.initialize(); _spotifService.play(card['url'] as String); isPlay.value = true; } catch (e) { Logger.error('加载异常: $e'); } } // 暂停播放音乐 void pause() async { try { _spotifService.pause(); isPlay.value = false; } catch (e) { Logger.error('加载异常: $e'); } } // 更新或添加最终响应 void _finalizeResponse(String content) { // 查找AI的中间状态消息 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { // 更新中间状态消息为最终状态 messages[index] = messages[index].copyWith( content: content, isIntermediate: false, timestamp: DateTime.now(), // 更新时间戳为最终时间 ); // 保存最终的助手消息到数据库 _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; _aiResponseSubscription = null; // 清理订阅 _shouldCancelAiResponse = false; // 重置取消标志 // 响应完成后,重新启动语音识别的自动停止计时器 if (_isVoiceInteractionMode) { _startAutoStopTimer(); } } // 处理响应错误 void _handleResponseError() { // 查找AI的中间状态消息 final index = messages.indexWhere((m) => m.isIntermediate && m.role == 'assistant'); if (index != -1) { messages[index] = messages[index].copyWith( content: '抱歉,我遇到了一些问题,无法回应您的请求。', isIntermediate: false, ); messages.refresh(); } isSending.value = false; _aiResponseSubscription = null; } // 切换TTS状态 void toggleTts() { isTtsEnabled.toggle(); // 如果关闭TTS,停止当前播放 if (!isTtsEnabled.value) { _ttsService.stop(); } } // 开始语音识别 Future startVoiceRecognition() async { if (isVoiceRecognitionActive.value) return; _voiceInteractionService.pauseVoiceInteraction(); try { // 启动连续语音识别 // 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据 final recognitionStream = await _asrService.startContinuousRecognition(); isVoiceRecognitionActive.value = true; isListening.value = true; recognizedText.value = ''; // 启动自动停止计时器 _startAutoStopTimer(); // 监听识别事件 _recognitionSubscription = recognitionStream.listen( _handleRecognitionEvent, onError: _handleRecognitionError, ); } catch (e) { Logger.error('启动语音识别失败', e); isVoiceRecognitionActive.value = false; isListening.value = false; } } // 处理语音识别事件 void _handleRecognitionEvent(RecognitionEvent event) { // 更新活动时间 _updateActivityTime(); switch (event.type) { case RecognitionEventType.intermediateResult: _handleRecognizing(event.text); break; case RecognitionEventType.finalResult: _handleFinalResult(event.text); break; case RecognitionEventType.error: _handleRecognitionEventError(event.error); break; default: // 不做任何处理 break; } } // 更新最后活动时间 void _updateActivityTime() { _lastActivityTime = DateTime.now(); } // 启动自动停止计时器 void _startAutoStopTimer() { // 取消现有计时器 _autoStopTimer?.cancel(); // 初始化最后活动时间 _updateActivityTime(); // 根据模式设置超时时间 final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15; // 创建新的计时器,每秒检查一次 _autoStopTimer = Timer.periodic(Duration(seconds: 1), (timer) { // 检查是否有活动 bool isActive = _ttsService.isSpeaking || isSpeechDetected.value; // 如果有活动,更新时间并继续等待 if (isActive) { _updateActivityTime(); return; } // 检查空闲时间 int idleSeconds = DateTime.now().difference(_lastActivityTime).inSeconds; if (idleSeconds >= timeoutSeconds) { timer.cancel(); _stopVoiceRecognitionAutomatically(); } }); } // 处理语音识别中的状态 void _handleRecognizing(String text) { if (text.isEmpty) return; recognizedText.value = text; // 设置语音检测状态为true,用于控制波形动画 if (!isSpeechDetected.value) { isSpeechDetected.value = true; } // 更新活动时间 _updateActivityTime(); // 用户开始讲话时,立即停止当前TTS和中断AI响应 if (isSending.value || _aiResponseSubscription != null) { _interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); } // 查找或创建用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { // 更新已有的中间状态消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( content: text, ); messages.refresh(); } else { // 添加新的中间状态消息 var intermediateMessage = Message.intermediate('user'); intermediateMessage = intermediateMessage.copyWith(content: text); messages.add(intermediateMessage); messages.refresh(); } WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } // 处理语音识别最终结果 void _handleFinalResult(String text) { if (text.isEmpty) return; Logger.info('处理语音识别最终结果: $text'); recognizedText.value = text; // 更新活动时间 _updateActivityTime(); // 查找并移除用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { messages.removeAt(intermediateIndex); } // 发送识别到的文本 sendMessage(text); // 重置识别文本,但不关闭语音识别框 recognizedText.value = ''; // 重置语音检测状态 isSpeechDetected.value = false; } // 处理语音识别事件错误 void _handleRecognitionEventError(String? error) { Logger.error('语音识别错误: ${error ?? "未知错误"}'); _cleanupRecognition(); } // 处理语音识别流错误 void _handleRecognitionError(dynamic error) { Logger.error('语音识别错误', error); _cleanupRecognition(); } // 清理语音识别资源 void _cleanupRecognition() { // 查找并移除用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { if (messages[intermediateIndex].content.trim().isEmpty) { messages.removeAt(intermediateIndex); } else { // 如果中间状态消息有内容,将其转换为正式消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( isIntermediate: false, ); } messages.refresh(); } // 停止语音识别 isVoiceRecognitionActive.value = false; isListening.value = false; isSpeechDetected.value = false; _asrService.stopContinuousRecognition(); } // 停止语音识别 Future stopVoiceRecognition() async { if (!isVoiceRecognitionActive.value) return; try { await _asrService.stopContinuousRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; isVoiceRecognitionActive.value = false; isListening.value = false; recognizedText.value = ''; isSpeechDetected.value = false; // 查找并处理用户的中间状态消息 final intermediateIndex = messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); if (intermediateIndex >= 0) { if (messages[intermediateIndex].content.trim().isEmpty) { // 如果没有内容则移除 messages.removeAt(intermediateIndex); } else { // 有内容则转为正式消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( isIntermediate: false, ); } messages.refresh(); } } catch (e) { Logger.error('停止语音识别失败', e); } } // 清空聊天记录 void clearChat() { messages.clear(); _ttsService.stop(); // 添加欢迎消息 if (agent.welcomeMessage.isNotEmpty) { messages.add(Message( role: 'assistant', content: agent.welcomeMessage, timestamp: DateTime.now(), )); } // 删除数据库中的历史记录 ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) { Logger.error('清空数据库聊天记录失败: $e'); }); } // 滚动监听,用于触发加载更多历史记录 void _scrollListener() { // 当滚动到顶部并且有更多历史记录且不在加载中时,加载更多 if (scrollController.position.pixels == scrollController.position.minScrollExtent && _hasMoreHistory.value && !_isLoadingMore.value) { _loadMoreHistory(); } } // 加载聊天历史 (首次加载或刷新) Future _loadChatHistory() async { isLoading.value = true; _currentPage.value = 1; // 重置为第一页 _hasMoreHistory.value = true; // 假设有更多 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; } } // 加载更多历史记录 Future _loadMoreHistory() async { 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 _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) { messages.add(message); _saveMessageToDb(message); // 保存到数据库 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } void _stopVoiceRecognitionAutomatically() { final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15; Logger.info('${timeoutSeconds}秒无活动,自动停止语音识别'); stopVoiceRecognition(); } /// 检查是否为重复消息 bool _isDuplicateMessage(Message message) { return messages.any((m) => m.role == message.role && m.content == message.content && (m.timestamp.millisecondsSinceEpoch - message.timestamp.millisecondsSinceEpoch) .abs() < 5000); } }