import 'dart:async'; import 'package:get/get.dart'; import 'package:flutter/widgets.dart'; import '../models/message_model.dart'; import '../../../data/services/volcano_ai_service.dart'; import '../../../data/services/azure_tts_service.dart'; import '../../../data/services/azure_asr_service.dart'; import '../../../data/providers/agent_provider.dart'; import '../../../data/models/agent.dart'; import '../../../core/utils/logger.dart'; import '../../../data/services/chat_history_service.dart'; class ChatController extends GetxController { // 服务 final VolcanoAIService _aiService = Get.find(); final AzureTtsService _ttsService = Get.find(); final AzureAsrService _asrService = Get.find(); final ChatHistoryService _historyService = Get.find(); // 添加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 RxString recognizedText = ''.obs; final RxBool isListening = false.obs; final RxBool isSpeechDetected = false.obs; // 临时消息索引,用于更新临时消息 int _tempMessageIndex = -1; // 流式响应控制 StreamSubscription? _aiResponseSubscription; StreamSubscription? _recognitionSubscription; // 取消标志 bool _shouldCancelAiResponse = false; @override Future onInit() async { super.onInit(); Logger.info('ChatController onInit'); // 从路由参数获取Agent信息 final Map args = Get.arguments ?? {}; final String agentId = args['agentId'] ?? 'personal_assistant'; // 获取Agent final Agent? foundAgent = AgentProvider.getAgentById(agentId); if (foundAgent == null) { Logger.error('无法找到Agent: $agentId'); Get.back(); return; } agent = foundAgent; // 设置TTS服务 await _ttsService.setVoice(agent.voice); // 加载聊天历史 _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); } } // 如果playVoiceOnEnter为true,自动激活语音识别 if (args['playVoiceOnEnter'] == true) { startVoiceRecognition(); } // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } @override void onClose() { Logger.info('ChatController onClose'); // 清理资源 scrollController.dispose(); _aiResponseSubscription?.cancel(); _recognitionSubscription?.cancel(); // 停止TTS _ttsService.stop(); if (isVoiceRecognitionActive.value) { _asrService.stopContinuousRecognition(); } 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; // 将加载中的消息转换为正常消息,保留已输出的内容 final index = messages.indexWhere((m) => m.isLoading); if (index != -1) { messages[index] = messages[index].copyWith( isLoading: false, ); messages.refresh(); } // 重置发送状态 isSending.value = false; } } // 发送消息 Future sendMessage(String text) async { if (text.trim().isEmpty) return; // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); // 添加用户消息 final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); _scrollToBottom(); // 清空输入框 inputText.value = ''; // 添加加载中的消息 final loadingMessage = Message.loading(); messages.add(loadingMessage); _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; fullResponse += chunk; // 更新加载中的消息内容 _updateLoadingMessage(fullResponse); // 如果TTS启用,使用流式处理发送到TTS if (isTtsEnabled.value) { _ttsService.processStreamText(chunk); } }, onDone: () { if (_shouldCancelAiResponse) return; // 完成响应,将isLoading设为false _finalizeResponse(fullResponse); // 如果TTS启用,处理缓冲区中的剩余文本 if (isTtsEnabled.value) { _ttsService.flushStreamBuffer(); } // 保存聊天历史 _saveChatHistory(); }, 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.isLoading).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 _updateLoadingMessage(String content) { final index = messages.indexWhere((m) => m.isLoading); if (index != -1) { messages[index] = messages[index].copyWith( content: content, isLoading: true, ); messages.refresh(); _scrollToBottom(); } } // 完成响应 void _finalizeResponse(String content) { final index = messages.indexWhere((m) => m.isLoading); if (index != -1) { messages[index] = messages[index].copyWith( content: content, isLoading: false, ); messages.refresh(); _scrollToBottom(); } isSending.value = false; _aiResponseSubscription = null; } // 处理响应错误 void _handleResponseError() { final index = messages.indexWhere((m) => m.isLoading); if (index != -1) { messages[index] = messages[index].copyWith( content: '抱歉,我遇到了一些问题,无法回应您的请求。', isLoading: 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; try { // 初始化语音识别 await _asrService.initialize(language: 'zh-CN'); // 启动连续语音识别 final recognitionStream = await _asrService.startContinuousRecognition(); isVoiceRecognitionActive.value = true; isListening.value = true; recognizedText.value = ''; // 重置临时消息索引 _tempMessageIndex = -1; // 监听识别事件 _recognitionSubscription = recognitionStream.listen( _handleRecognitionEvent, onError: _handleRecognitionError, ); } catch (e) { Logger.error('启动语音识别失败', e); isVoiceRecognitionActive.value = false; isListening.value = false; } } // 处理语音识别事件 void _handleRecognitionEvent(RecognitionEvent event) { 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 _handleRecognizing(String text) { if (text.isEmpty) return; recognizedText.value = text; // 设置语音检测状态为true,用于控制波形动画 if (!isSpeechDetected.value) { isSpeechDetected.value = true; } // 用户开始讲话时,立即停止当前TTS和中断AI响应 if (isSending.value || _aiResponseSubscription != null) { _interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); } // 更新或创建临时消息 if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { // 更新已有的临时消息 messages[_tempMessageIndex] = messages[_tempMessageIndex].copyWith( content: text, ); messages.refresh(); } else { // 添加新的临时消息 final tempMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(tempMessage); _tempMessageIndex = messages.length - 1; messages.refresh(); } _scrollToBottom(); } // 处理语音识别最终结果 void _handleFinalResult(String text) { if (text.isEmpty) return; Logger.info('处理语音识别最终结果: $text'); recognizedText.value = text; // 查找并移除临时消息 if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { messages.removeAt(_tempMessageIndex); } // 重置临时消息索引 _tempMessageIndex = -1; // 发送识别到的文本 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() { // 查找并移除临时消息 if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { if (messages[_tempMessageIndex].content.trim().isEmpty) { messages.removeAt(_tempMessageIndex); } } // 重置临时消息索引 _tempMessageIndex = -1; // 停止语音识别 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; // 查找并移除临时消息 if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) { if (messages[_tempMessageIndex].content.trim().isEmpty) { messages.removeAt(_tempMessageIndex); } } // 重置临时消息索引 _tempMessageIndex = -1; } catch (e) { Logger.error('停止语音识别失败', e); } } // 清空聊天记录 void clearChat() { messages.clear(); // 添加欢迎消息 if (agent.welcomeMessage.isNotEmpty) { messages.add(Message( role: 'assistant', content: agent.welcomeMessage, timestamp: DateTime.now(), )); } // 删除存储的历史记录 _historyService.clearHistory(agent.id); } // 加载聊天历史 void _loadChatHistory() { // 从历史服务加载 final history = _historyService.loadHistory(agent.id); if (history.isNotEmpty) { messages.clear(); messages.addAll(history); } } // 保存聊天历史 void _saveChatHistory() { _historyService.saveHistory(agent.id, messages); } // 添加消息,供外部调用 void addMessage(Message message) { messages.add(message); _saveChatHistory(); _scrollToBottom(); } }