import 'dart:async'; import 'package:ai_chat_assistant/ai_chat_assistant.dart'; import 'package:ai_chat_assistant/utils/common_util.dart'; import 'package:ai_chat_assistant/utils/tts_util.dart'; import 'package:t_basic_intl/intl.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uuid/uuid.dart'; import '../services/chat_sse_service.dart'; import '../services/classification_service.dart'; import '../services/control_recognition_service.dart'; // import '../services/audio_recorder_service.dart'; // import '../services/voice_recognition_service.dart'; import 'command_service.dart'; import 'package:fluttertoast/fluttertoast.dart'; // 用单例的模式创建 class MessageService extends ChangeNotifier { static const MethodChannel _asrChannel = MethodChannel('com.example.ai_chat_assistant/ali_sdk'); static MessageService? _instance; static MessageService get instance { return _instance ??= MessageService._internal(); } // 提供工厂构造函数供 Provider 使用 factory MessageService() => instance; Completer? _asrCompleter; MessageService._internal() { // 注册MethodChannel的handler _asrChannel.setMethodCallHandler(_handleMethodCall); } @override void dispose() { // 取消注册MethodChannel的handler,避免内存泄漏和意外回调 _asrChannel.setMethodCallHandler(null); _asrCompleter?.completeError('Disposed'); _asrCompleter = null; super.dispose(); } // 提供真正的销毁方法(可选,用于应用退出时) void destroyInstance() { _asrChannel.setMethodCallHandler(null); _asrCompleter?.completeError('Destroyed'); _asrCompleter = null; super.dispose(); _instance = null; } Future _handleMethodCall(MethodCall call) async { switch (call.method) { case "onAsrResult": replaceMessage( id: _latestUserMessageId!, text: call.arguments, status: MessageStatus.normal); break; case "onAsrStop": int index = findMessageIndexById(_latestUserMessageId!); if (index == -1) { return; } final message = _messages[index]; if (message.text.isEmpty) { removeMessageById(_latestUserMessageId!); return; } if (_asrCompleter != null && !_asrCompleter!.isCompleted) { _asrCompleter!.complete(messages.last.text); } break; } } final ChatSseService _chatSseService = ChatSseService(); // final LocalTtsService _ttsService = LocalTtsService(); // final AudioRecorderService _audioService = AudioRecorderService(); // final VoiceRecognitionService _recognitionService = VoiceRecognitionService(); final TextClassificationService _classificationService = TextClassificationService(); final VehicleCommandService _vehicleCommandService = VehicleCommandService(); final List _messages = []; List get messages => List.unmodifiable(_messages); String? _latestUserMessageId; String? _latestAssistantMessageId; MessageServiceState _state = MessageServiceState.idle; bool get isRecording => _state == MessageServiceState.recording; bool get isRecognizing => _state == MessageServiceState.recognizing; bool get isReplying => _state == MessageServiceState.replying; bool get isBusy => _state != MessageServiceState.idle; bool _isReplyAborted = true; void initializeEmpty() { _messages.clear(); notifyListeners(); } Future startVoiceInput() async { if (await Permission.microphone.status == PermissionStatus.denied) { PermissionStatus status = await Permission.microphone.request(); if (status == PermissionStatus.permanentlyDenied) { await Fluttertoast.showToast( msg: Intl.getCurrentLocale().startsWith('zh') ? "请在设置中开启麦克风权限" : "Please enable microphone in the settings"); } return; } if (_state != MessageServiceState.idle) { return; } try { abortReply(); _latestUserMessageId = null; _latestAssistantMessageId = null; changeState(MessageServiceState.recording); _latestUserMessageId = addMessage("", true, MessageStatus.listening); _asrChannel.invokeMethod("startAsr"); } catch (e) { print('录音开始出错: $e'); } } void changeState(MessageServiceState state) { _state = state; notifyListeners(); } String addMessage(String text, bool isUser, MessageStatus status) { String uuid = Uuid().v1(); _messages.add(ChatMessage( id: uuid, text: text, isUser: isUser, timestamp: DateTime.now(), status: status)); notifyListeners(); return uuid; } Future stopAndProcessVoiceInput() async { if (_state != MessageServiceState.recording) { changeState(MessageServiceState.idle); return; } try { _isReplyAborted = false; changeState(MessageServiceState.recognizing); _asrChannel.invokeMethod("stopAsr"); _asrCompleter = Completer(); final recognizedText = await _asrCompleter!.future; // final audioData = await _audioService.stopRecording(); // replaceMessage( // id: _latestUserMessageId!, // text: "", // status: MessageStatus.recognizing); // if (audioData == null || audioData.isEmpty) { // removeMessageById(_latestUserMessageId!); // return; // } // final recognizedText = // await _recognitionService.recognizeSpeech(audioData); // if (recognizedText == null || recognizedText.isEmpty) { // removeMessageById(_latestUserMessageId!); // return; // } // replaceMessage( // id: _latestUserMessageId!, // text: recognizedText, // status: MessageStatus.normal); changeState(MessageServiceState.replying); await reply(recognizedText); } catch (e) { print(e); } finally { changeState(MessageServiceState.idle); _asrCompleter = null; } } int findMessageIndexById(String? id) { return id == null ? -1 : _messages.indexWhere((msg) => msg.id == id); } void replaceMessage({ required String id, String? text, MessageStatus? status, }) { final index = findMessageIndexById(id); if (index == -1) { return; } final message = _messages[index]; _messages[index] = ChatMessage( id: message.id, text: text ?? message.text, isUser: message.isUser, timestamp: message.timestamp, status: status ?? message.status, ); notifyListeners(); } Future reply(String text) async { _latestAssistantMessageId = addMessage("", false, MessageStatus.thinking); bool isChinese = CommonUtil.containChinese(text); try { if (_isReplyAborted) { return; } final category = await _classificationService.classifyText(text); switch (category) { case -1: await occurError(isChinese); break; case 2: await handleVehicleControl(text, isChinese); break; case 4: await answerWrongQuestion(isChinese); break; default: await answerQuestion(text, isChinese); break; } } catch (e) { await occurError(isChinese); } } Future handleVehicleControl(String text, bool isChinese) async { if (_isReplyAborted) { return; } final vehicleCommandResponse = await _vehicleCommandService.getCommandFromText(text); if (vehicleCommandResponse == null) { if (_isReplyAborted) { return; } String msg = isChinese ? "无法识别车辆控制命令,请重试" : "Cannot recognize the vehicle control command, please try again"; replaceMessage(id: _latestAssistantMessageId!, text: msg, status: MessageStatus.normal); } else { if (_isReplyAborted) { return; } replaceMessage( id: _latestAssistantMessageId!, text: vehicleCommandResponse.tips!, status: MessageStatus.executing); if (!_isReplyAborted) { if (await TtsUtil.start(isChinese) == true) { TtsUtil.send(vehicleCommandResponse.tips!); Future.delayed(const Duration(milliseconds: 300), () => TtsUtil.complete()); } } bool containOpenAC = false; List successCommandList = []; for (var command in vehicleCommandResponse.commands) { if (_isReplyAborted) { return; } if (command.type == VehicleCommandType.unknown || command.error.isNotEmpty) { continue; } if (command.type == VehicleCommandType.openAC) { containOpenAC = true; } bool isSuccess; if (containOpenAC && command.type == VehicleCommandType.changeACTemp) { isSuccess = await Future.delayed( const Duration(milliseconds: 2000), () => processCommand(command, isChinese)); } else { isSuccess = await processCommand(command, isChinese); } if (isSuccess) { successCommandList.add(isChinese ? command.type.chinese : command.type.english); } } replaceMessage( id: _latestAssistantMessageId!, text: vehicleCommandResponse.tips!, status: MessageStatus.normal); notifyListeners(); if (_isReplyAborted || successCommandList.isEmpty) { return; } String controlResponse = await _vehicleCommandService.getControlResponse(successCommandList); if (_isReplyAborted || controlResponse.isEmpty) { return; } addMessage(controlResponse, false, MessageStatus.normal); } } Future processCommand(VehicleCommand command, bool isChinese) async { String msg = ""; MessageStatus status = MessageStatus.normal; var commandObjc = VehicleCommand(type: command.type, params: command.params); var client = AIChatAssistantManager.instance.commandClient; if (client == null) { msg = isChinese ? "车辆控制服务未初始化" : "Vehicle control service is not initialized"; status = MessageStatus.failure; addMessage(msg, false, status); return false; } final (isSuccess, result) = await client.executeCommand(commandObjc); if (command.type == VehicleCommandType.locateCar) { msg = isChinese ? "很抱歉,定位车辆位置失败" : "Sorry, locate the vehicle unsuccessfully"; status = MessageStatus.failure; if (isSuccess && result != null && result.isNotEmpty) { String address = result['address']; if (address.isNotEmpty) { msg = isChinese ? "已为您找到车辆位置:\"$address\"" : "The vehicle location is \"$address\""; status = MessageStatus.success; } } } else { if (!isSuccess) { msg = isChinese ? "很抱歉,\"${command.type.chinese}\"失败" : "Sorry, execute \"${command.type.english}\" unsuccessfully"; status = MessageStatus.failure; } } if (msg.isNotEmpty) { addMessage(msg, false, status); } return isSuccess; } Future answerWrongQuestion(bool isChinese) async { if (_isReplyAborted) { return; } const chineseAnswer = "尊敬的ID.UNYX车主:\n" + "很抱歉,我暂时无法理解您的需求,请您更详细地描述,以便我能更好地为您服务。\n" + "作为您的智能助手,我专注于以下服务:\n" + "1. ID.UNYX车型功能咨询,例如:如何调节座椅记忆功能?\n" + "2. 日常用车问题解答,例如:充电注意事项有哪些?\n" + "3. 基础车辆控制协助,例如:帮我打开空调。"; const englishAnswer = "Dear ID.UNYX Owner,\n" + "I'm sorry that unable to understand your request currently. Please describe it in more detail so that I can better assist you.\n" + "As your intelligent assistant, I specialize in the following services:\n" + "1. ID.UNYX model feature consultation, such as: How do I adjust the seat memory function?\n" + "2. Daily vehicle usage inquiries, such as: What are the charging precautions?\n" + "3. Basic vehicle control assistance, such as: Help me turn on the air conditioning."; replaceMessage( id: _latestAssistantMessageId!, text: isChinese ? chineseAnswer : englishAnswer, status: MessageStatus.normal); } Future answerQuestion(String text, isChinese) async { if (_isReplyAborted) { return; } _chatSseService.request( messageId: _latestAssistantMessageId!, text: text, isChinese: isChinese, onStreamResponse: handleStreamResponse, ); } Future occurError(bool isChinese) async { if (_isReplyAborted) { return; } replaceMessage( id: _latestAssistantMessageId!, text: isChinese ? "很抱歉,众众暂时无法回答这个问题,请重试" : "Sorry, I can not answer this question for the moment, please try again", status: MessageStatus.normal); } void handleStreamResponse(String messageId, String responseText, bool isComplete) { if (_isReplyAborted) { return; } try { if (isComplete) { replaceMessage( id: messageId, text: responseText, status: MessageStatus.completed, ); } else { replaceMessage(id: messageId, text: responseText, status: MessageStatus.thinking); } } catch (e) { abortReply(); } } Future abortReply() async { _isReplyAborted = true; _chatSseService.abort(); int index = findMessageIndexById(_latestAssistantMessageId); if (index == -1 || messages[index].status != MessageStatus.thinking) { return; } replaceMessage(id: _latestAssistantMessageId!, status: MessageStatus.aborted); } void removeMessageById(String id) { _messages.removeWhere((msg) => msg.id == id); notifyListeners(); } void clearMessages() { _messages.clear(); notifyListeners(); } void removeNonListeningMessages() { _messages.removeWhere((message) => message.status != MessageStatus.listening); notifyListeners(); } }