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'; class ChatController extends GetxController { final String agentId; final String agentName; final String agentAvatar; final String? agentSubtitle; final String systemPrompt; final String welcomeMessage; final VolcanoAIService _aiService; late final VolcanoTtsService _ttsService; static const String _storagePrefix = 'chat_history_'; final messages = [].obs; final isLoading = false.obs; final textController = TextEditingController(); final scrollController = ScrollController(); final _storage = GetStorage(); // 用于存储当前正在流式生成的消息 final RxString currentStreamMessage = ''.obs; Message? _currentAssistantMessage; String _pendingTtsText = ''; // 用于累积TTS文本 static const int _minTtsLength = 20; // TTS最小文本长度 bool _isDisposed = false; // 添加销毁标记 ChatController({ required this.agentId, required this.agentName, required this.agentAvatar, this.agentSubtitle, required this.systemPrompt, required this.welcomeMessage, }) : _aiService = VolcanoAIService() { _initTtsService(); } void _initTtsService() { try { _ttsService = Get.find(); } catch (_) { // 如果找不到实例,创建一个新的 _ttsService = Get.put(VolcanoTtsService(), permanent: true); } // 默认开启TTS _ttsService.isEnabled.value = true; } @override void onInit() async { super.onInit(); await _initializeController(); } Future _initializeController() async { try { await _loadChatHistory(); // 如果没有聊天记录,显示欢迎消息并生成个性化问候语 if (messages.isEmpty) { // 添加欢迎消息 final initialMessage = Message( role: 'assistant', content: welcomeMessage, timestamp: DateTime.now(), ); messages.add(initialMessage); // 生成并添加个性化问候语 try { currentStreamMessage.value = ''; _currentAssistantMessage = Message( role: 'assistant', content: '', timestamp: DateTime.now(), ); messages.add(_currentAssistantMessage!); await for (final chunk in _aiService.generateGreeting( agentName: agentName, systemPrompt: systemPrompt, )) { if (_isDisposed) break; currentStreamMessage.value += chunk; _updateAssistantMessage(currentStreamMessage.value); // 累积文本用于TTS if (!_isDisposed && _ttsService.isEnabled.value) { _ttsService.speak(chunk); } } // 保存聊天记录 if (!_isDisposed) { await _saveChatHistory(); // 延迟滚动以确保布局完成 Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); } } catch (e) { print('生成问候语失败: $e'); // 如果生成问候语失败,移除空消息并只播放欢迎消息 if (_currentAssistantMessage != null) { messages.remove(_currentAssistantMessage); } if (_ttsService.isEnabled.value) { _ttsService.speak(welcomeMessage); } } finally { if (!_isDisposed) { _currentAssistantMessage = null; currentStreamMessage.value = ''; } } } } catch (e) { Get.snackbar( 'Error', 'Failed to initialize chat: $e', snackPosition: SnackPosition.BOTTOM, ); } } @override void onClose() { _isDisposed = true; // 标记为已销毁 textController.dispose(); scrollController.dispose(); _ttsService.stop(); // 停止当前播放 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(); } } 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.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } } void _updateAssistantMessage(String content) { if (_currentAssistantMessage != null) { final index = messages.indexWhere((msg) => msg.timestamp == _currentAssistantMessage!.timestamp && msg.role == 'assistant' ); if (index != -1) { messages[index] = Message( role: 'assistant', content: content, timestamp: _currentAssistantMessage!.timestamp, ); _scrollToBottom(); } } } void sendMessage(String text) async { if (text.trim().isEmpty) return; final userMessage = Message( role: 'user', content: text, timestamp: DateTime.now(), ); messages.add(userMessage); textController.clear(); 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; } } } 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}'); // 重新添加欢迎消息 final initialMessage = Message( role: 'assistant', content: welcomeMessage, timestamp: DateTime.now(), ); messages.add(initialMessage); await _saveChatHistory(); // 播放欢迎消息 if (_ttsService.isEnabled.value) { _ttsService.speak(welcomeMessage); } } catch (e) { Get.snackbar( 'Error', 'Failed to clear chat: $e', snackPosition: SnackPosition.BOTTOM, ); } } void toggleTTS() { _ttsService.toggleEnabled(); } }