Files
ai_chat_assistant/lib/services/message_service.dart

443 lines
15 KiB
Dart
Raw Permalink Normal View History

2025-08-18 11:26:58 +08:00
import 'dart:async';
import 'package:ai_chat_assistant/ai_chat_assistant.dart';
2025-08-12 13:36:42 +08:00
import 'package:ai_chat_assistant/utils/common_util.dart';
2025-08-15 09:37:53 +08:00
import 'package:ai_chat_assistant/utils/tts_util.dart';
2025-09-24 15:42:30 +08:00
import 'package:t_basic_intl/intl.dart';
2025-08-12 13:36:42 +08:00
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
2025-08-12 13:36:42 +08:00
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
import '../services/chat_sse_service.dart' as Service;
2025-08-12 13:36:42 +08:00
import '../services/control_recognition_service.dart';
// import '../services/audio_recorder_service.dart';
// import '../services/voice_recognition_service.dart';
2025-08-12 13:36:42 +08:00
import 'package:fluttertoast/fluttertoast.dart';
import 'platform_tts_service.dart';
const aliSdkChannelName = 'com.example.ai_chat_assistant/ali_sdk';
// 用单例的模式创建
2025-08-12 13:36:42 +08:00
class MessageService extends ChangeNotifier {
2025-09-17 15:09:05 +08:00
static const MethodChannel _asrChannel = MethodChannel('com.example.ai_chat_assistant/ali_sdk');
static MessageService? _instance;
2025-08-12 13:36:42 +08:00
static MessageService get instance {
return _instance ??= MessageService._internal();
}
// 提供工厂构造函数供 Provider 使用
factory MessageService() => instance;
2025-08-12 13:36:42 +08:00
2025-08-18 11:26:58 +08:00
Completer<String>? _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<dynamic> _handleMethodCall(MethodCall call) async {
switch (call.method) {
case "onAsrResult":
{
debugPrint("ASR 结果: ${call.arguments}");
replaceMessage(
id: _latestUserMessageId!, text: call.arguments, status: MessageStatus.normal);
break;
}
case "onAsrStop":
{
debugPrint("ASR 停止: ${call.arguments}");
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;
}
}
}
2025-08-12 13:36:42 +08:00
final ChatSseService _chatSseService = ChatSseService(PlatformTtsService(aliSdkChannelName));
// final Service.ChatSseService _chatSseService = Service.ChatSseService();
2025-08-13 16:00:23 +08:00
// final LocalTtsService _ttsService = LocalTtsService();
// final AudioRecorderService _audioService = AudioRecorderService();
// final VoiceRecognitionService _recognitionService = VoiceRecognitionService();
final TextClassificationService _classificationService = TextClassificationService();
2025-08-12 13:36:42 +08:00
final VehicleCommandService _vehicleCommandService = VehicleCommandService();
2025-08-13 15:17:13 +08:00
final List<ChatMessage> _messages = [];
2025-08-12 13:36:42 +08:00
List<ChatMessage> 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;
2025-08-13 15:17:13 +08:00
void initializeEmpty() {
_messages.clear();
notifyListeners();
}
2025-08-12 13:36:42 +08:00
Future<void> 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);
2025-08-22 17:35:02 +08:00
_latestUserMessageId = addMessage("", true, MessageStatus.listening);
_asrChannel.invokeMethod("startAsr");
2025-08-12 13:36:42 +08:00
} catch (e) {
2025-09-30 14:48:12 +08:00
Logger.e('录音开始出错: $e');
2025-08-12 13:36:42 +08:00
}
}
void changeState(MessageServiceState state) {
_state = state;
notifyListeners();
}
2025-08-22 17:35:02 +08:00
String addMessage(String text, bool isUser, MessageStatus status) {
String uuid = Uuid().v1();
2025-08-12 13:36:42 +08:00
_messages.add(ChatMessage(
id: uuid, text: text, isUser: isUser, timestamp: DateTime.now(), status: status));
2025-08-12 13:36:42 +08:00
notifyListeners();
2025-08-22 17:35:02 +08:00
return uuid;
2025-08-12 13:36:42 +08:00
}
Future<void> stopAndProcessVoiceInput() async {
if (_state != MessageServiceState.recording) {
changeState(MessageServiceState.idle);
return;
}
try {
2025-09-17 15:09:05 +08:00
_isReplyAborted = false;
2025-08-12 13:36:42 +08:00
changeState(MessageServiceState.recognizing);
2025-08-22 17:35:02 +08:00
_asrChannel.invokeMethod("stopAsr");
2025-08-18 11:26:58 +08:00
_asrCompleter = Completer<String>();
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);
2025-08-12 13:36:42 +08:00
changeState(MessageServiceState.replying);
await reply(recognizedText);
} catch (e) {
2025-09-30 14:48:12 +08:00
Logger.e(e.toString());
2025-08-12 13:36:42 +08:00
} finally {
changeState(MessageServiceState.idle);
2025-08-18 11:26:58 +08:00
_asrCompleter = null;
2025-08-12 13:36:42 +08:00
}
}
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<void> reply(String text) async {
2025-08-22 17:35:02 +08:00
_latestAssistantMessageId = addMessage("", false, MessageStatus.thinking);
2025-08-12 13:36:42 +08:00
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<void> handleVehicleControl(String text, bool isChinese) async {
if (_isReplyAborted) {
return;
}
final vehicleCommandResponse = await _vehicleCommandService.getCommandFromText(text);
2025-08-12 13:36:42 +08:00
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);
2025-08-12 13:36:42 +08:00
} else {
if (_isReplyAborted) {
return;
}
replaceMessage(
id: _latestAssistantMessageId!,
text: vehicleCommandResponse.tips!,
status: MessageStatus.executing);
2025-08-15 09:37:53 +08:00
if (!_isReplyAborted) {
if (await TtsUtil.start(isChinese) == true) {
TtsUtil.send(vehicleCommandResponse.tips!);
2025-08-22 17:35:02 +08:00
Future.delayed(const Duration(milliseconds: 300), () => TtsUtil.complete());
2025-08-15 09:37:53 +08:00
}
}
2025-08-12 13:36:42 +08:00
bool containOpenAC = false;
2025-08-22 17:35:02 +08:00
List<String> successCommandList = [];
2025-08-12 13:36:42 +08:00
for (var command in vehicleCommandResponse.commands) {
if (_isReplyAborted) {
return;
}
if (command.type == VehicleCommandType.unknown || command.error.isNotEmpty) {
2025-08-12 13:36:42 +08:00
continue;
}
if (command.type == VehicleCommandType.openAC) {
containOpenAC = true;
}
2025-08-22 17:35:02 +08:00
bool isSuccess;
2025-08-12 13:36:42 +08:00
if (containOpenAC && command.type == VehicleCommandType.changeACTemp) {
isSuccess = await Future.delayed(
const Duration(milliseconds: 2000), () => processCommand(command, isChinese));
2025-08-12 13:36:42 +08:00
} else {
2025-08-22 17:35:02 +08:00
isSuccess = await processCommand(command, isChinese);
}
if (isSuccess) {
2025-09-17 15:09:05 +08:00
successCommandList.add(isChinese ? command.type.chinese : command.type.english);
2025-08-12 13:36:42 +08:00
}
}
replaceMessage(
id: _latestAssistantMessageId!,
text: vehicleCommandResponse.tips!,
status: MessageStatus.normal);
notifyListeners();
2025-08-22 17:35:02 +08:00
if (_isReplyAborted || successCommandList.isEmpty) {
return;
}
String controlResponse = await _vehicleCommandService.getControlResponse(successCommandList);
2025-08-22 17:35:02 +08:00
if (_isReplyAborted || controlResponse.isEmpty) {
return;
}
addMessage(controlResponse, false, MessageStatus.normal);
2025-08-12 13:36:42 +08:00
}
}
2025-08-22 17:35:02 +08:00
Future<bool> 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);
2025-08-12 13:36:42 +08:00
if (command.type == VehicleCommandType.locateCar) {
msg = isChinese ? "很抱歉,定位车辆位置失败" : "Sorry, locate the vehicle unsuccessfully";
2025-08-12 13:36:42 +08:00
status = MessageStatus.failure;
if (isSuccess && result != null && result.isNotEmpty) {
String address = result['address'];
if (address.isNotEmpty) {
msg = isChinese ? "已为您找到车辆位置:\"$address\"" : "The vehicle location is \"$address\"";
2025-08-12 13:36:42 +08:00
status = MessageStatus.success;
}
}
} else {
2025-08-22 17:35:02 +08:00
if (!isSuccess) {
2025-08-12 13:36:42 +08:00
msg = isChinese
? "很抱歉,\"${command.type.chinese}\"失败"
: "Sorry, execute \"${command.type.english}\" unsuccessfully";
status = MessageStatus.failure;
}
}
2025-08-22 17:35:02 +08:00
if (msg.isNotEmpty) {
addMessage(msg, false, status);
}
return isSuccess;
2025-08-12 13:36:42 +08:00
}
Future<void> 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<void> answerQuestion(String text, isChinese) async {
if (_isReplyAborted) {
return;
}
_chatSseService.request(
messageId: _latestAssistantMessageId!,
text: text,
2025-08-15 09:37:53 +08:00
isChinese: isChinese,
2025-08-12 13:36:42 +08:00
onStreamResponse: handleStreamResponse,
);
}
Future<void> 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);
}
2025-08-15 09:37:53 +08:00
void handleStreamResponse(String messageId, String responseText, bool isComplete) {
2025-08-12 13:36:42 +08:00
if (_isReplyAborted) {
return;
}
try {
if (isComplete) {
replaceMessage(
id: messageId,
text: responseText,
status: MessageStatus.completed,
);
} else {
replaceMessage(id: messageId, text: responseText, status: MessageStatus.thinking);
2025-08-12 13:36:42 +08:00
}
} catch (e) {
abortReply();
}
}
Future<void> 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);
2025-08-12 13:36:42 +08:00
}
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();
}
2025-08-12 13:36:42 +08:00
}