Browse Source

修改选择音色

weicu
tanlongsheng 2 years ago
parent
commit
090d80321d
  1. 17
      lib/data/services/speech_factory.dart
  2. 169
      lib/modules/chat/controllers/chat_controller.dart

17
lib/data/services/speech_factory.dart

@ -1,7 +1,5 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'asr_service.dart';
import 'speech_impl/voice_clone_tts_service.dart';
import 'tts_service.dart';
import 'speech_impl/azure_asr_service.dart';
import 'speech_impl/azure_tts_service.dart';
@ -59,26 +57,15 @@ class SpeechFactory extends GetxService {
Get.delete<TtsService>(force: true);
}
final GetStorage storage = GetStorage();
String timbre = storage.read('timbre') ?? '1';
// 根据类型注册新服务
switch (_currentServiceType) {
case SpeechServiceType.azure:
Get.lazyPut<AsrService>(() => AzureAsrService());
if (timbre == '1') {
Get.lazyPut<TtsService>(() => AzureTtsService());
} else {
Get.lazyPut<TtsService>(() => VoiceCloneTtsService());
}
Get.lazyPut<TtsService>(() => AzureTtsService());
break;
case SpeechServiceType.flutter:
Get.lazyPut<AsrService>(() => FlutterAsrService());
if (timbre == '1') {
Get.lazyPut<TtsService>(() => FlutterTtsService());
} else {
Get.lazyPut<TtsService>(() => VoiceCloneTtsService());
}
Get.lazyPut<TtsService>(() => FlutterTtsService());
break;
}
}

169
lib/modules/chat/controllers/chat_controller.dart

