|
|
|
@ -1,8 +1,10 @@ |
|
|
|
import 'dart:async'; |
|
|
|
import 'dart:convert'; |
|
|
|
import 'package:deep_voice/data/models/appconfig_model.dart'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart'; |
|
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
|
import '../models/events/voice_interaction_event.dart'; |
|
|
|
import '../../core/utils/logger.dart'; |
|
|
|
import '../../modules/chat/models/message_model.dart'; |
|
|
|
@ -14,22 +16,26 @@ import 'chat_history_service.dart'; |
|
|
|
/// 管理与平台原生语音交互服务的通信,提供统一的接口供应用使用 |
|
|
|
class VoiceInteractionService extends GetxService { |
|
|
|
static VoiceInteractionService get to => Get.find<VoiceInteractionService>(); |
|
|
|
|
|
|
|
final GetStorage _storage = GetStorage(); |
|
|
|
// 方法通道 |
|
|
|
static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction'); |
|
|
|
static const MethodChannel _channel = |
|
|
|
MethodChannel('com.deep_voice.voice_interaction'); |
|
|
|
|
|
|
|
// 事件通道 |
|
|
|
static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events'); |
|
|
|
static const EventChannel _eventChannel = |
|
|
|
EventChannel('com.deep_voice.voice_interaction_events'); |
|
|
|
|
|
|
|
// 服务状态 |
|
|
|
final _isServiceRunning = false.obs; |
|
|
|
bool get isServiceRunning => _isServiceRunning.value; |
|
|
|
|
|
|
|
// 流控制器 |
|
|
|
final _eventStreamController = StreamController<VoiceInteractionEvent>.broadcast(); |
|
|
|
final _eventStreamController = |
|
|
|
StreamController<VoiceInteractionEvent>.broadcast(); |
|
|
|
|
|
|
|
// 事件流 |
|
|
|
Stream<VoiceInteractionEvent> get eventStream => _eventStreamController.stream; |
|
|
|
Stream<VoiceInteractionEvent> get eventStream => |
|
|
|
_eventStreamController.stream; |
|
|
|
|
|
|
|
// 事件通道订阅 |
|
|
|
StreamSubscription? _eventSubscription; |
|
|
|
@ -51,28 +57,36 @@ class VoiceInteractionService extends GetxService { |
|
|
|
// 聊天历史服务 |
|
|
|
late final ChatHistoryService _chatHistoryService; |
|
|
|
|
|
|
|
|
|
|
|
/// 设置事件通道 |
|
|
|
void _setupEventChannel() { |
|
|
|
_eventSubscription = _eventChannel |
|
|
|
.receiveBroadcastStream() |
|
|
|
.listen(_handleVoiceInteractionEvent, onError: (error) { |
|
|
|
Logger.error('语音交互事件通道错误: $error'); |
|
|
|
}); |
|
|
|
Logger.error('语音交互事件通道错误: $error'); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/// 从环境变量加载配置 |
|
|
|
void _loadConfig() { |
|
|
|
_azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; |
|
|
|
_azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; |
|
|
|
_openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; |
|
|
|
_openaiBaseUrl = dotenv.env['OPENAI_BASE_URL'] ?? ''; |
|
|
|
_openaiModel = dotenv.env['OPENAI_MODEL'] ?? ''; |
|
|
|
_volcanoSpeechAppId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; |
|
|
|
_volcanoSpeechAppToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; |
|
|
|
// _azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; |
|
|
|
// _azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; |
|
|
|
// _openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; |
|
|
|
// _openaiBaseUrl = dotenv.env['OPENAI_BASE_URL'] ?? ''; |
|
|
|
// _openaiModel = dotenv.env['OPENAI_MODEL'] ?? ''; |
|
|
|
// _volcanoSpeechAppId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; |
|
|
|
// _volcanoSpeechAppToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; |
|
|
|
final _env = _storage.read("ENV") as Map<String, String>; |
|
|
|
_azureSpeechKey = _env['AZURE_SPEECH_KEY'] ?? ''; |
|
|
|
_azureSpeechRegion = _env['AZURE_SPEECH_REGION'] ?? ''; |
|
|
|
_openaiApiKey = _env['OPENAI_API_KEY'] ?? ''; |
|
|
|
_openaiBaseUrl = _env['OPENAI_BASE_URL'] ?? ''; |
|
|
|
_openaiModel = _env['OPENAI_MODEL'] ?? ''; |
|
|
|
_volcanoSpeechAppId = _env['VOLCANO_SPEECH_APP_ID'] ?? ''; |
|
|
|
_volcanoSpeechAppToken = _env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; |
|
|
|
|
|
|
|
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) { |
|
|
|
Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); |
|
|
|
Logger.warning( |
|
|
|
'未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); |
|
|
|
} |
|
|
|
|
|
|
|
// 加载MCP配置 |
|
|
|
@ -82,10 +96,14 @@ class VoiceInteractionService extends GetxService { |
|
|
|
/// 加载MCP配置 |
|
|
|
void _loadMcpConfig() { |
|
|
|
try { |
|
|
|
rootBundle.loadString('.mcp.json').then((config) { |
|
|
|
_mcpConfig = config; |
|
|
|
Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig'); |
|
|
|
}); |
|
|
|
final mcps = _storage.read("MCPS") as Map<String, DBMCPServer>; |
|
|
|
String config = jsonEncode(mcps); |
|
|
|
_mcpConfig = config; |
|
|
|
Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig'); |
|
|
|
// rootBundle.loadString('.mcp.json').then((config) { |
|
|
|
// _mcpConfig = config; |
|
|
|
// Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig'); |
|
|
|
// }); |
|
|
|
} catch (e) { |
|
|
|
Logger.warning('加载MCP配置失败: $e'); |
|
|
|
_mcpConfig = null; |
|
|
|
@ -115,7 +133,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
// 聊天历史更新事件 |
|
|
|
final String agentId = eventMap['agentId'] as String? ?? ''; |
|
|
|
final String userMessage = eventMap['userMessage'] as String? ?? ''; |
|
|
|
final String assistantMessage = eventMap['assistantMessage'] as String? ?? ''; |
|
|
|
final String assistantMessage = |
|
|
|
eventMap['assistantMessage'] as String? ?? ''; |
|
|
|
|
|
|
|
final chatHistoryEvent = ChatHistoryEvent( |
|
|
|
agentId: agentId, |
|
|
|
@ -134,7 +153,6 @@ class VoiceInteractionService extends GetxService { |
|
|
|
Logger.info('收到进入翻译模式事件,正在导航到翻译界面'); |
|
|
|
_navigateToTranslation(); |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -143,7 +161,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
void _navigateToTranslation() { |
|
|
|
try { |
|
|
|
// 使用GetX导航到翻译页面 |
|
|
|
Get.offNamedUntil(Routes.translation, (route) => route.settings.name == Routes.home); |
|
|
|
Get.offNamedUntil( |
|
|
|
Routes.translation, (route) => route.settings.name == Routes.home); |
|
|
|
Logger.info('已导航到翻译界面'); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('导航到翻译界面失败: $e'); |
|
|
|
@ -151,7 +170,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
} |
|
|
|
|
|
|
|
/// 保存聊天历史 |
|
|
|
Future<void> _saveChatHistory(String agentId, String userMessage, String assistantMessage) async { |
|
|
|
Future<void> _saveChatHistory( |
|
|
|
String agentId, String userMessage, String assistantMessage) async { |
|
|
|
try { |
|
|
|
final timestamp = DateTime.now().millisecondsSinceEpoch; |
|
|
|
|
|
|
|
@ -165,7 +185,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
final assistantMsg = Message( |
|
|
|
role: 'assistant', |
|
|
|
content: assistantMessage, |
|
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息 |
|
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch( |
|
|
|
timestamp + 1), // 确保助手消息时间戳晚于用户消息 |
|
|
|
); |
|
|
|
|
|
|
|
// 加载现有历史记录 |
|
|
|
@ -247,7 +268,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
params['mcp_config'] = _mcpConfig; |
|
|
|
} |
|
|
|
|
|
|
|
final result = await _channel.invokeMethod<bool>('startService', params) ?? false; |
|
|
|
final result = |
|
|
|
await _channel.invokeMethod<bool>('startService', params) ?? false; |
|
|
|
|
|
|
|
if (result) { |
|
|
|
_isServiceRunning.value = true; |
|
|
|
@ -287,7 +309,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
/// 检查服务是否运行 |
|
|
|
Future<bool> checkServiceStatus() async { |
|
|
|
try { |
|
|
|
final bool result = await _channel.invokeMethod<bool>('isServiceRunning') ?? false; |
|
|
|
final bool result = |
|
|
|
await _channel.invokeMethod<bool>('isServiceRunning') ?? false; |
|
|
|
_isServiceRunning.value = result; |
|
|
|
return result; |
|
|
|
} catch (e) { |
|
|
|
@ -301,7 +324,8 @@ class VoiceInteractionService extends GetxService { |
|
|
|
try { |
|
|
|
Logger.info('暂停后台语音交互...'); |
|
|
|
|
|
|
|
final result = await _channel.invokeMethod<bool>('pauseVoiceInteraction') ?? false; |
|
|
|
final result = |
|
|
|
await _channel.invokeMethod<bool>('pauseVoiceInteraction') ?? false; |
|
|
|
|
|
|
|
if (result) { |
|
|
|
Logger.info('语音交互已暂停'); |
|
|
|
|