Browse Source

v005:

weicu
wolfplus 2 years ago
parent
commit
d7436b3047
  1. 3
      android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt
  2. 26
      android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
  3. 97
      android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt
  4. 4
      lib/core/bindings/initial_binding.dart
  5. 5
      lib/core/routes/app_pages.dart
  6. 2
      lib/core/routes/app_routes.dart
  7. 3
      lib/data/services/azure_asr_service.dart
  8. 1
      lib/data/services/azure_tts_service.dart
  9. 400
      lib/data/services/voice_interaction_service.dart
  10. 11
      lib/main.dart
  11. 94
      lib/modules/chat/controllers/chat_controller.dart
  12. 3
      lib/modules/explore/controllers/explore_controller.dart
  13. 3
      lib/modules/home/controllers/home_controller.dart
  14. 6
      lib/modules/translation/controllers/translation_controller.dart

3
android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt

@ -244,7 +244,8 @@ class AzureTtsHelper {
try {
synthesizer?.StopSpeakingAsync()
Log.d(TAG, "已停止语音合成")
Log.d(TAG, "已停止语音合成kt")
return true
} catch (e: Exception) {
Log.e(TAG, "停止语音合成失败: ${e.message}")

26
android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt

@ -72,13 +72,30 @@ class MainActivity: FlutterActivity() {
var azureSpeechKey: String = "2K2VOHSuL8ZSEnbKgHSyTwo1ohj7C2gbxyVimsjpVUt6aETxVwlvJQQJ99BCACqBBLyXJ3w3AAAYACOGqCMj"
var azureSpeechRegion: String = "southeastasia"
var volcanoAiApiKey: String = "0fd7c93b-b9bf-4b0b-bca6-e284491792bf"
// 标记是否已初始化
var isAzureHelpersInitialized = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化本地的 Azure 语音服务实例
Log.d(TAG, "初始化本地的 Azure 语音服务实例")
azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper()
// 初始化 Azure 服务
// if (azureSpeechKey.isNotEmpty() && azureSpeechRegion.isNotEmpty()) {
// Log.d(TAG, "初始化本地实例的 Azure 服务配置")
// // 初始化ASR
// azureAsrHelper.initialize(azureSpeechKey, azureSpeechRegion, "zh-CN")
// // 初始化TTS
// azureTtsHelper.initialize(azureSpeechKey, azureSpeechRegion, "zh-CN")
// } else {
// Log.e(TAG, "Azure配置信息不完整,无法初始化本地实例的Azure服务")
// }
// 注册广播接收器
registerVoiceInteractionReceiver()
@ -480,6 +497,13 @@ class MainActivity: FlutterActivity() {
Log.d(TAG, "配置参数: key=${if (key.isNotEmpty()) key.take(5) + "..." else "空"}, region=$region, aiKey=${if (aiKey.isNotEmpty()) aiKey.take(5) + "..." else "空"}")
// 如果配置发生变化,需要重新初始化本地实例
// if (key.isNotEmpty() && region.isNotEmpty()) {
// Log.d(TAG, "配置已更新,重新初始化本地实例")
// azureAsrHelper.initialize(key, region, "zh-CN")
// azureTtsHelper.initialize(key, region, "zh-CN")
// }
// 创建 Intent 并启动服务
val serviceIntent = Intent(this, VoiceInteractionService::class.java)
@ -618,8 +642,10 @@ class MainActivity: FlutterActivity() {
Log.e(TAG, "注销广播接收器失败: ${e.message}")
}
// 在这里释放本地实例
azureAsrHelper.dispose()
azureTtsHelper.dispose()
super.onDestroy()
}
}

97
android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt

@ -90,6 +90,7 @@ class VoiceInteractionService : Service() {
请保持回答简短、准确,避免过长的解释。
如果用户的问题不清楚,请礼貌地请求澄清。
不要使用复杂的术语,除非用户明确要求。
用户用语音和你交互.
""".trimIndent()
override fun onCreate() {
@ -139,7 +140,8 @@ class VoiceInteractionService : Service() {
* 初始化所有服务
*/
private fun initServices() {
// 初始化Azure服务
// 创建新的Azure服务实例
Log.d(TAG, "创建新的Azure服务实例")
azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper()
@ -185,6 +187,8 @@ class VoiceInteractionService : Service() {
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent")
handleDoubleClick()
return true
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
@ -328,6 +332,14 @@ class VoiceInteractionService : Service() {
private fun handleDoubleClick() {
Log.d(TAG, "处理双击事件")
//屏蔽短时间重复响应的问题
val currentTime = System.currentTimeMillis()
val timeDiff = currentTime - lastKeyEventTime
if (timeDiff < 500) {
Log.d(TAG, "短时间重复响应,忽略")
return
}
// 停止当前TTS播放
stopCurrentTTS()
@ -362,7 +374,9 @@ class VoiceInteractionService : Service() {
playNotification("需要录音权限,请在设置中授予权限")
return
}
// 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
isActive = true
isRecognitionActive = true
hasSpeechDetected = false
@ -390,34 +404,33 @@ class VoiceInteractionService : Service() {
override fun onSessionStarted() {
updateLastActivityTime()
// 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
}
override fun onSessionStopped() {
isRecognitionActive = false
// 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) {
handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition()
}
}, 1000)
}
// // 如果不是因超时暂停,尝试重新启动
// if (isActive && !isTimeoutPaused) {
// handler.postDelayed({
// if (isActive && !isRecognitionActive && !isTtsSpeaking) {
// startVoiceRecognition()
// }
// }, 1000)
// }
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
// 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) {
handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition()
}
}, 1000)
}
// // 如果不是因超时暂停,尝试重新启动
// if (isActive && !isTimeoutPaused) {
// handler.postDelayed({
// if (isActive && !isRecognitionActive && !isTtsSpeaking) {
// startVoiceRecognition()
// }
// }, 1000)
// }
}
override fun onError(error: String) {
@ -425,13 +438,13 @@ class VoiceInteractionService : Service() {
playNotification("语音识别出错")
// 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) {
handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition()
}
}, 2000)
}
// if (isActive && !isTimeoutPaused) {
// handler.postDelayed({
// if (isActive && !isRecognitionActive && !isTtsSpeaking) {
// startVoiceRecognition()
// }
// }, 2000)
// }
}
})
} catch (e: Exception) {
@ -439,11 +452,11 @@ class VoiceInteractionService : Service() {
playNotification("启动语音识别失败")
// 延迟重试
handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition()
}
}, 3000)
// handler.postDelayed({
// if (isActive && !isRecognitionActive && !isTtsSpeaking) {
// startVoiceRecognition()
// }
// }, 3000)
}
}
@ -725,22 +738,30 @@ class VoiceInteractionService : Service() {
override fun onDestroy() {
Log.d(TAG, "onDestroy - 语音交互服务正在销毁")
super.onDestroy()
// 停止监控
stopMonitoring()
// 取消注册媒体按钮接收器
val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name)
audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver)
// 停止语音识别
if (isRecognitionActive) {
stopVoiceRecognition()
}
// 停止TTS
stopCurrentTTS()
// 释放资源
mediaSession.isActive = false
// 释放 MediaSession
mediaSession.release()
// 释放Azure服务实例
Log.d(TAG, "释放Azure服务实例")
azureAsrHelper.dispose()
azureTtsHelper.dispose()
// 更新服务状态
isRunning.set(false)
super.onDestroy()
}
/**

4
lib/core/bindings/initial_binding.dart

@ -22,8 +22,8 @@ class InitialBinding extends Bindings {
// 火山翻译服务
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), fenix: true);
// 语音交互服务 - 使用 putAsync 确保 init 方法被调用
// 注意:VoiceInteractionService 在 HomeController 中初始化
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true);
}
}

5
lib/core/routes/app_pages.dart

@ -26,6 +26,11 @@ import './app_routes.dart';
abstract class AppPages {
static final pages = [
GetPage(
name: Routes.initial,
page: () => const HomeView(),
binding: HomeBinding(),
),
GetPage(
name: Routes.home,
page: () => const HomeView(),

2
lib/core/routes/app_routes.dart

@ -1,5 +1,5 @@
abstract class Routes {
static const initial = '/';
static const initial = '/home';
static const home = '/home';
static const explore = '/explore';
static const chat = '/chat';

3
lib/data/services/azure_asr_service.dart

@ -2,11 +2,12 @@ import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../../core/utils/logger.dart';
import 'package:get/get.dart';
/// 微软语音识别服务
///
/// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口
class AzureAsrService {
static final AzureAsrService to = Get.put(AzureAsrService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_asr');
static const EventChannel _eventChannel = EventChannel('com.deep_voice.azure_asr_events');

1
lib/data/services/azure_tts_service.dart

@ -18,6 +18,7 @@ class AzureTtsException implements Exception {
/// 该服务通过平台通道与 Android 上的 Microsoft Speech SDK 交互,
/// 提供文本转语音功能。
class AzureTtsService extends GetxService {
static final AzureTtsService to = Get.put(AzureTtsService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_tts');
bool _isInitialized = false;

400
lib/data/services/voice_interaction_service.dart

@ -1,49 +1,48 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../core/utils/logger.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../services/azure_asr_service.dart';
import '../services/azure_tts_service.dart';
import '../services/chat_history_service.dart';
import '../../modules/chat/models/message_model.dart';
import '../../core/utils/logger.dart';
import 'chat_history_service.dart';
import '../../data/services/azure_asr_service.dart';
import '../../data/services/azure_tts_service.dart';
/// 语音交互服务,用于管理原生的 VoiceInteractionService
/// 语音交互服务
///
/// 该服务提供了与Android端的VoiceInteractionService.kt通信的接口,
/// 用于管理后台语音交互服务的生命周期和接收语音交互事件
class VoiceInteractionService extends GetxService {
static VoiceInteractionService get to => Get.find();
// 服务运行状态
final _isRunning = false.obs;
bool get isRunning => _isRunning.value;
// 语音识别状态
final _isRecognitionActive = false.obs;
bool get isRecognitionActive => _isRecognitionActive.value;
// 聊天记录 - 可被 ChatView 监听
final RxList<Message> personalAssistantMessages = <Message>[].obs;
// 方法通道
// 方法通道和事件通道
static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction');
// 事件通道
static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events');
// 配置信息
late final String _azureSpeechKey;
late final String _azureSpeechRegion;
late final String _volcanoAiApiKey;
// 服务状态
final _isServiceRunning = false.obs;
bool get isServiceRunning => _isServiceRunning.value;
// 事件流控制器
StreamController<VoiceInteractionEvent>? _eventStreamController;
Stream<VoiceInteractionEvent>? _eventStream;
Stream<VoiceInteractionEvent>? get eventStream => _eventStream;
// 事件通道状态
bool _isEventChannelReady = false;
Completer<void>? _eventChannelReadyCompleter;
StreamSubscription? _eventSubscription;
// 配置信息
late String _azureSpeechKey;
late String _azureSpeechRegion;
late String _volcanoAiApiKey;
// 初始化标记,避免重复初始化
bool _isInitialized = false;
// 初始化状态标志
static bool _isInitialized = false;
/// 构造函数
VoiceInteractionService() {
_loadConfig();
_setupEventChannel();
}
/// 从环境变量加载配置
@ -53,39 +52,44 @@ class VoiceInteractionService extends GetxService {
_volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? '';
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) {
Logger.error('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
}
if (_volcanoAiApiKey.isEmpty) {
Logger.error('未找到火山 AI 配置。请在 .env 文件中设置 VOLCANO_AI_API_KEY');
Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY');
}
}
/// 设置事件通道监听
/// 设置事件通道
void _setupEventChannel() {
_eventChannelReadyCompleter = Completer<void>();
_eventChannel.receiveBroadcastStream().listen(
(event) {
if (event is Map) {
final String eventType = event['type'] as String? ?? '';
// 处理通道准备好的事件
if (eventType == 'channelReady') {
_isEventChannelReady = true;
if (!_eventChannelReadyCompleter!.isCompleted) {
_eventChannelReadyCompleter!.complete();
_eventSubscription = _eventChannel
.receiveBroadcastStream()
.listen((event) {
if (event is Map) {
final String eventType = event['type'] as String? ?? '';
// 处理通道准备好的事件
if (eventType == 'channelReady') {
_isEventChannelReady = true;
if (!_eventChannelReadyCompleter!.isCompleted) {
_eventChannelReadyCompleter!.complete();
}
return;
}
return;
_handleVoiceInteractionEvent(event);
}
_handleEvent(event);
}
},
onError: (error) {
Logger.error('语音交互事件通道错误: $error');
}
);
}, onError: (error) {
Logger.error('语音交互事件通道错误: $error');
});
}
/// 创建事件流
void _createEventStream() {
_eventStreamController = StreamController<VoiceInteractionEvent>.broadcast();
_eventStream = _eventStreamController?.stream;
}
/// 等待事件通道准备好
@ -96,152 +100,67 @@ class VoiceInteractionService extends GetxService {
await _eventChannelReadyCompleter!.future.timeout(timeout);
return true;
} on TimeoutException {
Logger.error('等待事件通道准备好超时');
Logger.error('等待语音交互事件通道准备好超时');
return false;
}
}
/// 初始化服务
Future<VoiceInteractionService> init() async {
// 如果已经初始化过,直接返回
if (_isInitialized) {
return this;
}
Future<VoiceInteractionService> initialize() async {
try {
// 等待事件通道准备好
final bool eventChannelReady = await _waitForEventChannel();
if (!eventChannelReady) {
Logger.error('事件通道未准备好,初始化失败');
if (_isInitialized) {
return this;
}
_isInitialized = true;
_loadConfig();
_setupEventChannel();
_createEventStream();
// 检查服务是否正在运行
await _checkServiceStatus();
// 获取服务运行状态
await _updateServiceStatus();
// 加载个人助手的历史记录
_loadPersonalAssistantHistory();
// 等待事件通道准备好
await _waitForEventChannel();
// 标记为已初始化
_isInitialized = true;
Logger.info('语音交互服务初始化完成');
return this;
} catch (e) {
Logger.error('VoiceInteractionService 初始化失败: $e');
Logger.error('语音交互服务初始化失败: $e');
return this;
}
}
/// 加载个人助手的历史记录
void _loadPersonalAssistantHistory() {
if (Get.isRegistered<ChatHistoryService>()) {
final historyService = Get.find<ChatHistoryService>();
final messages = historyService.loadHistory('personal_assistant');
if (messages.isNotEmpty) {
personalAssistantMessages.assignAll(messages);
}
}
}
/// 处理来自原生端的事件
void _handleEvent(dynamic event) {
try {
if (event is! Map) {
return;
}
final type = event['type'] as String?;
if (type == null) {
return;
}
switch (type) {
case 'recognitionStarted':
_isRecognitionActive.value = true;
// 检测到后台服务的语音识别开始运行,主动停止Flutter端的语音识别和TTS
// _stopFlutterVoiceServices();
break;
case 'chatHistory':
// 处理聊天记录
_handleChatHistory(event);
break;
}
} catch (e) {
Logger.error('处理原生端事件时出错: $e');
}
}
/// 处理聊天记录事件
void _handleChatHistory(Map<dynamic, dynamic> event) {
/// 检查服务状态
Future<void> _checkServiceStatus() async {
try {
final agentId = event['agentId'] as String?;
final userMessage = event['userMessage'] as String?;
final assistantMessage = event['assistantMessage'] as String?;
final timestamp = event['timestamp'] as int?;
if (agentId == null || userMessage == null || assistantMessage == null || timestamp == null) {
return;
}
// 只处理个人助手的聊天记录
if (agentId != 'personal_assistant') {
return;
}
// 添加用户消息
personalAssistantMessages.add(Message(
role: 'user',
content: userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp),
));
// 添加助手消息
personalAssistantMessages.add(Message(
role: 'assistant',
content: assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1000), // 助手消息时间戳稍后
));
// 保存到历史记录
if (Get.isRegistered<ChatHistoryService>()) {
final historyService = Get.find<ChatHistoryService>();
historyService.saveHistory('personal_assistant', personalAssistantMessages);
}
final bool isRunning = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false;
_isServiceRunning.value = isRunning;
Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
} catch (e) {
Logger.error('处理聊天记录事件失败: $e');
}
}
/// 停止 Flutter 端的语音服务
void _stopFlutterVoiceServices() {
// 停止 TTS
if (Get.isRegistered<AzureTtsService>()) {
Get.find<AzureTtsService>().stop();
}
// 停止 ASR
if (Get.isRegistered<AzureAsrService>()) {
Get.find<AzureAsrService>().stopContinuousRecognition();
Logger.error('检查语音交互服务状态失败: $e');
_isServiceRunning.value = false;
}
}
/// 启动语音交互服务
Future<bool> startService() async {
if (!_isInitialized) {
await init();
if (_isServiceRunning.value) {
Logger.info('语音交互服务已经在运行');
return true;
}
try {
// 启动服务
final result = await _channel.invokeMethod<bool>('startVoiceInteractionService', {
final bool result = await _channel.invokeMethod('startVoiceInteractionService', {
'azure_speech_key': _azureSpeechKey,
'azure_speech_region': _azureSpeechRegion,
'volcano_ai_api_key': _volcanoAiApiKey,
}) ?? false;
// 更新状态
if (result) {
await _updateServiceStatus();
_isServiceRunning.value = true;
Logger.info('语音交互服务启动成功');
} else {
Logger.error('语音交互服务启动失败');
}
return result;
@ -253,12 +172,19 @@ class VoiceInteractionService extends GetxService {
/// 停止语音交互服务
Future<bool> stopService() async {
if (!_isServiceRunning.value) {
Logger.info('语音交互服务未运行');
return true;
}
try {
final result = await _channel.invokeMethod<bool>('stopVoiceInteractionService') ?? false;
final bool result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false;
// 更新状态
if (result) {
await _updateServiceStatus();
_isServiceRunning.value = false;
Logger.info('语音交互服务停止成功');
} else {
Logger.error('语音交互服务停止失败');
}
return result;
@ -268,10 +194,22 @@ class VoiceInteractionService extends GetxService {
}
}
/// 暂停语音交互
/// 暂停语音交互(停止TTS和语音识别,但保持服务运行)
Future<bool> pauseVoiceInteraction() async {
if (!_isServiceRunning.value) {
Logger.info('语音交互服务未运行,无法暂停');
return false;
}
try {
final result = await _channel.invokeMethod<bool>('pauseVoiceInteraction') ?? false;
final bool result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false;
if (result) {
Logger.info('语音交互暂停成功');
} else {
Logger.error('语音交互暂停失败');
}
return result;
} catch (e) {
Logger.error('暂停语音交互失败: $e');
@ -279,34 +217,116 @@ class VoiceInteractionService extends GetxService {
}
}
/// 更新服务运行状态
Future<void> _updateServiceStatus() async {
try {
final status = await _channel.invokeMethod<bool>('isVoiceInteractionServiceRunning') ?? false;
_isRunning.value = status;
} catch (e) {
Logger.error('获取语音交互服务状态失败: $e');
_isRunning.value = false;
/// 处理来自原生端的语音交互事件
void _handleVoiceInteractionEvent(dynamic event) {
if (event is! Map || _eventStreamController == null) return;
final Map<dynamic, dynamic> eventMap = event;
final String eventType = eventMap['type'] as String? ?? '';
final int timestamp = eventMap['timestamp'] as int? ?? 0;
// 添加时间戳日志,帮助调试
Logger.info('收到原生端事件: $eventType, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
switch (eventType) {
case 'chatHistory':
final String agentId = eventMap['agentId'] as String? ?? '';
final String userMessage = eventMap['userMessage'] as String? ?? '';
final String assistantMessage = eventMap['assistantMessage'] as String? ?? '';
_eventStreamController?.add(ChatHistoryEvent(
agentId: agentId,
userMessage: userMessage,
assistantMessage: assistantMessage,
timestamp: timestamp,
));
// 保存聊天记录
_saveChatHistory(agentId, userMessage, assistantMessage, timestamp);
Logger.info('收到聊天历史事件: agentId=$agentId');
break;
case 'recognitionStarted':
_eventStreamController?.add(RecognitionStartedEvent(
timestamp: timestamp,
));
Logger.info('收到语音识别开始事件');
break;
default:
Logger.warning('收到未知类型的语音交互事件: $eventType');
break;
}
}
/// 释放资源
Future<void> dispose() async {
@override
void onClose() {
// 清理资源
_eventSubscription?.cancel();
_eventStreamController?.close();
super.onClose();
}
/// 保存聊天记录
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) {
try {
if (_isRunning.value) {
await stopService();
}
// 获取ChatHistoryService实例
final chatHistoryService = Get.find<ChatHistoryService>();
// 创建用户消息和助手消息
final userMsg = Message(
role: 'user',
content: userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp),
);
final assistantMsg = Message(
role: 'assistant',
content: assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息
);
// 加载现有历史记录
final existingMessages = chatHistoryService.loadHistory(agentId);
// 添加新消息
existingMessages.addAll([userMsg, assistantMsg]);
_isInitialized = false;
// 保存更新后的历史记录
chatHistoryService.saveHistory(agentId, existingMessages);
} catch (e) {
Logger.error('释放语音交互服务资源失败: $e');
Logger.error('保存聊天记录失败: $e');
}
}
}
/// 语音交互事件基类
abstract class VoiceInteractionEvent {
final int timestamp;
@override
void onClose() {
// 清理资源
dispose();
super.onClose();
}
VoiceInteractionEvent({required this.timestamp});
}
/// 聊天历史事件
class ChatHistoryEvent extends VoiceInteractionEvent {
final String agentId;
final String userMessage;
final String assistantMessage;
ChatHistoryEvent({
required this.agentId,
required this.userMessage,
required this.assistantMessage,
required int timestamp,
}) : super(timestamp: timestamp);
}
/// 语音识别开始事件
class RecognitionStartedEvent extends VoiceInteractionEvent {
RecognitionStartedEvent({
required int timestamp,
}) : super(timestamp: timestamp);
}

11
lib/main.dart

@ -9,6 +9,7 @@ import 'core/routes/app_routes.dart';
import 'dart:io';
import 'package:window_size/window_size.dart';
import 'app/translations/app_translations.dart';
import 'data/services/voice_interaction_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -54,10 +55,10 @@ class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸
designSize: const Size(375, 812),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
builder: (_, child) {
return GetMaterialApp(
title: 'Deep Voice',
theme: ThemeData(
@ -65,14 +66,16 @@ class MainApp extends StatelessWidget {
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialBinding: InitialBinding(),
initialRoute: Routes.home,
initialRoute: Routes.initial,
getPages: AppPages.pages,
debugShowCheckedModeBanner: false,
translations: AppTranslations(),
locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'),
// 注意:VoiceInteractionService 在 HomeController 的 onInit 方法中初始化
// 不要在这里重复初始化,以避免多次注册事件监听器
);
}
},
);
}
}

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

@ -9,6 +9,7 @@ import '../../../data/providers/agent_provider.dart';
import '../../../data/models/agent.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/chat_history_service.dart';
import '../../../data/services/voice_interaction_service.dart';
class ChatController extends GetxController {
// 服务
@ -16,6 +17,7 @@ class ChatController extends GetxController {
final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final AzureAsrService _asrService = Get.find<AzureAsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
// 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController();
@ -46,6 +48,7 @@ class ChatController extends GetxController {
// 流式响应控制
StreamSubscription? _aiResponseSubscription;
StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
// 取消标志
bool _shouldCancelAiResponse = false;
@ -93,12 +96,101 @@ class ChatController extends GetxController {
startVoiceRecognition();
}
// 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents();
// 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
}
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
_handleRecognitionStartedEvent();
} else if (event is ChatHistoryEvent) {
// 收到chatHistory事件时,更新聊天界面
_handleChatHistoryEvent(event);
}
});
}
}
/// 处理recognitionStarted事件
void _handleRecognitionStartedEvent() {
Logger.info('收到后台语音识别开始事件,停止当前AI交互');
// 停止当前AI交互
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应");
}
// 停止TTS
_ttsService.stop();
// 停止语音识别
if (isVoiceRecognitionActive.value) {
stopVoiceRecognition();
}
}
/// 处理chatHistory事件
void _handleChatHistoryEvent(ChatHistoryEvent event) {
Logger.info('收到后台聊天历史事件: agentId=${event.agentId}');
// 检查是否是当前Agent的消息
if (event.agentId != agent.id) {
Logger.info('收到的消息不属于当前Agent,忽略');
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), // 确保助手消息时间戳晚于用户消息
);
// 检查是否已存在相同内容的消息(避免重复添加)
final bool userMsgExists = messages.any((m) =>
m.role == userMsg.role &&
m.content == userMsg.content &&
(m.timestamp.millisecondsSinceEpoch - userMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
final bool assistantMsgExists = messages.any((m) =>
m.role == assistantMsg.role &&
m.content == assistantMsg.content &&
(m.timestamp.millisecondsSinceEpoch - assistantMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
// 如果消息不存在,则添加到聊天记录中
if (!userMsgExists) {
messages.add(userMsg);
}
if (!assistantMsgExists) {
messages.add(assistantMsg);
}
// 如果有新消息添加,刷新列表并滚动到底部
if (!userMsgExists || !assistantMsgExists) {
messages.refresh();
}
_scrollToBottom();
}
@override
void onClose() {
Logger.info('ChatController onClose');
@ -107,6 +199,7 @@ class ChatController extends GetxController {
scrollController.dispose();
_aiResponseSubscription?.cancel();
_recognitionSubscription?.cancel();
_voiceInteractionSubscription?.cancel();
// 停止TTS
_ttsService.stop();
@ -321,6 +414,7 @@ class ChatController extends GetxController {
Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
// 初始化语音识别
await _asrService.initialize(language: 'zh-CN');

3
lib/modules/explore/controllers/explore_controller.dart

@ -1,6 +1,6 @@
import 'package:get/get.dart';
import '../../../data/providers/agent_provider.dart';
import '../../../data/services/voice_interaction_service.dart';
class ChatItem {
final String id;
@ -28,4 +28,5 @@ class ExploreController extends GetxController {
agents.map((agent) => ChatItem(id: agent.id)).toList(),
);
}
}

3
lib/modules/home/controllers/home_controller.dart

@ -2,6 +2,7 @@ import 'package:get/get.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../data/services/voice_interaction_service.dart';
enum ConnectStatus {
initial, // 初始状态,显示添加设备
@ -25,8 +26,10 @@ class HomeController extends GetxController {
void onInit() {
super.onInit();
_connectStatus.value = ConnectStatus.initial;
VoiceInteractionService.to.initialize();
}
@override
void onClose() {
_isDisposed = true;

6
lib/modules/translation/controllers/translation_controller.dart

@ -7,7 +7,6 @@ import '../../../data/services/volcano_translation_service.dart';
import '../../../data/services/azure_asr_service.dart';
import '../../../data/services/azure_tts_service.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/voice_interaction_service.dart';
class TranslationController extends GetxController {
// Services
@ -81,11 +80,6 @@ class TranslationController extends GetxController {
void onInit() {
super.onInit();
// 暂停后台语音交互服务,避免与翻译页面的语音功能相互干扰
if (Get.isRegistered<VoiceInteractionService>()) {
VoiceInteractionService.to.pauseVoiceInteraction();
Logger.info('已暂停后台语音交互服务');
}
// 加载历史记录
loadTranslationHistory();

Loading…
Cancel
Save