You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
14 KiB
498 lines
14 KiB
import 'dart:async';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import '../../core/utils/logger.dart';
|
|
import '../providers/agent_provider.dart';
|
|
import '../models/agent.dart';
|
|
import '../../modules/chat/controllers/chat_controller.dart';
|
|
import '../../modules/chat/models/message_model.dart';
|
|
import 'volcano_asr_api_service.dart';
|
|
import 'volcano_ai_service.dart';
|
|
import 'volcano_tts_api_service.dart';
|
|
import 'chat_history_service.dart';
|
|
|
|
/// 后台代理服务,用于在应用处于后台时处理AI交互
|
|
class BackgroundAgentService extends GetxService {
|
|
// 单例实例
|
|
static BackgroundAgentService? _instance;
|
|
|
|
// 单例工厂方法
|
|
factory BackgroundAgentService() {
|
|
_instance ??= BackgroundAgentService._internal();
|
|
return _instance!;
|
|
}
|
|
|
|
// 私有构造函数
|
|
BackgroundAgentService._internal() {
|
|
Logger.info('BackgroundAgentService 已创建');
|
|
}
|
|
|
|
// 服务实例
|
|
final VolcanoAsrApiService _asrService = Get.find<VolcanoAsrApiService>();
|
|
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
|
|
final VolcanoTtsApiService _ttsService = Get.find<VolcanoTtsApiService>();
|
|
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
|
|
|
|
// 状态控制
|
|
final RxBool isProcessing = false.obs;
|
|
final RxBool isListening = false.obs;
|
|
|
|
// 语音识别状态
|
|
final RxString recognizedText = ''.obs;
|
|
|
|
// 流式响应控制
|
|
StreamSubscription? _recognitionSubscription;
|
|
StreamSubscription? _aiResponseSubscription;
|
|
|
|
// 定时器,用于检测无活动超时
|
|
Timer? _inactivityTimer;
|
|
|
|
// 当前处理的Agent
|
|
Agent? _currentAgent;
|
|
|
|
// 消息历史 - 与ChatController共享
|
|
List<Map<String, String>> get messageHistory {
|
|
// 首先尝试从ChatController获取消息历史
|
|
if (Get.isRegistered<ChatController>()) {
|
|
final chatController = Get.find<ChatController>();
|
|
// 检查ChatController是否正在与personal_assistant交互
|
|
if (chatController.agent?.id == 'personal_assistant') {
|
|
return chatController.messages.map((m) => {
|
|
'role': m.role,
|
|
'content': m.content,
|
|
}).toList();
|
|
}
|
|
}
|
|
|
|
// 如果无法从ChatController获取,则使用本地存储的历史记录
|
|
if (_localMessageHistory.isEmpty) {
|
|
_loadChatHistory();
|
|
}
|
|
|
|
return _localMessageHistory;
|
|
}
|
|
|
|
// 本地消息历史备份
|
|
final List<Map<String, String>> _localMessageHistory = [];
|
|
|
|
// TTS是否正在播放,直接从VolcanoTtsApiService获取
|
|
bool get isTtsSpeaking => _ttsService.isPlaying;
|
|
|
|
@override
|
|
void onClose() {
|
|
// 取消所有订阅
|
|
_recognitionSubscription?.cancel();
|
|
_aiResponseSubscription?.cancel();
|
|
|
|
// 停止TTS并结束会话
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
_ttsService.endSession();
|
|
}
|
|
|
|
// 停止语音识别
|
|
if (isListening.value) {
|
|
_asrService.stopRecognition();
|
|
}
|
|
|
|
// 取消定时器
|
|
_inactivityTimer?.cancel();
|
|
|
|
super.onClose();
|
|
}
|
|
|
|
/// 开始代理交互
|
|
///
|
|
/// [agentId] 要交互的代理ID
|
|
Future<void> startAgentInteraction(String agentId) async {
|
|
// 先说"我在听",然后再播放欢迎消息
|
|
const listeningMessage = "我在听";
|
|
_ttsService.speakOnce(listeningMessage);
|
|
|
|
// 防止重复处理
|
|
if (isProcessing.value) {
|
|
Logger.warning('已经在处理交互,忽略此次请求');
|
|
return;
|
|
}
|
|
|
|
isProcessing.value = true;
|
|
|
|
try {
|
|
// 获取Agent
|
|
final agent = AgentProvider.getAgentById(agentId);
|
|
if (agent == null) {
|
|
Logger.error('无法获取Agent: $agentId');
|
|
isProcessing.value = false;
|
|
return;
|
|
}
|
|
|
|
_currentAgent = agent;
|
|
|
|
// 加载该Agent的聊天历史
|
|
_loadChatHistory();
|
|
|
|
// 启动语音识别 - 保持持续识别状态
|
|
await _startVoiceRecognition();
|
|
|
|
// 启动无活动定时器
|
|
_startInactivityTimer();
|
|
|
|
} catch (e) {
|
|
Logger.error('启动代理交互失败', e);
|
|
isProcessing.value = false;
|
|
}
|
|
}
|
|
|
|
/// 停止语音识别
|
|
Future<void> _stopVoiceRecognition() async {
|
|
if (isListening.value) {
|
|
isListening.value = false;
|
|
|
|
// 取消订阅
|
|
await _recognitionSubscription?.cancel();
|
|
_recognitionSubscription = null;
|
|
|
|
// 停止语音识别
|
|
_asrService.stopRecognition();
|
|
|
|
// 重置状态
|
|
recognizedText.value = '';
|
|
}
|
|
}
|
|
|
|
/// 开始语音识别
|
|
Future<void> _startVoiceRecognition() async {
|
|
if (isListening.value) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 启动语音识别
|
|
final success = await _asrService.startRecognition();
|
|
|
|
if (success) {
|
|
isListening.value = true;
|
|
|
|
// 监听识别结果
|
|
_recognitionSubscription = _asrService.recognitionStream.listen(
|
|
(event) {
|
|
switch (event.type) {
|
|
case RecognitionEventType.recognizing:
|
|
// 实时更新识别文本
|
|
if (event.text.isNotEmpty) {
|
|
recognizedText.value = event.text;
|
|
Logger.info('处理语音识别中间结果: $event.text');
|
|
// 只有当TTS正在播放或AI响应流正在接收时才中断
|
|
if (_ttsService.isPlaying || _aiResponseSubscription != null) {
|
|
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
|
|
}
|
|
}
|
|
break;
|
|
case RecognitionEventType.finalResult:
|
|
// 最终结果
|
|
if (event.text.isNotEmpty) {
|
|
recognizedText.value = event.text;
|
|
Logger.info('处理语音识别最终结果: $event.text');
|
|
// 处理识别到的文本
|
|
_processRecognizedText(event.text);
|
|
|
|
// 重置识别文本
|
|
recognizedText.value = '';
|
|
}
|
|
break;
|
|
case RecognitionEventType.error:
|
|
// 错误处理
|
|
Logger.info('语音识别错误: ${event.error}');
|
|
|
|
// 不再尝试重新启动语音识别,而是直接停止交互
|
|
Logger.info('语音识别出错,停止后台代理交互');
|
|
stopInteraction();
|
|
break;
|
|
case RecognitionEventType.started:
|
|
default:
|
|
// 不做任何处理
|
|
break;
|
|
}
|
|
},
|
|
onError: (error) {
|
|
Logger.info('语音识别错误:$error');
|
|
|
|
// 不再尝试重新启动语音识别,而是直接停止交互
|
|
Logger.info('语音识别流错误,停止后台代理交互');
|
|
stopInteraction();
|
|
},
|
|
);
|
|
} else {
|
|
Logger.error('启动语音识别失败');
|
|
}
|
|
} catch (e) {
|
|
Logger.error('启动语音识别失败', e);
|
|
isListening.value = false;
|
|
isProcessing.value = false;
|
|
}
|
|
}
|
|
|
|
/// 中断当前响应
|
|
void _interruptCurrentResponse(String reason) {
|
|
Logger.info(reason);
|
|
|
|
// 停止TTS并结束会话
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
_ttsService.endSession();
|
|
}
|
|
|
|
// 取消AI响应流订阅
|
|
if (_aiResponseSubscription != null) {
|
|
_aiResponseSubscription!.cancel();
|
|
_aiResponseSubscription = null;
|
|
|
|
// 记录中断事件
|
|
Logger.info('已中断AI响应流和TTS播放');
|
|
}
|
|
}
|
|
|
|
/// 处理识别到的文本
|
|
Future<void> _processRecognizedText(String text) async {
|
|
if (text.trim().isEmpty || _currentAgent == null) return;
|
|
|
|
try {
|
|
// 在处理新的文本前,确保中断任何正在进行的响应
|
|
_interruptCurrentResponse("处理新的用户输入,中断当前响应");
|
|
|
|
// 添加用户消息到历史记录
|
|
_addMessageToHistory('user', text);
|
|
|
|
// 重置取消标志
|
|
String fullResponse = '';
|
|
|
|
// 启动TTS会话,使用当前Agent的voice
|
|
await _ttsService.startSession(_currentAgent!.voice);
|
|
|
|
// 获取AI响应流
|
|
final responseStream = _aiService.sendMessageStream(
|
|
messages: messageHistory,
|
|
systemPrompt: _currentAgent!.systemPrompt,
|
|
);
|
|
|
|
// 订阅响应流
|
|
_aiResponseSubscription = responseStream.listen(
|
|
(chunk) {
|
|
fullResponse += chunk;
|
|
|
|
// 发送到TTS
|
|
_ttsService.speak(chunk);
|
|
|
|
// 重置无活动定时器
|
|
_resetInactivityTimer();
|
|
},
|
|
onDone: () {
|
|
// 添加AI响应到历史记录
|
|
_addMessageToHistory('assistant', fullResponse);
|
|
|
|
// 结束TTS会话
|
|
_ttsService.endSession();
|
|
|
|
// 清除AI响应流订阅引用
|
|
_aiResponseSubscription = null;
|
|
|
|
// 注意:我们不再在这里重启语音识别,因为它应该已经在运行
|
|
},
|
|
onError: (error) {
|
|
Logger.error('AI响应错误', error);
|
|
|
|
// 结束TTS会话
|
|
_ttsService.endSession();
|
|
|
|
// 播放错误消息
|
|
const errorMessage = '抱歉,我遇到了一些问题,无法回应您的请求。';
|
|
_ttsService.speakOnce(errorMessage);
|
|
|
|
// 添加错误消息到历史记录
|
|
_addMessageToHistory('assistant', errorMessage);
|
|
|
|
// 清除AI响应流订阅引用
|
|
_aiResponseSubscription = null;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
Logger.error('处理识别文本失败', e);
|
|
|
|
// 结束TTS会话
|
|
_ttsService.endSession();
|
|
|
|
// 播放错误消息
|
|
const errorMessage = '抱歉,我遇到了一些问题,无法回应您的请求。';
|
|
_ttsService.speakOnce(errorMessage);
|
|
|
|
// 添加错误消息到历史记录
|
|
_addMessageToHistory('assistant', errorMessage);
|
|
}
|
|
}
|
|
|
|
/// 重新启动语音识别(仅在识别会话意外终止时使用)
|
|
Future<void> _restartVoiceRecognition() async {
|
|
// 停止当前的语音识别
|
|
if (isListening.value) {
|
|
await _asrService.stopRecognition();
|
|
isListening.value = false;
|
|
|
|
// 取消当前的订阅
|
|
_recognitionSubscription?.cancel();
|
|
_recognitionSubscription = null;
|
|
}
|
|
|
|
// 等待一段时间,确保资源被正确释放
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
|
|
// 重新启动语音识别
|
|
await _startVoiceRecognition();
|
|
}
|
|
|
|
/// 添加消息到历史记录
|
|
void _addMessageToHistory(String role, String content) {
|
|
// 添加到本地历史记录
|
|
_localMessageHistory.add({
|
|
'role': role,
|
|
'content': content,
|
|
});
|
|
|
|
// 保持本地历史记录不超过10条消息
|
|
if (_localMessageHistory.length > 10) {
|
|
_localMessageHistory.removeAt(0);
|
|
}
|
|
|
|
// 如果ChatController可用,同步消息到ChatController
|
|
if (Get.isRegistered<ChatController>()) {
|
|
final chatController = Get.find<ChatController>();
|
|
if (chatController.agent?.id == 'personal_assistant') {
|
|
// 向ChatController添加消息
|
|
final message = Message(
|
|
role: role,
|
|
content: content,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
chatController.addMessage(message);
|
|
}
|
|
} else {
|
|
// 如果ChatController不可用,保存到本地存储
|
|
_saveChatHistory();
|
|
}
|
|
}
|
|
|
|
// 加载聊天历史
|
|
void _loadChatHistory() {
|
|
if (_currentAgent == null) return;
|
|
|
|
try {
|
|
// 加载聊天历史
|
|
final messages = _historyService.loadHistory(_currentAgent!.id);
|
|
|
|
// 清空本地历史,然后添加加载的历史
|
|
_localMessageHistory.clear();
|
|
|
|
// 将Message转换为Map格式
|
|
if (messages.isNotEmpty) {
|
|
for (final message in messages) {
|
|
_localMessageHistory.add({
|
|
'role': message.role,
|
|
'content': message.content,
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('加载聊天历史记录失败', e);
|
|
}
|
|
}
|
|
|
|
// 保存聊天历史
|
|
void _saveChatHistory() {
|
|
if (_currentAgent == null) return;
|
|
|
|
try {
|
|
// 将本地历史转换为Message列表
|
|
final messages = _localMessageHistory.map((m) => Message(
|
|
role: m['role']!,
|
|
content: m['content']!,
|
|
timestamp: DateTime.now(),
|
|
)).toList();
|
|
|
|
// 保存到ChatHistoryService
|
|
_historyService.saveHistory(_currentAgent!.id, messages);
|
|
} catch (e) {
|
|
Logger.error('保存聊天历史记录失败', e);
|
|
}
|
|
}
|
|
|
|
/// 启动无活动定时器
|
|
void _startInactivityTimer() {
|
|
_inactivityTimer?.cancel();
|
|
_inactivityTimer = Timer(Duration(seconds: 30), () {
|
|
// 如果30秒内没有TTS输出或用户说话,停止语音识别
|
|
if (!_ttsService.isPlaying && isProcessing.value) {
|
|
Logger.info('检测到30秒内无活动,停止语音识别');
|
|
stopInteraction();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 重置无活动定时器
|
|
void _resetInactivityTimer() {
|
|
if (_inactivityTimer != null) {
|
|
_inactivityTimer!.cancel();
|
|
_startInactivityTimer();
|
|
}
|
|
}
|
|
|
|
/// 响应新的媒体事件(双击媒体按钮)
|
|
Future<void> respondToNewMediaEvent() async {
|
|
// 不管当前状态如何,总是中断当前响应
|
|
_interruptCurrentResponse("检测到新的媒体事件,中断当前响应");
|
|
|
|
// 回答"我在听"
|
|
const listeningMessage = "我在听";
|
|
await _ttsService.speakOnce(listeningMessage);
|
|
|
|
// 如果识别会话已经终止,确保重新启动
|
|
if (!isListening.value) {
|
|
await _restartVoiceRecognition();
|
|
}
|
|
|
|
// 重置无活动定时器
|
|
_resetInactivityTimer();
|
|
}
|
|
|
|
/// 停止当前交互
|
|
Future<void> stopInteraction() async {
|
|
try {
|
|
// 保存聊天历史
|
|
_saveChatHistory();
|
|
|
|
// 取消所有订阅
|
|
_recognitionSubscription?.cancel();
|
|
_aiResponseSubscription?.cancel();
|
|
|
|
// 停止TTS并结束会话
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
_ttsService.endSession();
|
|
}
|
|
|
|
// 停止语音识别
|
|
if (isListening.value) {
|
|
await _stopVoiceRecognition();
|
|
}
|
|
|
|
// 取消定时器
|
|
_inactivityTimer?.cancel();
|
|
|
|
// 重置状态
|
|
isProcessing.value = false;
|
|
_currentAgent = null;
|
|
|
|
Logger.info('已停止后台代理交互');
|
|
} catch (e) {
|
|
Logger.error('停止交互失败', e);
|
|
}
|
|
}
|
|
}
|