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.

671 lines
23 KiB

import 'package:get/get.dart';
import 'dart:async';
import 'volcano_ai_service.dart';
import 'volcano_tts_service.dart';
import 'voice_recognition_service.dart';
import '../../modules/chat/models/message_model.dart';
class BackgroundAgentService extends GetxService {
static BackgroundAgentService get to => Get.find();
final VolcanoAIService _aiService;
final VolcanoTtsService _ttsService;
final VoiceRecognitionService _voiceRecognitionService;
final List<Message> _messageHistory = [];
String _pendingTtsText = '';
static const int _minTtsLength = 20;
bool _isProcessing = false;
bool _isListening = false;
StreamSubscription? _recognitionSubscription;
// 可观察的状态
final RxBool isListening = false.obs;
final RxString recognizedText = ''.obs;
// 添加一个标志,表示是否已经识别到语音
bool _hasRecognizedSpeech = false;
// 添加一个标志,表示是否应该继续循环交互
bool _shouldContinueInteraction = false;
// 添加一个 Completer 用于在识别到最终结果时完成
Completer<String>? _recognitionCompleter;
// 添加一个标志,表示是否已经收到最终结果
bool _hasFinalResult = false;
// 添加一个计时器,用于在一段时间没有新的识别结果时提交当前结果
Timer? _silenceTimer;
// 添加一个计时器,用于检测用户长时间没有说话
Timer? _noSpeechTimer;
// 最后一次识别到语音的时间
DateTime? _lastSpeechTime;
// 添加一个标志,表示系统是否正在播放 TTS
bool _isSpeaking = false;
// 添加一个变量来跟踪当前的AI响应流订阅
StreamSubscription? _aiResponseSubscription;
// 添加一个标志,表示是否应该取消当前的AI响应
bool _shouldCancelAiResponse = false;
// 添加一个变量来跟踪最后一次 TTS 状态变化的时间
DateTime? _lastTtsStateChangeTime;
BackgroundAgentService()
: _aiService = VolcanoAIService(),
_ttsService = Get.find<VolcanoTtsService>(),
_voiceRecognitionService = Get.find<VoiceRecognitionService>() {
// 不再监听 TTS 播放状态,而是使用定期检查
// 初始化 TTS 状态
_isSpeaking = _ttsService.isActuallyPlaying();
_lastTtsStateChangeTime = DateTime.now();
// 添加定期检查实际播放状态的计时器
Timer.periodic(const Duration(seconds: 1), (timer) {
// 检查实际播放状态
final actuallyPlaying = _ttsService.isActuallyPlaying();
// 如果状态发生变化,更新状态并记录时间
if (_isSpeaking != actuallyPlaying) {
print('TTS 播放状态变化: $_isSpeaking -> $actuallyPlaying');
_isSpeaking = actuallyPlaying;
_lastTtsStateChangeTime = DateTime.now();
// 如果 TTS 服务的 isPlaying 与实际状态不一致,尝试修正
if (actuallyPlaying != _ttsService.isPlaying.value) {
try {
_ttsService.isPlaying.value = actuallyPlaying;
print('已修正 TTS 服务的播放状态');
} catch (e) {
print('修正 TTS 播放状态失败: $e');
}
}
// 如果 TTS 停止播放,且正在进行语音识别,重新启动无语音超时计时器
if (!actuallyPlaying && _isListening && _hasRecognizedSpeech && _noSpeechTimer == null) {
_startNoSpeechTimer();
}
}
// 如果正在处理交互,进行更全面的检查
if (_isProcessing) {
_checkAndUpdateTtsState();
}
});
}
// 添加一个方法来检查和更新 TTS 状态
void _checkAndUpdateTtsState() {
try {
// 获取实际播放状态
final actuallyPlaying = _ttsService.isActuallyPlaying();
// 如果状态显示正在播放,但已经很长时间没有状态变化,强制重置
if (actuallyPlaying && _lastTtsStateChangeTime != null) {
final now = DateTime.now();
final difference = now.difference(_lastTtsStateChangeTime!);
// 如果超过15秒没有状态变化,强制重置
if (difference.inSeconds > 15) {
print('强制重置 TTS 状态:已经 ${difference.inSeconds} 秒没有状态变化');
try {
// 停止 TTS 播放
_ttsService.stop();
// 强制更新状态
_isSpeaking = false;
_ttsService.isPlaying.value = false;
// 更新最后状态变化时间
_lastTtsStateChangeTime = DateTime.now();
print('TTS 状态已强制重置');
} catch (e) {
print('强制重置 TTS 状态失败: $e');
}
}
}
} catch (e) {
print('检查 TTS 播放状态失败: $e');
}
}
// 处理蓝牙耳机按钮触发的交互
Future<void> handleAgentInteraction(String systemPrompt) async {
if (_isProcessing) {
print('已经在处理交互,忽略此次请求');
return;
}
// 设置循环交互标志为 true
_shouldContinueInteraction = true;
try {
// 确保之前的语音识别已经停止
if (_isListening) {
print('交互开始前确保语音识别已停止');
await stopVoiceRecognition();
}
// 循环进行交互,直到用户 10 秒没有说话或手动停止
while (_shouldContinueInteraction) {
await _processSingleInteraction(systemPrompt);
}
} finally {
// 确保在交互结束时停止语音识别
if (_isListening) {
await stopVoiceRecognition();
}
// 重置语音识别服务
try {
print('交互结束,重置语音识别服务');
await _voiceRecognitionService.initialize();
} catch (e) {
print('重置语音识别服务失败: $e');
}
}
}
// 启动无语音超时计时器
void _startNoSpeechTimer() {
// 检查 TTS 播放状态,使用 isActuallyPlaying 获取实际状态
_isSpeaking = _ttsService.isActuallyPlaying();
// 如果系统正在播放 TTS,不启动计时器
if (_isSpeaking) {
print('系统正在播放 TTS,不启动无语音超时计时器 (isPlaying.value: ${_ttsService.isPlaying.value}, 实际播放状态: $_isSpeaking)');
// 即使TTS正在播放,也设置一个更长的安全超时,防止系统永远卡在循环中
_noSpeechTimer?.cancel();
_noSpeechTimer = Timer(const Duration(seconds: 30), () {
// 再次检查 TTS 状态,使用 isActuallyPlaying
_isSpeaking = _ttsService.isActuallyPlaying();
print('安全超时触发:即使TTS正在播放,30秒内没有新的语音输入,强制退出循环交互 (实际播放状态: $_isSpeaking)');
if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
_recognitionCompleter!.complete('');
}
_shouldContinueInteraction = false;
});
return;
}
// 取消之前的计时器
_noSpeechTimer?.cancel();
// 设置新的计时器,如果 10 秒内没有新的识别结果,且系统没有在播放 TTS,则认为用户已经停止交互
_noSpeechTimer = Timer(const Duration(seconds: 10), () {
// 再次检查是否正在播放 TTS,使用 isActuallyPlaying
_isSpeaking = _ttsService.isActuallyPlaying();
print('无语音超时计时器触发,当前 TTS 播放状态: $_isSpeaking (isPlaying.value: ${_ttsService.isPlaying.value}, 实际播放状态: $_isSpeaking)');
if (!_isSpeaking && _isListening && _recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
print('10秒内没有新的语音输入,且系统没有在播放 TTS,退出循环交互');
_recognitionCompleter!.complete('');
// 直接设置标志,停止循环交互
_shouldContinueInteraction = false;
}
});
print('启动无语音超时计时器,10秒后检查');
}
// 处理单次交互
Future<void> _processSingleInteraction(String systemPrompt) async {
_isProcessing = true;
_hasRecognizedSpeech = false;
_hasFinalResult = false;
try {
// 强制重置 TTS 状态,确保不会因为错误的状态导致系统卡住
_resetTtsStateIfNeeded();
// 先播放一个简短的提示音或提示语,表示开始监听
try {
await _ttsService.speak("我在听");
} catch (e) {
print('播放提示音失败: $e');
// 继续执行,不要因为提示音失败而中断整个流程
}
// 开始语音识别
bool recognitionStarted = false;
String userInput = '';
try {
// 确保之前的语音识别已经停止
if (_isListening) {
print('检测到上一次语音识别未正确停止,先停止它');
await stopVoiceRecognition();
}
// 尝试启动语音识别
await startVoiceRecognition();
recognitionStarted = true;
// 创建一个 Completer 来处理语音识别完成
_recognitionCompleter = Completer<String>();
// 启动无语音超时计时器
_startNoSpeechTimer();
// 等待语音识别完成
userInput = await _recognitionCompleter!.future;
// 取消无语音超时计时器
_noSpeechTimer?.cancel();
_noSpeechTimer = null;
// 如果没有识别到语音,则停止循环交互
if (!_hasRecognizedSpeech || userInput.trim().isEmpty) {
print('没有识别到用户语音或识别结果为空,退出循环交互');
// 停止语音识别
if (_isListening) {
await stopVoiceRecognition();
}
try {
await _ttsService.speak("没有听到您说话,已退出语音交互");
} catch (e) {
print('播放退出提示失败: $e');
}
// 设置标志,停止循环交互
_shouldContinueInteraction = false;
_isProcessing = false;
return;
}
// 注意:不再停止语音识别,保持语音识别状态
// 只有在退出交互时才停止语音识别
} catch (e) {
print('语音识别过程出错: $e');
} finally {
// 清理资源,但保持语音识别状态
_recognitionCompleter = null;
_silenceTimer?.cancel();
_silenceTimer = null;
_noSpeechTimer?.cancel();
_noSpeechTimer = null;
}
if (userInput.isEmpty) {
try {
await _ttsService.speak("没有听到您说话");
} catch (_) {}
// 如果没有识别到语音,停止循环交互
_shouldContinueInteraction = false;
_isProcessing = false;
// 停止语音识别
if (_isListening) {
await stopVoiceRecognition();
}
return;
}
// 保存用户消息到历史记录
_messageHistory.add(Message(
role: 'user',
content: userInput,
timestamp: DateTime.now(),
));
// 构建用于生成回应的消息列表
final messages = [
{'role': 'system', 'content': systemPrompt},
{'role': 'user', 'content': userInput},
];
String fullResponse = '';
try {
// 重置取消标志
_shouldCancelAiResponse = false;
// 创建一个本地变量来跟踪是否已取消
bool isCancelled = false;
// 获取AI响应流
final responseStream = _aiService.sendMessageStream(
messages: messages,
systemPrompt: systemPrompt,
);
// 创建一个订阅来处理响应流
_aiResponseSubscription = responseStream.listen(
(chunk) {
// 如果已经设置了取消标志,则不处理这个块
if (_shouldCancelAiResponse) {
isCancelled = true;
return;
}
fullResponse += chunk;
// 累积文本并处理TTS
_pendingTtsText += chunk;
if (_ttsService.isEnabled.value) {
if (_pendingTtsText.length >= _minTtsLength) {
int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText);
if (lastSentenceEnd > 0) {
String textToSpeak = _pendingTtsText.substring(0, lastSentenceEnd + 1);
try {
_ttsService.speak(textToSpeak);
} catch (e) {
print('播放TTS失败: $e');
}
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1);
}
}
}
},
onError: (e) {
print('AI响应流错误: $e');
// 清理订阅
_aiResponseSubscription = null;
},
onDone: () {
// 如果已取消,不处理剩余的文本
if (!isCancelled && !_shouldCancelAiResponse) {
// 处理剩余的文本
if (_ttsService.isEnabled.value && _pendingTtsText.isNotEmpty) {
try {
_ttsService.speak(_pendingTtsText);
} catch (e) {
print('播放剩余TTS失败: $e');
}
}
}
_pendingTtsText = '';
_aiResponseSubscription = null;
}
);
// 等待响应流完成
await _aiResponseSubscription!.asFuture();
} catch (e) {
print('AI响应生成失败: $e');
// 如果AI响应失败,使用默认回复
fullResponse = '抱歉,我现在无法回答您的问题。请稍后再试。';
} finally {
// 清理资源
_aiResponseSubscription?.cancel();
_aiResponseSubscription = null;
_shouldCancelAiResponse = false;
}
// 如果响应被取消,不保存到历史记录
if (!_shouldCancelAiResponse && fullResponse.isNotEmpty) {
// 保存助手回复到历史记录
_messageHistory.add(Message(
role: 'assistant',
content: fullResponse,
timestamp: DateTime.now(),
));
}
// 限制历史记录长度
if (_messageHistory.length > 20) {
_messageHistory.removeRange(0, _messageHistory.length - 20);
}
// 短暂暂停,然后开始下一轮交互
await Future.delayed(const Duration(milliseconds: 500));
} catch (e) {
print('Background agent interaction failed: $e');
try {
await _ttsService.speak("抱歉,出现了一些问题");
} catch (_) {}
// 发生错误时停止循环交互
_shouldContinueInteraction = false;
} finally {
_isProcessing = false;
}
}
// 停止循环交互
void stopContinuousInteraction() {
_shouldContinueInteraction = false;
print('手动停止循环交互');
}
// 开始语音识别
Future<void> startVoiceRecognition() async {
if (_isListening) {
print('已经在监听中,先停止当前监听');
await stopVoiceRecognition();
}
try {
// 确保语音识别服务已初始化
if (!await _voiceRecognitionService.initialize()) {
print('语音识别服务初始化失败,尝试重新初始化');
// 尝试重新初始化
await Future.delayed(const Duration(milliseconds: 500));
if (!await _voiceRecognitionService.initialize()) {
throw Exception('无法初始化语音识别服务');
}
}
// 开始连续识别
final recognitionStream = await _voiceRecognitionService.startContinuousRecognition();
_isListening = true;
isListening.value = true;
recognizedText.value = '';
_hasRecognizedSpeech = false;
_hasFinalResult = false;
_lastSpeechTime = null;
// 监听识别结果
_recognitionSubscription = recognitionStream.listen((event) {
if (event.type == RecognitionEventType.finalResult) {
recognizedText.value = event.text;
if (event.text.isNotEmpty) {
_hasRecognizedSpeech = true;
_hasFinalResult = true;
_lastSpeechTime = DateTime.now();
// 收到最终结果,立即完成识别过程
if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
print('收到最终识别结果,立即处理: ${event.text}');
_recognitionCompleter!.complete(event.text);
}
} else {
// 如果最终结果为空,但之前有中间结果,仍然标记为有语音
if (_hasRecognizedSpeech) {
print('最终识别结果为空,但之前有中间结果,使用最后的中间结果');
if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
_recognitionCompleter!.complete(recognizedText.value);
}
} else {
// 如果最终结果为空,且没有中间结果,重新启动无语音超时计时器
print('最终识别结果为空,且没有中间结果,重新启动无语音超时计时器');
_startNoSpeechTimer();
}
}
print('最终识别结果: ${event.text}');
} else if (event.type == RecognitionEventType.intermediateResult) {
recognizedText.value = event.text;
if (event.text.isNotEmpty) {
_hasRecognizedSpeech = true;
_lastSpeechTime = DateTime.now();
// 如果系统正在播放TTS或接收AI响应,检测到用户开始说话时立即中断
if (_isSpeaking && event.text.trim().isNotEmpty) {
print('检测到用户开始说话,中断TTS播放和AI响应');
_ttsService.stop(); // 停止当前TTS播放
// 设置标志,表示应该取消当前的AI响应
_shouldCancelAiResponse = true;
// 取消当前的AI响应流订阅
_aiResponseSubscription?.cancel();
_aiResponseSubscription = null;
// 清空待处理的TTS文本
_pendingTtsText = '';
}
// 取消之前的静默计时器
_silenceTimer?.cancel();
// 取消之前的无语音超时计时器
_noSpeechTimer?.cancel();
_noSpeechTimer = null;
// 设置新的静默计时器,如果 2 秒内没有新的识别结果,则认为用户已经停止说话
_silenceTimer = Timer(const Duration(seconds: 2), () {
if (_hasRecognizedSpeech && !_hasFinalResult &&
_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
print('用户停止说话 2 秒,使用当前识别结果: ${recognizedText.value}');
_recognitionCompleter!.complete(recognizedText.value);
}
});
}
print('中间识别结果: ${event.text}');
} else if (event.type == RecognitionEventType.error) {
print('识别错误: ${event.error}');
}
}, onError: (error) {
print('语音识别流错误: $error');
_isListening = false;
isListening.value = false;
// 发生错误时完成 completer
if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
_recognitionCompleter!.completeError(error);
}
});
} catch (e) {
print('启动语音识别失败: $e');
_isListening = false;
isListening.value = false;
rethrow;
}
}
// 停止语音识别并返回识别的文本
Future<String> stopVoiceRecognition() async {
if (!_isListening) return '';
try {
print('停止语音识别...');
// 取消静默计时器
_silenceTimer?.cancel();
_silenceTimer = null;
// 取消无语音超时计时器
_noSpeechTimer?.cancel();
_noSpeechTimer = null;
// 取消订阅
await _recognitionSubscription?.cancel();
_recognitionSubscription = null;
// 停止语音识别
await _voiceRecognitionService.stopContinuousRecognition();
// 获取最终识别结果
final result = recognizedText.value;
// 重置状态
_isListening = false;
isListening.value = false;
print('语音识别已停止');
return result;
} catch (e) {
print('停止语音识别失败: $e');
_isListening = false;
isListening.value = false;
// 尝试强制重置语音识别服务
try {
await _voiceRecognitionService.initialize();
} catch (_) {}
return recognizedText.value; // 返回当前已识别的文本
}
}
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);
}
List<Message> get messageHistory => List.unmodifiable(_messageHistory);
void clearHistory() {
_messageHistory.clear();
}
@override
void onClose() {
_recognitionSubscription?.cancel();
_silenceTimer?.cancel();
_noSpeechTimer?.cancel();
_aiResponseSubscription?.cancel();
_shouldContinueInteraction = false;
super.onClose();
}
// 添加一个方法来强制重置 TTS 状态
void _resetTtsStateIfNeeded() {
// 检查 TTS 状态,使用 isActuallyPlaying 获取实际状态
bool isCurrentlyPlaying = false;
try {
isCurrentlyPlaying = _ttsService.isActuallyPlaying();
} catch (e) {
print('获取实际播放状态失败: $e');
isCurrentlyPlaying = _ttsService.isPlaying.value;
}
// 如果实际状态与当前状态不一致,更新它
if (_isSpeaking != isCurrentlyPlaying) {
print('重置前检测到 TTS 播放状态不一致,修正: _isSpeaking=$_isSpeaking, 实际=${isCurrentlyPlaying}');
_isSpeaking = isCurrentlyPlaying;
// 如果实际状态与 TTS 服务的 isPlaying 不一致,尝试修正 TTS 服务的状态
if (isCurrentlyPlaying != _ttsService.isPlaying.value) {
try {
_ttsService.isPlaying.value = isCurrentlyPlaying;
print('已修正 TTS 服务的播放状态');
} catch (e) {
print('修正 TTS 播放状态失败: $e');
}
}
// 更新最后一次状态变化的时间
_lastTtsStateChangeTime = DateTime.now();
}
// 检查是否需要强制重置
_checkAndUpdateTtsState();
}
}