import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:get_storage/get_storage.dart'; import 'dart:convert'; import 'package:flutter/rendering.dart'; import '../../../data/services/volcano_ai_service.dart'; import '../../../data/services/volcano_tts_api_service.dart'; import '../models/message_model.dart'; import '../controllers/voice_input_controller.dart'; import 'dart:async'; class ChatController extends GetxController { String agentId = ''; String agentName = ''; String agentAvatar = ''; String? agentSubtitle; String systemPrompt = ''; String welcomeMessage = ''; bool playVoiceOnEnter = false; final VolcanoAIService _aiService = VolcanoAIService(); late final VolcanoTtsApiService _ttsService; static const String _storagePrefix = 'chat_history_'; final messages = [].obs; final isLoading = false.obs; late final TextEditingController messageController; late final ScrollController scrollController; final _storage = GetStorage(); // TTS启用状态 final isTtsEnabled = true.obs; // 用于存储当前正在流式生成的消息 final RxString currentStreamMessage = ''.obs; Message? _currentAssistantMessage; bool _isDisposed = false; // TTS事件订阅 StreamSubscription? _ttsEventSubscription; bool _isTtsSpeaking = false; // 语音输入相关状态 final isRecording = false.obs; final recordingText = ''.obs; final isVoiceInputVisible = false.obs; final isVoiceConnecting = true.obs; final isVoiceMuted = false.obs; Message? _currentVoiceMessage; // 添加一个标志,表示当前是否有正在进行的语音识别 final isRecognizing = false.obs; // 输入框状态 final isInputCollapsed = false.obs; final isVoiceMode = true.obs; // 默认为语音模式 final isRecordingVoice = false.obs; // 是否正在录音(按住说话状态) // 添加是否在底部的状态变量 final RxBool isAtBottom = true.obs; // 切换输入框折叠状态 void toggleInputCollapsed() { isInputCollapsed.value = !isInputCollapsed.value; } // 切换语音/文本输入模式 void toggleInputMode() { isVoiceMode.value = !isVoiceMode.value; } // 开始语音输入 void startVoiceInput() { // 确保先停止任何可能正在进行的语音识别会话 if (Get.isRegistered()) { final voiceController = Get.find(); voiceController.stopRecording(); Get.delete(); } // 如果TTS正在播放,先停止它 if (_isTtsSpeaking) { try { // 立即停止当前声音播放 _ttsService.stop(); // 结束当前TTS会话 _ttsService.endSession(); _isTtsSpeaking = false; } catch (e) { debugPrint('停止TTS播放失败: $e'); } } // 如果正在处理AI响应,中断处理 if (isLoading.value) { isLoading.value = false; // 清除当前正在生成的消息 if (_currentAssistantMessage != null) { // 如果消息内容为空,则移除该消息 if (_currentAssistantMessage!.content.isEmpty) { messages.remove(_currentAssistantMessage); } _currentAssistantMessage = null; } currentStreamMessage.value = ''; } isVoiceInputVisible.value = true; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; isRecognizing.value = false; // 当语音面板打开时,确保ListView滚动到适当位置,防止最后的消息被遮挡 WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isDisposed && scrollController.hasClients && messages.isNotEmpty) { // 计算需要额外滚动的距离(语音面板高度) final extraScrollDistance = 120.0; // 获取当前滚动位置 final currentPosition = scrollController.position.pixels; final maxScrollExtent = scrollController.position.maxScrollExtent; // 如果已经接近底部,则向上滚动一定距离,确保最后的消息可见 if (maxScrollExtent - currentPosition < extraScrollDistance) { scrollController.animateTo( currentPosition + extraScrollDistance, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } } }); // 创建语音输入控制器 Get.put(VoiceInputController( onRecordingResult: handleVoiceResult, onClosePanel: stopVoiceInput, onRecognizing: handleRecognizing, )); // 滚动到底部 _scrollToBottom(animate: true); } void startRecording() { if (!isVoiceConnecting.value && isVoiceInputVisible.value) { // 不再需要禁用TTS,因为已启用回声消除 // 当用户开始说话时,停止当前TTS播放 if (_isTtsSpeaking) { try { // 立即停止当前声音播放 _ttsService.stop(); // 结束当前TTS会话 _ttsService.endSession(); _isTtsSpeaking = false; } catch (e) { debugPrint('停止TTS播放失败: $e'); } } // 如果正在处理AI响应,中断处理 if (isLoading.value) { isLoading.value = false; // 清除当前正在生成的消息 if (_currentAssistantMessage != null) { // 如果消息内容为空,则移除该消息 if (_currentAssistantMessage!.content.isEmpty) { messages.remove(_currentAssistantMessage); } _currentAssistantMessage = null; } currentStreamMessage.value = ''; } isRecording.value = true; } } void stopRecording() { if (isRecording.value) { isRecording.value = false; } } void toggleMute() { isVoiceMuted.value = !isVoiceMuted.value; } void stopVoiceInput() { // 停止语音识别 if (Get.isRegistered()) { try { Get.find().stopRecording(); Get.delete(); } catch (e) { debugPrint('停止语音输入失败: $e'); } } // 重置状态 isVoiceInputVisible.value = false; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; isRecognizing.value = false; // 清理临时语音消息 if (_currentVoiceMessage != null && (_currentVoiceMessage!.content == '🎤 ...' || _currentVoiceMessage!.content.startsWith('🎤 '))) { messages.remove(_currentVoiceMessage); } _currentVoiceMessage = null; // 滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isDisposed && messages.isNotEmpty) { _scrollToBottom(animate: true); } }); // 重置按住说话状态 // 删除语音输入控制器 try { if (Get.isRegistered()) { Get.delete(); } } catch (e) { debugPrint('Error deleting VoiceInputController: $e'); } // 确保按住说话状态被重置 isRecordingVoice.value = false; } void handleRecognizing(String text) { if (text.isEmpty) return; // 如果TTS正在播放,确保停止它 if (_isTtsSpeaking) { try { // 立即停止当前声音播放 _ttsService.stop(); // 不需要每次都结束会话,只需要停止当前播放 _isTtsSpeaking = false; } catch (e) { debugPrint('停止TTS播放失败: $e'); } } // 更新识别中的文本 recordingText.value = text; // 如果还没有创建临时语音消息,则创建一个 if (_currentVoiceMessage == null) { _currentVoiceMessage = Message( role: 'user', content: '🎤 $text', timestamp: DateTime.now(), ); messages.add(_currentVoiceMessage!); isRecognizing.value = true; // 滚动到底部 _scrollToBottom(animate: true); } else { // 更新现有的临时语音消息 final index = messages.indexWhere((msg) => msg.timestamp == _currentVoiceMessage!.timestamp && msg.role == _currentVoiceMessage!.role); if (index != -1) { messages[index] = Message( role: 'user', content: '🎤 $text', timestamp: _currentVoiceMessage!.timestamp, ); // 滚动到底部 _scrollToBottom(animate: false); } } } void handleVoiceResult(String text) { if (text.isEmpty) return; Message userMessage; // 处理现有的临时语音消息 if (_currentVoiceMessage != null) { final index = messages.indexWhere((msg) => msg.timestamp == _currentVoiceMessage!.timestamp && msg.role == _currentVoiceMessage!.role); if (index != -1) { // 更新消息,移除麦克风图标 userMessage = Message( role: 'user', content: text, timestamp: _currentVoiceMessage!.timestamp, ); messages[index] = userMessage; } else { // 创建新消息 userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); } } else { // 创建新消息 userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); } // 保存聊天历史 _saveChatHistory(); // 滚动到底部 _scrollToBottom(animate: true); // 重置状态 _currentVoiceMessage = null; isRecognizing.value = false; // 处理AI响应 _processAIResponse(userMessage); } // 处理AI响应 Future _processAIResponse(Message userMessage) async { if (_isDisposed) return; // TTS会话状态 bool ttsSessionStarted = false; const speaker = 'zh_female_shuangkuaisisi_moon_bigtts'; // 创建一个标志,用于跟踪流是否应该继续处理 bool shouldContinueProcessing = true; // 创建监听器,用于检测用户交互 StreamSubscription? voiceInputSubscription; StreamSubscription? voicePanelSubscription; try { isLoading.value = true; // 创建助手消息 currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); // 准备TTS会话 if (isTtsEnabled.value) { await _ttsService.endSession(); if (!_ttsService.isConnected.value) { await _ttsService.connect(); } ttsSessionStarted = await _ttsService.startSession(speaker); } // 设置监听器,检测用户是否开始说话 voiceInputSubscription = isRecording.listen((isRecordingNow) { if (isRecordingNow) { // 用户开始说话,设置标志为false shouldContinueProcessing = false; // 如果TTS正在播放,立即停止 if (_isTtsSpeaking) { try { _ttsService.stop(); _isTtsSpeaking = false; } catch (e) { debugPrint('停止TTS播放失败: $e'); } } } }); // 设置监听器,检测用户是否打开语音输入面板 voicePanelSubscription = isVoiceInputVisible.listen((isVisible) { if (isVisible) { // 用户打开语音输入面板,设置标志为false shouldContinueProcessing = false; // 如果TTS正在播放,立即停止 if (_isTtsSpeaking) { try { _ttsService.stop(); _isTtsSpeaking = false; } catch (e) { debugPrint('停止TTS播放失败: $e'); } } } }); // 处理AI流式响应 await for (final chunk in _aiService.sendMessageStream( messages: messages.map((m) => {'role': m.role, 'content': m.content}).toList(), systemPrompt: systemPrompt, )) { // 检查是否应该继续处理 if (_isDisposed || !shouldContinueProcessing) { // 用户开始说话或控制器已销毁,中止流处理 break; } // 更新UI currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); // 直接将文本片段发送到TTS接口 if (isTtsEnabled.value && ttsSessionStarted && chunk.isNotEmpty) { await _ttsService.speak(chunk, speaker: speaker); } } // 结束TTS会话 if (isTtsEnabled.value && ttsSessionStarted) { await _ttsService.endSession(); } // 保存聊天历史 if (!_isDisposed) { await _saveChatHistory(); } } catch (e) { // 错误处理 if (!_isDisposed) { if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } Get.snackbar('Error', 'Failed to get response from AI', snackPosition: SnackPosition.BOTTOM); // 结束TTS会话 if (isTtsEnabled.value && ttsSessionStarted) { await _ttsService.endSession(); } } } finally { // 取消监听器 voiceInputSubscription?.cancel(); voicePanelSubscription?.cancel(); // 重置状态 if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; isLoading.value = false; } } } // 播放文本(用于独立播放场景) Future _speakText(String text) async { if (text.isEmpty || !isTtsEnabled.value) return; try { if (!_ttsService.isConnected.value) { await _ttsService.connect(); } const speaker = 'zh_female_shuangkuaisisi_moon_bigtts'; await _ttsService.startSession(speaker); await _ttsService.speak(text, speaker: speaker); await _ttsService.endSession(); } catch (e) { debugPrint('TTS播放出错: $e'); await _ttsService.endSession(); } } // 确保滚动到底部 void _ensureScrollToBottom() { _scrollToBottom(); // 延迟滚动以确保视图已构建 Future.delayed(const Duration(milliseconds: 100), () { if (!_isDisposed) _scrollToBottom(animate: true); }); // 帧回调确保在渲染后滚动 WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isDisposed) _scrollToBottom(animate: true); }); } // 滚动监听器 void _scrollListener() { if (_isDisposed) return; // 检测是否接近底部 if (scrollController.hasClients) { final maxScroll = scrollController.position.maxScrollExtent; final currentScroll = scrollController.offset; isAtBottom.value = (maxScroll - currentScroll) < 50; } } @override void onInit() { super.onInit(); messageController = TextEditingController(); scrollController = ScrollController(); // 清理可能存在的语音输入控制器 if (Get.isRegistered()) { try { Get.find().stopRecording(); Get.delete(); } catch (e) { debugPrint('清理语音控制器失败: $e'); } } // 初始化服务和数据 _initTtsService(); _initFromArguments(); _initializeController(); // 添加滚动监听器 scrollController.addListener(_scrollListener); // 确保滚动到底部 _ensureScrollToBottom(); } void _initFromArguments() { try { final arguments = Get.arguments; if (arguments == null) { throw Exception('No arguments provided to ChatController'); } final args = arguments as Map; agentId = args['agentId'] as String? ?? ''; agentName = args['agentName'] as String? ?? 'Assistant'; agentAvatar = args['agentAvatar'] as String? ?? 'assets/images/default_avatar.png'; agentSubtitle = args['agentSubtitle'] as String?; systemPrompt = args['systemPrompt'] as String? ?? ''; welcomeMessage = args['welcomeMessage'] as String? ?? 'Hello! How can I help you today?'; playVoiceOnEnter = args['playVoiceOnEnter'] as bool? ?? false; if (agentId.isEmpty) { throw Exception('Invalid agent ID'); } } catch (e) { debugPrint('🔴 Error initializing ChatController: $e'); Get.back(); // 返回上一页 Get.snackbar( 'Error', 'Failed to initialize chat: Invalid parameters', snackPosition: SnackPosition.BOTTOM, ); } } void _initTtsService() { _ttsService = Get.find(); isTtsEnabled.value = true; // 设置TTS事件监听 _setupTtsEventListener(); } void _setupTtsEventListener() { _ttsEventSubscription = _ttsService.eventStream.listen(_handleTtsEvent); } void _handleTtsEvent(Map event) { if (_isDisposed) return; final String eventType = event['eventType'] as String? ?? 'unknown'; // 根据事件类型更新TTS状态 switch (eventType) { case 'ttsSentenceStart': case 'playbackStarted': case 'synthesisBegin': _isTtsSpeaking = true; break; case 'ttsSentenceEnd': case 'playbackCompleted': case 'synthesisEnd': case 'sessionFinished': case 'connectionFinished': case 'connectionFailed': case 'sessionFailed': case 'playbackError': _isTtsSpeaking = false; break; } } Future _initializeController() async { try { await _loadChatHistory(); await _showWelcomeMessages(); } catch (e) { Get.snackbar( 'Error', 'Failed to initialize chat: $e', snackPosition: SnackPosition.BOTTOM, ); } } Future _showWelcomeMessages() async { if (!agentName.isNotEmpty || !playVoiceOnEnter) return; try { // 生成动态问候语 await _generatePersonalizedGreeting(); await _saveChatHistory(); _scrollToBottom(animate: true); // 添加系统消息 messages.add(Message( role: 'system', content: '已为您自动启用连续语音对话模式,您可以直接开始说话', timestamp: DateTime.now(), )); await _saveChatHistory(); // 启动语音输入 Future.delayed(const Duration(milliseconds: 300), () { if (!_isDisposed) { startVoiceInput(); } }); } catch (e) { debugPrint('显示欢迎消息失败: $e'); // 播放静态欢迎消息 if (isTtsEnabled.value) { _speakText(welcomeMessage); } // 仍然启动语音输入 if (!_isDisposed) { Future.delayed(const Duration(milliseconds: 300), () { startVoiceInput(); }); } } } Future _generatePersonalizedGreeting() async { if (_isDisposed) return; try { // 创建助手消息 currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); // 构建问候语请求 final greetingMessages = [ {'role': 'system', 'content': systemPrompt}, {'role': 'user', 'content': '请用一句简短的话向我打个招呼,要体现你的特点,不要超过18个字。'}, ]; // 生成问候语 String fullGreeting = ''; await for (final chunk in _aiService.sendMessageStream( messages: greetingMessages, systemPrompt: systemPrompt, )) { if (_isDisposed) break; // 更新消息内容 currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); fullGreeting += chunk; } // 播放问候语 if (isTtsEnabled.value && fullGreeting.isNotEmpty) { await _speakText(fullGreeting); } } catch (e) { debugPrint('生成问候语失败: $e'); if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; } } } @override void onClose() { _isDisposed = true; // 停止语音输入和TTS stopVoiceInput(); // 确保TTS完全停止并清理资源 if (isTtsEnabled.value) { try { _ttsService.stop(); // 停止当前TTS播放并清除未播放的数据 _ttsService.endSession(); // 确保断开TTS连接,释放所有资源 _ttsService.disconnect(); } catch (e) { debugPrint('关闭TTS服务时出错: $e'); } } // 取消TTS事件订阅 if (_ttsEventSubscription != null) { _ttsEventSubscription!.cancel(); _ttsEventSubscription = null; } // 重置状态 currentStreamMessage.value = ''; _currentAssistantMessage = null; _isTtsSpeaking = false; // 释放控制器 messageController.dispose(); scrollController.dispose(); super.onClose(); } Future _loadChatHistory() async { try { final historyJson = _storage.read('${_storagePrefix}$agentId'); if (historyJson != null) { final List historyList = json.decode(historyJson); messages.value = historyList.map((item) => Message.fromJson(item)).toList(); // 如果有历史消息,等待下一帧再滚动到底部 if (messages.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(animate: true); }); } } } catch (e) { Get.snackbar( 'Error', 'Failed to load chat history: $e', snackPosition: SnackPosition.BOTTOM, ); } } Future _saveChatHistory() async { try { final historyJson = json.encode(messages.map((msg) => msg.toJson()).toList()); await _storage.write('${_storagePrefix}$agentId', historyJson); } catch (e) { Get.snackbar( 'Error', 'Failed to save chat history: $e', snackPosition: SnackPosition.BOTTOM, ); } } void _scrollToBottom({bool animate = false}) { if (!scrollController.hasClients) return; final position = scrollController.position.maxScrollExtent; if (animate) { scrollController.animateTo( position, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } else { scrollController.jumpTo(position); } } void _updateAssistantMessage(String content) { if (_currentAssistantMessage == null) return; final index = messages.indexWhere((msg) => msg.timestamp == _currentAssistantMessage?.timestamp && msg.role == _currentAssistantMessage?.role); if (index != -1) { // 使用非空断言,因为我们已经在方法开始时检查了非空 final timestamp = _currentAssistantMessage!.timestamp; messages[index] = Message( role: 'assistant', content: content, timestamp: timestamp, ); // 只在非初始化阶段滚动 if (!playVoiceOnEnter || content.isNotEmpty) { _scrollToBottom(animate: false); } } } void clearMessage() { if (!_isDisposed && messageController.text.isNotEmpty) { messageController.clear(); } } Future sendMessage() async { if (_isDisposed) return; final text = messageController.text.trim(); if (text.isEmpty) return; // 创建用户消息 final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); clearMessage(); await _saveChatHistory(); _scrollToBottom(animate: true); // 处理AI响应 await _processAIResponse(userMessage); } Future clearChat() async { try { messages.clear(); await _storage.remove('${_storagePrefix}$agentId'); await _showWelcomeMessages(); } catch (e) { Get.snackbar( 'Error', 'Failed to clear chat: $e', snackPosition: SnackPosition.BOTTOM, ); } } void toggleTTS() { // 切换TTS启用状态 isTtsEnabled.toggle(); if (!isTtsEnabled.value) { _ttsService.endSession(); } } /// 生成简单的问候语 Future generateSimpleGreeting() async { if (_isDisposed) return; try { // 创建助手消息 currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); // 构建用于生成问候语的消息列表 final greetingMessages = [ {'role': 'system', 'content': systemPrompt}, {'role': 'user', 'content': '请用一句简短的话向我打个招呼,要体现你的特点,不要超过12个字。'}, ]; // 生成问候语 String fullGreeting = ''; await for (final chunk in _aiService.sendMessageStream( messages: greetingMessages, systemPrompt: systemPrompt, )) { if (_isDisposed) break; // 更新消息内容 currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); fullGreeting += chunk; } // 播放问候语 if (isTtsEnabled.value && fullGreeting.isNotEmpty) { await _speakText(fullGreeting); } } catch (e) { debugPrint('生成问候语失败: $e'); // 移除未完成的消息 if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; } } } // 开始按住说话 void startPressToTalk() { isRecordingVoice.value = true; startVoiceInput(); } // 结束按住说话 void endPressToTalk() { isRecordingVoice.value = false; stopVoiceInput(); } }