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.
442 lines
14 KiB
442 lines
14 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;
|
|
|
|
// 添加一个订阅,用于监听 TTS 状态变化
|
|
StreamSubscription? _ttsSpeakingSubscription;
|
|
|
|
BackgroundAgentService()
|
|
: _aiService = VolcanoAIService(),
|
|
_ttsService = Get.find<VolcanoTtsService>(),
|
|
_voiceRecognitionService = Get.find<VoiceRecognitionService>() {
|
|
// 监听 TTS 播放状态
|
|
_ttsSpeakingSubscription = _ttsService.isPlaying.listen((speaking) {
|
|
_isSpeaking = speaking;
|
|
print('TTS 播放状态变化: $_isSpeaking');
|
|
|
|
// 如果 TTS 停止播放,且正在进行语音识别,重新启动无语音超时计时器
|
|
if (!speaking && _isListening && _hasRecognizedSpeech && _noSpeechTimer == null) {
|
|
_startNoSpeechTimer();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 处理蓝牙耳机按钮触发的交互
|
|
Future<void> handleAgentInteraction(String systemPrompt) async {
|
|
if (_isProcessing) {
|
|
print('已经在处理交互,忽略此次请求');
|
|
return;
|
|
}
|
|
|
|
// 设置循环交互标志为 true
|
|
_shouldContinueInteraction = true;
|
|
|
|
try {
|
|
// 循环进行交互,直到用户 10 秒没有说话或手动停止
|
|
while (_shouldContinueInteraction) {
|
|
await _processSingleInteraction(systemPrompt);
|
|
}
|
|
} finally {
|
|
// 确保在交互结束时停止语音识别
|
|
if (_isListening) {
|
|
await stopVoiceRecognition();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 启动无语音超时计时器
|
|
void _startNoSpeechTimer() {
|
|
// 如果系统正在播放 TTS,不启动计时器
|
|
if (_isSpeaking) {
|
|
print('系统正在播放 TTS,不启动无语音超时计时器');
|
|
return;
|
|
}
|
|
|
|
// 取消之前的计时器
|
|
_noSpeechTimer?.cancel();
|
|
|
|
// 设置新的计时器,如果 10 秒内没有新的识别结果,且系统没有在播放 TTS,则认为用户已经停止交互
|
|
_noSpeechTimer = Timer(const Duration(seconds: 10), () {
|
|
// 再次检查是否正在播放 TTS
|
|
if (!_isSpeaking && _isListening && _recognitionCompleter != null && !_recognitionCompleter!.isCompleted) {
|
|
print('10秒内没有新的语音输入,且系统没有在播放 TTS,退出循环交互');
|
|
_recognitionCompleter!.complete('');
|
|
}
|
|
});
|
|
|
|
print('启动无语音超时计时器,10秒后检查');
|
|
}
|
|
|
|
// 处理单次交互
|
|
Future<void> _processSingleInteraction(String systemPrompt) async {
|
|
_isProcessing = true;
|
|
_hasRecognizedSpeech = false;
|
|
_hasFinalResult = false;
|
|
|
|
try {
|
|
// 先播放一个简短的提示音或提示语,表示开始监听
|
|
try {
|
|
await _ttsService.speak("我在听");
|
|
} catch (e) {
|
|
print('播放提示音失败: $e');
|
|
// 继续执行,不要因为提示音失败而中断整个流程
|
|
}
|
|
|
|
// 开始语音识别
|
|
bool recognitionStarted = false;
|
|
String userInput = '';
|
|
|
|
try {
|
|
// 尝试启动语音识别
|
|
await startVoiceRecognition();
|
|
recognitionStarted = true;
|
|
|
|
// 创建一个 Completer 来处理语音识别完成
|
|
_recognitionCompleter = Completer<String>();
|
|
|
|
// 启动无语音超时计时器
|
|
_startNoSpeechTimer();
|
|
|
|
// 等待语音识别完成
|
|
userInput = await _recognitionCompleter!.future;
|
|
|
|
// 取消无语音超时计时器
|
|
_noSpeechTimer?.cancel();
|
|
_noSpeechTimer = null;
|
|
|
|
// 如果没有识别到语音,则停止循环交互
|
|
if (!_hasRecognizedSpeech) {
|
|
print('没有识别到用户语音,退出循环交互');
|
|
|
|
// 停止语音识别
|
|
if (_isListening) {
|
|
await stopVoiceRecognition();
|
|
}
|
|
|
|
try {
|
|
await _ttsService.speak("没有听到您说话,已退出语音交互");
|
|
} catch (e) {
|
|
print('播放退出提示失败: $e');
|
|
}
|
|
|
|
// 设置标志,停止循环交互
|
|
_shouldContinueInteraction = false;
|
|
_isProcessing = false;
|
|
return;
|
|
}
|
|
|
|
// 注意:不再停止语音识别,保持语音识别状态
|
|
// 只有在退出交互时才停止语音识别
|
|
} catch (e) {
|
|
print('语音识别过程出错: $e');
|
|
// 如果语音识别失败,尝试使用默认问候语
|
|
userInput = '你好,请帮我回答一个问题';
|
|
} 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 {
|
|
await for (final chunk in _aiService.sendMessageStream(
|
|
messages: messages,
|
|
systemPrompt: systemPrompt,
|
|
)) {
|
|
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 {
|
|
await _ttsService.speak(textToSpeak);
|
|
} catch (e) {
|
|
print('播放TTS失败: $e');
|
|
}
|
|
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('AI响应生成失败: $e');
|
|
// 如果AI响应失败,使用默认回复
|
|
fullResponse = '抱歉,我现在无法回答您的问题。请稍后再试。';
|
|
}
|
|
|
|
// 处理剩余的文本
|
|
if (_ttsService.isEnabled.value && _pendingTtsText.isNotEmpty) {
|
|
try {
|
|
await _ttsService.speak(_pendingTtsText);
|
|
} catch (e) {
|
|
print('播放剩余TTS失败: $e');
|
|
}
|
|
}
|
|
_pendingTtsText = '';
|
|
|
|
// 保存助手回复到历史记录
|
|
_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) return;
|
|
|
|
try {
|
|
// 确保语音识别服务已初始化
|
|
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);
|
|
}
|
|
}
|
|
print('最终识别结果: ${event.text}');
|
|
} else if (event.type == RecognitionEventType.intermediateResult) {
|
|
recognizedText.value = event.text;
|
|
if (event.text.isNotEmpty) {
|
|
_hasRecognizedSpeech = true;
|
|
_lastSpeechTime = DateTime.now();
|
|
|
|
// 如果系统正在播放TTS,检测到用户开始说话时立即中断TTS播放
|
|
if (_isSpeaking && event.text.trim().isNotEmpty) {
|
|
print('检测到用户开始说话,中断TTS播放');
|
|
_ttsService.stop(); // 停止当前TTS播放
|
|
}
|
|
|
|
// 取消之前的静默计时器
|
|
_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 {
|
|
// 取消静默计时器
|
|
_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;
|
|
|
|
return result;
|
|
} catch (e) {
|
|
print('停止语音识别失败: $e');
|
|
_isListening = false;
|
|
isListening.value = false;
|
|
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();
|
|
_ttsSpeakingSubscription?.cancel();
|
|
_shouldContinueInteraction = false;
|
|
super.onClose();
|
|
}
|
|
}
|