@ -1,6 +1,8 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:flutter/widgets.dart';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/speech_impl/voice_clone_tts_service.dart';
import '../models/message_model.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/volcano_ai_service.dart';
@ -15,10 +17,11 @@ import '../../../data/services/voice_interaction_service.dart';
class ChatController extends GetxController {
// 服务
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
final TtsService _ttsService = Get.find<TtsService>();
late TtsService _ttsService;
final AsrService _asrService = Get.find<AsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController();
@ -37,7 +40,7 @@ class ChatController extends GetxController {
final RxBool isSending = false.obs;
final RxBool isTtsEnabled = true.obs;
final RxBool isVoiceRecognitionActive = false.obs;
// 语音交互模式
bool _isVoiceInteractionMode = false;
@ -48,7 +51,7 @@ class ChatController extends GetxController {
// 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1;
// 防抖计时器
Timer? _scrollDebounceTimer;
@ -59,10 +62,10 @@ class ChatController extends GetxController {
// 取消标志
bool _shouldCancelAiResponse = false;
// 自动停止语音识别计时器
Timer? _autoStopTimer;
// 记录最后一次活动时间
DateTime _lastActivityTime = DateTime.now();
@ -70,11 +73,11 @@ class ChatController extends GetxController {
Future<void> onInit() async {
super.onInit();
Logger.info('ChatController onInit');
// 从路由参数获取Agent信息
final Map<String, dynamic> args = Get.arguments ?? {};
final String agentId = args['agent'] ?? args['agentId'] ?? 'personal_assistant';
final String agentId =
args['agent'] ?? args['agentId'] ?? 'personal_assistant';
// 获取Agent
final Agent? foundAgent = AgentProvider.getAgentById(agentId);
@ -85,10 +88,20 @@ class ChatController extends GetxController {
}
agent = foundAgent;
final GetStorage storage = GetStorage();
String timbre = storage.read('timbre') ?? '1';
if (timbre == '1') {
_ttsService = Get.find<TtsService>();
} else {
Get.put(VoiceCloneTtsService());
_ttsService = Get.find<VoiceCloneTtsService>();
}
_ttsService.initialize();
_asrService.initialize();
// 设置语音交互模式
_isVoiceInteractionMode = args['isVoiceInteraction'] == true;
@ -130,7 +143,8 @@ class ChatController extends GetxController {
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream.listen((event) {
_voiceInteractionSubscription =
_voiceInteractionService.eventStream.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
_stopCurrentInteraction();
@ -145,26 +159,27 @@ class ChatController extends GetxController {
/// 处理聊天历史事件
void _processChatHistoryEvent(ChatHistoryEvent event) {
Logger.info('收到后台聊天历史事件: agentId=${event.agentId}');
// 如果消息不是空的且不是当前Agent的,不处理
if (event.userMessage.isEmpty || event.agentId != agent.id) {
return;
}
// 添加用户消息
final userMsg = Message(
role: 'user',
content: event.userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp),
);
// 添加助手消息
final assistantMsg = Message(
role: 'assistant',
content: event.assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒
timestamp:
DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒
);
// 防止消息重复
if (!_isDuplicateMessage(userMsg) && !_isDuplicateMessage(assistantMsg)) {
messages.add(userMsg);
@ -176,15 +191,15 @@ class ChatController extends GetxController {
/// 停止当前交互
void _stopCurrentInteraction() {
Logger.info('收到后台语音识别开始事件,停止当前AI交互');
// 停止当前AI交互
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应");
}
// 停止TTS
_ttsService.stop();
// 停止语音识别
if (isVoiceRecognitionActive.value) {
stopVoiceRecognition();
@ -194,7 +209,7 @@ class ChatController extends GetxController {
@override
void onClose() {
Logger.info('ChatController onClose');
// 清理资源
scrollController.dispose();
_aiResponseSubscription?.cancel();
@ -202,10 +217,10 @@ class ChatController extends GetxController {
_voiceInteractionSubscription?.cancel();
_autoStopTimer?.cancel();
_scrollDebounceTimer?.cancel(); // 清理防抖计时器
// 停止TTS
_ttsService.stop();
if (isVoiceRecognitionActive.value) {
_asrService.stopContinuousRecognition();
}
@ -220,33 +235,32 @@ class ChatController extends GetxController {
/// 滚动到列表底部方法,添加防抖功能
void _scrollToBottom() {
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
}
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
}
}
}
/// 中断当前响应
void _interruptCurrentResponse(String reason) {
Logger.info('${reason}');
// 停止TTS
_ttsService.stop();
// 取消AI响应流订阅
if (_aiResponseSubscription != null) {
_shouldCancelAiResponse = true;
_aiResponseSubscription!.cancel();
_aiResponseSubscription = null;
// 将加载中的消息转换为正常消息,保留已输出的内容
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
@ -255,7 +269,7 @@ class ChatController extends GetxController {
);
messages.refresh();
}
// 重置发送状态
isSending.value = false;
}
@ -278,14 +292,13 @@ class ChatController extends GetxController {
);
messages.add(userMessage);
// 清空输入框
inputText.value = '';
// 添加加载中的消息
final loadingMessage = Message.loading();
messages.add(loadingMessage);
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
@ -298,7 +311,7 @@ class ChatController extends GetxController {
// 重置取消标志
_shouldCancelAiResponse = false;
String fullResponse = '';
// 获取AI响应流
@ -327,12 +340,12 @@ class ChatController extends GetxController {
// 完成响应,将isLoading设为false
_finalizeResponse(fullResponse);
// 如果TTS启用,处理缓冲区中的剩余文本
if (isTtsEnabled.value) {
_ttsService.flushStream();
}
// 保存聊天历史
_saveChatHistory();
},
@ -345,7 +358,7 @@ class ChatController extends GetxController {
);
} catch (e) {
Logger.error('发送消息失败', e);
_handleResponseError();
}
}
@ -353,10 +366,11 @@ class ChatController extends GetxController {
// 构建消息历史
List<Map<String, String>> _buildMessageHistory() {
final List<Map<String, String>> messageHistory = [];
// 只取最近的10条消息作为上下文
final recentMessages = messages.where((m) => !m.isLoading).toList();
final startIndex = recentMessages.length > 10 ? recentMessages.length - 10 : 0;
final startIndex =
recentMessages.length > 10 ? recentMessages.length - 10 : 0;
for (int i = startIndex; i < recentMessages.length; i++) {
final m = recentMessages[i];
@ -365,7 +379,7 @@ class ChatController extends GetxController {
'content': m.content,
});
}
return messageHistory;
}
@ -393,7 +407,7 @@ class ChatController extends GetxController {
messages.refresh();
_scrollToBottom();
}
isSending.value = false;
_aiResponseSubscription = null;
}
@ -408,7 +422,7 @@ class ChatController extends GetxController {
);
messages.refresh();
}
isSending.value = false;
_aiResponseSubscription = null;
}
@ -429,18 +443,17 @@ class ChatController extends GetxController {
_voiceInteractionService.pauseVoiceInteraction();
try {
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
final recognitionStream = await _asrService.startContinuousRecognition();
isVoiceRecognitionActive.value = true;
isListening.value = true;
recognizedText.value = '';
// 重置临时消息索引
_tempMessageIndex = -1;
// 启动自动停止计时器
_startAutoStopTimer();
@ -460,7 +473,7 @@ class ChatController extends GetxController {
void _handleRecognitionEvent(RecognitionEvent event) {
// 更新活动时间
_updateActivityTime();
switch (event.type) {
case RecognitionEventType.intermediateResult:
_handleRecognizing(event.text);
@ -486,24 +499,24 @@ class ChatController extends GetxController {
void _startAutoStopTimer() {
// 取消现有计时器
_autoStopTimer?.cancel();
// 初始化最后活动时间
_updateActivityTime();
// 根据模式设置超时时间
final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15;
// 创建新的计时器,每秒检查一次
_autoStopTimer = Timer.periodic(Duration(seconds: 1), (timer) {
// 检查是否有活动
bool isActive = _ttsService.isSpeaking || isSpeechDetected.value;
// 如果有活动,更新时间并继续等待
if (isActive) {
_updateActivityTime();
return;
}
// 检查空闲时间
int idleSeconds = DateTime.now().difference(_lastActivityTime).inSeconds;
if (idleSeconds >= timeoutSeconds) {
@ -516,17 +529,17 @@ class ChatController extends GetxController {
// 处理语音识别中的状态
void _handleRecognizing(String text) {
if (text.isEmpty) return;
recognizedText.value = text;
// 设置语音检测状态为true,用于控制波形动画
if (!isSpeechDetected.value) {
isSpeechDetected.value = true;
}
// 更新活动时间
_updateActivityTime();
// 用户开始讲话时,立即停止当前TTS和中断AI响应
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
@ -558,10 +571,10 @@ class ChatController extends GetxController {
// 处理语音识别最终结果
void _handleFinalResult(String text) {
if (text.isEmpty) return;
Logger.info('处理语音识别最终结果: $text');
recognizedText.value = text;
// 更新活动时间
_updateActivityTime();
@ -569,7 +582,7 @@ class ChatController extends GetxController {
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
messages.removeAt(_tempMessageIndex);
}
// 重置临时消息索引
_tempMessageIndex = -1;
@ -578,7 +591,7 @@ class ChatController extends GetxController {
// 重置识别文本,但不关闭语音识别框
recognizedText.value = '';
// 重置语音检测状态
isSpeechDetected.value = false;
}
@ -586,14 +599,14 @@ class ChatController extends GetxController {
// 处理语音识别事件错误
void _handleRecognitionEventError(String? error) {
Logger.error('语音识别错误: ${error ?? "未知错误"}');
_cleanupRecognition();
}
// 处理语音识别流错误
void _handleRecognitionError(dynamic error) {
Logger.error('语音识别错误', error);
_cleanupRecognition();
}
@ -605,10 +618,10 @@ class ChatController extends GetxController {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
// 停止语音识别
isVoiceRecognitionActive.value = false;
isListening.value = false;
@ -636,7 +649,7 @@ class ChatController extends GetxController {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
} catch (e) {
@ -691,10 +704,12 @@ class ChatController extends GetxController {
/// 检查是否为重复消息
bool _isDuplicateMessage(Message message) {
return messages.any((m) =>
m.role == message.role &&
m.content == message.content &&
(m.timestamp.millisecondsSinceEpoch - message.timestamp.millisecondsSinceEpoch).abs() < 5000
);
return messages.any((m) =>
m.role == message.role &&
m.content == message.content &&
(m.timestamp.millisecondsSinceEpoch -
message.timestamp.millisecondsSinceEpoch)
.abs() <
5000);
}
}

Loading…
Cancel
Save