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 '../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; 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(); _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; } 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, )); } void startRecording() { if (!isVoiceConnecting.value && isVoiceInputVisible.value) { isRecording.value = true; } } void stopRecording() { if (isRecording.value) { isRecording.value = false; } } void toggleMute() { isVoiceMuted.value = !isVoiceMuted.value; } void stopVoiceInput() { isVoiceInputVisible.value = false; isVoiceConnecting.value = true; isRecording.value = false; recordingText.value = ''; // 如果存在临时语音消息但没有实际内容,则移除它 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; } }