2025-08-18 11:26:58 +08:00
|
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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';
|
2025-08-15 14:48:29 +08:00
|
|
|
|
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';
|
2025-09-29 18:33:46 +08:00
|
|
|
|
import '../services/chat_sse_service.dart' as Service;
|
2025-08-12 13:36:42 +08:00
|
|
|
|
import '../services/control_recognition_service.dart';
|
2025-08-15 14:48:29 +08:00
|
|
|
|
// 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';
|
|
|
|
|
|
|
2025-09-29 18:33:46 +08:00
|
|
|
|
import 'platform_tts_service.dart';
|
|
|
|
|
|
|
|
|
|
|
|
const aliSdkChannelName = 'com.example.ai_chat_assistant/ali_sdk';
|
|
|
|
|
|
|
2025-09-18 15:53:39 +08:00
|
|
|
|
// 用单例的模式创建
|
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');
|
2025-08-15 14:48:29 +08:00
|
|
|
|
|
2025-09-18 15:53:39 +08:00
|
|
|
|
static MessageService? _instance;
|
2025-08-12 13:36:42 +08:00
|
|
|
|
|
2025-09-18 15:53:39 +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;
|
|
|
|
|
|
|
2025-08-15 14:48:29 +08:00
|
|
|
|
MessageService._internal() {
|
2025-09-18 15:53:39 +08:00
|
|
|
|
// 注册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":
|
2025-09-30 11:43:40 +08:00
|
|
|
|
{
|
|
|
|
|
|
debugPrint("ASR 结果: ${call.arguments}");
|
|
|
|
|
|
replaceMessage(
|
|
|
|
|
|
id: _latestUserMessageId!, text: call.arguments, status: MessageStatus.normal);
|
|
|
|
|
|
break;
|
2025-09-18 15:53:39 +08:00
|
|
|
|
}
|
2025-09-30 11:43:40 +08:00
|
|
|
|
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-09-18 15:53:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-15 14:48:29 +08:00
|
|
|
|
}
|
2025-08-12 13:36:42 +08:00
|
|
|
|
|
2025-09-30 14:38:03 +08:00
|
|
|
|
final ChatSseService _chatSseService = ChatSseService(PlatformTtsService(aliSdkChannelName));
|
|
|
|
|
|
// final Service.ChatSseService _chatSseService = Service.ChatSseService();
|
2025-09-29 18:33:46 +08:00
|
|
|
|
|
2025-08-13 16:00:23 +08:00
|
|
|
|
// final LocalTtsService _ttsService = LocalTtsService();
|
2025-08-15 14:48:29 +08:00
|
|
|
|
// final AudioRecorderService _audioService = AudioRecorderService();
|
|
|
|
|
|
// final VoiceRecognitionService _recognitionService = VoiceRecognitionService();
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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(
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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;
|
2025-08-15 14:48:29 +08:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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";
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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) {
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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;
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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) {
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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) {
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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 {
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-18 15:53:39 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
2025-08-16 14:28:15 +08:00
|
|
|
|
|
|
|
|
|
|
void removeNonListeningMessages() {
|
|
|
|
|
|
_messages.removeWhere((message) => message.status != MessageStatus.listening);
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
}
|
2025-08-12 13:36:42 +08:00
|
|
|
|
}
|