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_service.dart'; import '../models/message_model.dart'; import '../controllers/voice_input_controller.dart'; 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 VolcanoTtsService _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(); // 用于存储当前正在流式生成的消息 final RxString currentStreamMessage = ''.obs; Message? _currentAssistantMessage; String _pendingTtsText = ''; static const int _minTtsLength = 20; bool _isDisposed = false; // 语音输入相关状态 final isRecording = false.obs; final recordingText = ''.obs; final isVoiceInputVisible = false.obs; final isVoiceConnecting = true.obs; final isVoiceMuted = false.obs; Message? _currentVoiceMessage; // 输入框状态 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; } // 辅助方法:管理TTS状态 void _manageTtsState(bool enable) { if (!enable && _ttsService.isEnabled.value) { // 需要禁用TTS _ttsService.stop(); // 先停止当前播放 _ttsService.isEnabled.value = false; } else if (enable && !_ttsService.isEnabled.value) { // 需要启用TTS _ttsService.isEnabled.value = true; } } // 开始语音输入 void startVoiceInput() { // 确保先停止任何可能正在进行的语音识别会话 if (Get.isRegistered()) { final voiceController = Get.find(); voiceController.stopRecording(); Get.delete(); } // 不再需要禁用TTS,因为已启用回声消除 isVoiceInputVisible.value = true; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; // 当语音面板打开时,确保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, )); } void startRecording() { if (!isVoiceConnecting.value && isVoiceInputVisible.value) { // 不再需要禁用TTS,因为已启用回声消除 isRecording.value = true; } } void stopRecording() { if (isRecording.value) { isRecording.value = false; } } void toggleMute() { isVoiceMuted.value = !isVoiceMuted.value; } void stopVoiceInput() { // 确保停止语音识别 if (Get.isRegistered()) { try { final voiceController = Get.find(); voiceController.stopRecording(); } catch (e) { debugPrint('Error stopping voice recording: $e'); } } isVoiceInputVisible.value = false; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; // 不再需要恢复TTS状态,因为已启用回声消除 // 如果存在临时语音消息但没有实际内容,则移除它 if (_currentVoiceMessage != null && _currentVoiceMessage!.content == '🎤 ...') { messages.remove(_currentVoiceMessage); } _currentVoiceMessage = null; // 当语音面板关闭时,确保ListView滚动回适当位置 WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isDisposed && scrollController.hasClients && 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.isNotEmpty) { // 更新识别中的文本,但不创建消息 recordingText.value = text; } } void handleVoiceResult(String text) { if (text.isNotEmpty) { // 创建一个新的用户消息 final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); // 添加到消息列表 messages.add(userMessage); // 保存聊天历史 _saveChatHistory(); // 滚动到底部 _scrollToBottom(animate: true); // 重新启用TTS,以便AI回复时可以播放 _manageTtsState(true); // 发送给AI处理 _processAIResponse(userMessage); } } // 处理AI响应 Future _processAIResponse(Message userMessage) async { if (_isDisposed) return; try { isLoading.value = true; currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); _pendingTtsText = ''; await for (final chunk in _aiService.sendMessageStream( messages: messages .map((m) => { 'role': m.role, 'content': m.content, }) .toList(), systemPrompt: systemPrompt, )) { if (_isDisposed) break; currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); // 累积文本并合成 _pendingTtsText += chunk; if (!_isDisposed && _ttsService.isEnabled.value) { // 检查是否达到最小长度 if (_pendingTtsText.length >= _minTtsLength) { // 找到最后一个句子结束的位置 int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText); if (lastSentenceEnd > 0) { // 播放到最后一个句子结束的位置 String textToSpeak = _pendingTtsText.substring(0, lastSentenceEnd + 1); _ttsService.speak(textToSpeak); // 保留剩余的文本 _pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1); } } } } // 处理剩余的文本 if (!_isDisposed && _ttsService.isEnabled.value && _pendingTtsText.isNotEmpty) { _ttsService.speak(_pendingTtsText); } _pendingTtsText = ''; if (!_isDisposed) { await _saveChatHistory(); } } catch (e) { if (!_isDisposed) { if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } Get.snackbar( 'Error', 'Failed to get response from AI: $e', snackPosition: SnackPosition.BOTTOM, ); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; isLoading.value = false; } } } // 确保滚动到底部的方法,使用多种策略确保成功 void _ensureScrollToBottom() { // 立即尝试滚动 _scrollToBottom(); // 延迟100ms后再次尝试滚动(等待视图构建) Future.delayed(const Duration(milliseconds: 100), () { if (!_isDisposed) _scrollToBottom(animate: true); }); // 延迟500ms后再次尝试滚动(确保所有元素都已加载) Future.delayed(const Duration(milliseconds: 500), () { 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 { final voiceController = Get.find(); voiceController.stopRecording(); Get.delete(); } catch (e) { debugPrint('Error cleaning up existing VoiceInputController: $e'); } } _initTtsService(); _initFromArguments(); _initializeController(); // 添加滚动监听器 scrollController.addListener(_scrollListener); // 确保在页面加载完成后滚动到底部 _ensureScrollToBottom(); // 添加帧回调,确保在页面完全渲染后滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { // 延迟执行,确保页面已完全加载 Future.delayed(const Duration(milliseconds: 300), () { if (!_isDisposed) { _scrollToBottom(animate: true); } }); }); } 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(); _ttsService.isEnabled.value = true; } Future _initializeController() async { try { await _loadChatHistory(); await _showWelcomeMessages(); } catch (e) { Get.snackbar( 'Error', 'Failed to initialize chat: $e', snackPosition: SnackPosition.BOTTOM, ); } } Future _showWelcomeMessages() async { try { if (messages.isEmpty && agentName.isNotEmpty && playVoiceOnEnter) { // 只有 playVoiceOnEnter 为 true 时才生成动态问候语 await _generatePersonalizedGreeting(); // 保存聊天记录并立即滚动到底部 await _saveChatHistory(); _scrollToBottom(animate: true); } } catch (e) { print('显示欢迎消息失败: $e'); // 如果生成问候语失败,确保至少显示静态欢迎消息 if (_ttsService.isEnabled.value) { _ttsService.speak(welcomeMessage); } } } Future _generatePersonalizedGreeting() async { if (_isDisposed) return; String fullGreeting = ''; try { currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); // 构建用于生成个性化问候语的消息列表 final greetingMessages = [ {'role': 'system', 'content': systemPrompt}, { 'role': 'user', 'content': '请根据你的身份和性格,用一句简短的话向我打个招呼,要体现你的特点,不要超过18个字。' }, ]; await for (final chunk in _aiService.sendMessageStream( messages: greetingMessages, systemPrompt: systemPrompt, )) { if (_isDisposed) break; // 更新消息内容 currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); fullGreeting += chunk; } // 完整问候语生成完毕后,一次性播放 if (!_isDisposed && _ttsService.isEnabled.value && fullGreeting.isNotEmpty) { await _ttsService.speak(fullGreeting); } } catch (e) { print('生成问候语失败: $e'); // 移除未完成的消息 if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; } } } @override void onClose() { _isDisposed = true; // 确保语音输入被停止 stopVoiceInput(); // 先停止所有正在进行的操作 _ttsService.stop(); currentStreamMessage.value = ''; _currentAssistantMessage = null; _pendingTtsText = ''; // 最后再处理控制器的销毁 try { messageController.dispose(); } catch (e) { debugPrint('Error disposing messageController: $e'); } try { scrollController.dispose(); } catch (e) { debugPrint('Error disposing scrollController: $e'); } 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) { if (animate) { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } else { scrollController.jumpTo(scrollController.position.maxScrollExtent); } } else { // 如果 scrollController 还没有附加到 ListView,延迟执行 Future.delayed(const Duration(milliseconds: 100), () { if (!_isDisposed) { _scrollToBottom(animate: animate); } }); } } void _updateAssistantMessage(String content) { if (_currentAssistantMessage != null) { final index = messages.indexWhere((msg) => msg.timestamp == _currentAssistantMessage?.timestamp && msg.role == _currentAssistantMessage?.role); if (index != -1) { messages[index] = Message( role: 'assistant', content: content, timestamp: _currentAssistantMessage!.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; try { final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); clearMessage(); await _saveChatHistory(); _scrollToBottom(animate: true); isLoading.value = true; try { currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); _pendingTtsText = ''; await for (final chunk in _aiService.sendMessageStream( messages: messages .map((m) => { 'role': m.role, 'content': m.content, }) .toList(), systemPrompt: systemPrompt, )) { if (_isDisposed) break; currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); // 累积文本并合成 _pendingTtsText += chunk; if (!_isDisposed && _ttsService.isEnabled.value) { // 检查是否达到最小长度 if (_pendingTtsText.length >= _minTtsLength) { // 找到最后一个句子结束的位置 int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText); if (lastSentenceEnd > 0) { // 播放到最后一个句子结束的位置 String textToSpeak = _pendingTtsText.substring(0, lastSentenceEnd + 1); _ttsService.speak(textToSpeak); // 保留剩余的文本 _pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1); } } } } // 处理剩余的文本 if (!_isDisposed && _ttsService.isEnabled.value && _pendingTtsText.isNotEmpty) { _ttsService.speak(_pendingTtsText); } _pendingTtsText = ''; if (!_isDisposed) { await _saveChatHistory(); } } catch (e) { if (!_isDisposed) { if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } Get.snackbar( 'Error', 'Failed to get response from AI: $e', snackPosition: SnackPosition.BOTTOM, ); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; isLoading.value = false; } } } catch (e) { print('发送消息失败: $e'); } } int _findLastSentenceEnd(String text) { final sentenceEnds = [ text.lastIndexOf('。'), text.lastIndexOf('!'), text.lastIndexOf('?'), text.lastIndexOf('.'), text.lastIndexOf('!'), text.lastIndexOf('?'), ]; return sentenceEnds.reduce((max, pos) => pos > max ? pos : max); } 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() { // 使用 VolcanoTtsService 的 toggleEnabled 方法 _ttsService.toggleEnabled(); if (!_ttsService.isEnabled.value) { _ttsService.stop(); } } /// 生成简单的问候语 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 (!_isDisposed && _ttsService.isEnabled.value && fullGreeting.isNotEmpty) { await _ttsService.speak(fullGreeting); } } catch (e) { print('生成问候语失败: $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(); } }