import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:get_storage/get_storage.dart'; import 'dart:convert'; import '../../../data/services/volcano_ai_service.dart'; import '../../../data/services/volcano_tts_service.dart'; import '../models/message_model.dart'; import '../../../data/services/voice_recognition_service.dart'; import '../controllers/voice_input_controller.dart'; import 'package:flutter/services.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; late final VoiceRecognitionService _recognitionService; 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; @override void onInit() { super.onInit(); messageController = TextEditingController(); scrollController = ScrollController(); _initTtsService(); _initVoiceRecognitionService(); _initFromArguments(); _initializeController(); // 延迟生成问候语,确保页面已完全加载 if (playVoiceOnEnter) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isDisposed) { generateSimpleGreeting(); } }); } } 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; } void _initVoiceRecognitionService() { _recognitionService = Get.find(); _setupVoiceRecognitionCallbacks(); } void _setupVoiceRecognitionCallbacks() { _recognitionService.onRecognitionResult = (result) { if (isVoiceMuted.value) return; recordingText.value = result; handleRecognizing(result); }; _recognitionService.onError = (error) { // 不要立即显示错误,先尝试重新连接 _handleRecognitionError(error); }; _recognitionService.onRecognitionComplete = () { stopRecording(); if (!isVoiceMuted.value && recordingText.value.isNotEmpty) { handleVoiceResult(recordingText.value); } }; _recognitionService.onConnectionStatusChanged = (status) { if (status == 'disconnected' && isRecording.value) { // 如果在录音过程中断开连接,尝试重新连接 _handleWebSocketDisconnection(); } }; // 添加句子完成的回调 _recognitionService.onSentenceComplete = (text) { if (!isVoiceMuted.value && text.isNotEmpty) { handleVoiceResult(text); // 继续录音,等待下一句话 if (isVoiceInputVisible.value) { startRecording(); } } }; } Future _handleRecognitionError(String error) async { // 如果是权限相关错误,请求权限 if (error.toLowerCase().contains('permission')) { try { const platform = MethodChannel('com.example.deep_voice/audio'); await platform.invokeMethod('requestAudioPermissions'); // 权限请求后重试 if (isVoiceInputVisible.value) { await Future.delayed(const Duration(seconds: 1)); startRecording(); } return; } catch (e) { debugPrint('Failed to request permissions: $e'); } } // 如果是连接错误,尝试重新连接 if (error.toLowerCase().contains('connection') || error.toLowerCase().contains('websocket')) { _handleWebSocketDisconnection(); return; } // 其他错误显示给用户 Get.snackbar( 'Error', '语音识别失败: $error', snackPosition: SnackPosition.BOTTOM, ); stopVoiceInput(); } Future _handleWebSocketDisconnection() async { if (!isVoiceInputVisible.value) return; isVoiceConnecting.value = true; stopRecording(); // 保存当前的语音文本 final currentText = recordingText.value; try { // 等待一段时间后重试 await Future.delayed(const Duration(seconds: 2)); if (!isVoiceInputVisible.value) return; // 重新初始化语音识别 await _recognitionService.onInit(); // 恢复之前的状态 if (currentText.isNotEmpty) { recordingText.value = currentText; } isVoiceConnecting.value = false; if (isVoiceInputVisible.value) { startRecording(); } } catch (e) { debugPrint('Failed to reconnect: $e'); Get.snackbar( 'Error', '重新连接失败,请稍后再试', snackPosition: SnackPosition.BOTTOM, ); stopVoiceInput(); } } 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(); } } 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(); }); } } } 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() { if (scrollController.hasClients) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } } 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(); } } } } 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(); 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() { _ttsService.toggleEnabled(); } /// 生成简单的问候语 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 startVoiceInput() { isVoiceInputVisible.value = true; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; // 创建语音输入控制器 Get.put(VoiceInputController( onRecordingResult: handleVoiceResult, onClosePanel: stopVoiceInput, onRecognizing: handleRecognizing, )); // 初始化语音识别 _initializeVoiceRecognition(); } Future _initializeVoiceRecognition() async { try { await _recognitionService.onInit(); isVoiceConnecting.value = false; if (isVoiceInputVisible.value) { startRecording(); } } catch (e) { debugPrint('Failed to initialize voice recognition: $e'); Get.snackbar( 'Error', '初始化语音识别失败: $e', snackPosition: SnackPosition.BOTTOM, ); stopVoiceInput(); } } void startRecording() { if (!isVoiceConnecting.value && isVoiceInputVisible.value) { isRecording.value = true; _recognitionService.startRecognition(); } } void stopRecording() { if (isRecording.value) { isRecording.value = false; _recognitionService.stopRecognition(); } } void toggleMute() { isVoiceMuted.value = !isVoiceMuted.value; } void stopVoiceInput() { isVoiceInputVisible.value = false; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; // 停止语音识别服务 _recognitionService.stopRecognition(); // 如果存在临时语音消息但没有实际内容,则移除它 if (_currentVoiceMessage != null && _currentVoiceMessage!.content == '🎤 ...') { messages.remove(_currentVoiceMessage); } _currentVoiceMessage = null; // 删除语音输入控制器 try { if (Get.isRegistered()) { Get.delete(); } } catch (e) { debugPrint('Error deleting VoiceInputController: $e'); } } void handleRecognizing(String text) { if (text.isNotEmpty) { // 如果还没有创建临时消息,创建一个 if (_currentVoiceMessage == null) { _currentVoiceMessage = Message( role: 'user', content: '🎤 $text', timestamp: DateTime.now(), ); messages.add(_currentVoiceMessage!); } else { // 更新已存在的临时消息 final index = messages.indexOf(_currentVoiceMessage!); if (index != -1) { messages[index] = Message( role: 'user', content: '🎤 $text', timestamp: _currentVoiceMessage!.timestamp, ); } } _scrollToBottom(); } } void handleVoiceResult(String text) { if (text.isNotEmpty) { if (_currentVoiceMessage != null) { final index = messages.indexOf(_currentVoiceMessage!); if (index != -1) { messages[index] = Message( role: 'user', content: text, timestamp: _currentVoiceMessage!.timestamp, ); } } // 发送消息 messageController.text = text; sendMessage(); messageController.clear(); _scrollToBottom(); } // 不再在这里停止语音输入,让用户可以继续说下一句 _currentVoiceMessage = null; } }