diff --git a/lib/data/models/appconfig.dart b/lib/data/models/appconfig.dart new file mode 100644 index 000000000..1c04272a6 --- /dev/null +++ b/lib/data/models/appconfig.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:deep_voice/data/models/appconfig_model.dart'; + +class AppConfig { + static late UserGetAppConfigResp config; + + // 可选:提供初始化方法 + static void initialize(UserGetAppConfigResp resp) { + config = resp; + } + + //环境变量 + static String? env(String key) { + return config.env[key]; + } + + //mcp配置 + static dynamic mcpConfig() { + return {"mcpServers": config.mcps}; + } + + //智能体配置 + static String agent(String agentId) { + final foundAgent = config.agents.firstWhere((agent) => agent.id == agentId); + return jsonEncode((foundAgent)); + } +} diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 399569d60..56f2841ac 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:get/get.dart'; import 'package:ble_service/ble_service.dart'; import 'package:deep_voice/core/utils/logger.dart'; @@ -383,17 +384,28 @@ class BleManager extends GetxService { Future startBleService() async { try { // 从.env文件直接读取环境变量 - final azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; - final azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? 'eastasia'; - final openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; + // final azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; + // final azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? 'eastasia'; + // final openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; + // final openaiBaseUrl = + // dotenv.env['OPENAI_BASE_URL'] ?? 'https://api.openai.com/v1'; + // final openaiModel = dotenv.env['OPENAI_MODEL'] ?? 'gpt-3.5-turbo'; + // 从Flutter资源包中加载.mcp.json + // final mcpConfig = await rootBundle.loadString('.mcp.json'); + + // 验证JSON格式 + // final jsonData = jsonDecode(mcpConfig); + + final azureSpeechKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; + final azureSpeechRegion = + AppConfig.env('AZURE_SPEECH_REGION') ?? 'eastasia'; + final openaiApiKey = AppConfig.env('OPENAI_API_KEY') ?? ''; final openaiBaseUrl = - dotenv.env['OPENAI_BASE_URL'] ?? 'https://api.openai.com/v1'; - final openaiModel = dotenv.env['OPENAI_MODEL'] ?? 'gpt-3.5-turbo'; - // 从Flutter资源包中加载.mcp.json - final mcpConfig = await rootBundle.loadString('.mcp.json'); + AppConfig.env('OPENAI_BASE_URL') ?? 'https://api.openai.com/v1'; + final openaiModel = AppConfig.env('OPENAI_MODEL') ?? 'gpt-3.5-turbo'; + // 从Flutter资源包中加载.mcp.json + final jsonData = AppConfig.mcpConfig(); - // 验证JSON格式 - final jsonData = jsonDecode(mcpConfig); final config = { // 语音代理配置参数 'azureSpeechKey': azureSpeechKey, diff --git a/lib/data/services/open_ai_service_adapter.dart b/lib/data/services/open_ai_service_adapter.dart index cd3adca7d..2a88ac09e 100644 --- a/lib/data/services/open_ai_service_adapter.dart +++ b/lib/data/services/open_ai_service_adapter.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:convert'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:deep_voice/data/models/appconfig_model.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get_storage/get_storage.dart'; @@ -103,10 +104,10 @@ class OpenAIServiceAdapter implements AiService { // final apiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; // final baseUrl = dotenv.env['OPENAI_BASE_URL'] ?? ''; // final model = dotenv.env['OPENAI_MODEL'] ?? ''; - final _env = _storage.read("ENV") as Map; - final apiKey = _env['OPENAI_API_KEY'] ?? ''; - final baseUrl = _env['OPENAI_BASE_URL'] ?? ''; - final model = _env['OPENAI_MODEL'] ?? ''; + // final _env = _storage.read("ENV") as Map; + final apiKey = AppConfig.env('OPENAI_API_KEY') ?? ''; + final baseUrl = AppConfig.env('OPENAI_BASE_URL') ?? ''; + final model = AppConfig.env('OPENAI_MODEL') ?? ''; printInfo(info: '从.env读取OpenAI配置'); printInfo(info: '基础URL: $baseUrl'); diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index 7cfcf6d2a..d15038a97 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:flutter/services.dart'; import 'package:get_storage/get_storage.dart'; import '../../../core/utils/logger.dart'; @@ -20,7 +21,7 @@ class AzureAsrService extends GetxService implements AsrService { static const MethodChannel _channel = MethodChannel('azure_speech/asr'); static const EventChannel _eventChannel = EventChannel('azure_speech/asr_events'); - final GetStorage _storage = GetStorage(); + // final GetStorage _storage = GetStorage(); bool _isInitialized = false; late final String _subscriptionKey; late final String _serviceRegion; @@ -51,9 +52,9 @@ class AzureAsrService extends GetxService implements AsrService { /// 从环境变量加载配置 void _loadConfig() { - final _env = _storage.read("ENV") as Map; - _subscriptionKey = _env['AZURE_SPEECH_KEY'] ?? ''; - _serviceRegion = _env['AZURE_SPEECH_REGION'] ?? ''; + // final _env = _storage.read("ENV") as Map; + _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; + _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { throw Exception( @@ -312,4 +313,31 @@ class AzureAsrService extends GetxService implements AsrService { _isInitialized = false; } } + + @override + Future pushAudioData(Uint8List data) async { + if (!_isInitialized) { + await initialize(useExternalAudio: true); + } + + if (_audioSourceType != AudioSourceType.external) { + Logger.error('当前非外部音频模式,不能推送音频数据'); + return false; + } + + try { + final bool result = await _channel.invokeMethod('pushAudioData', { + 'audioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer + }); + + if (!result) { + throw Exception('推送音频数据失败'); + } + + return true; + } catch (e) { + Logger.error('推送音频数据失败: $e'); + return false; + } + } } diff --git a/lib/data/services/speech_impl/azure_tts_service.dart b/lib/data/services/speech_impl/azure_tts_service.dart index cf6c98bf7..2e24a90b8 100644 --- a/lib/data/services/speech_impl/azure_tts_service.dart +++ b/lib/data/services/speech_impl/azure_tts_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; @@ -13,22 +14,24 @@ import '../tts_service.dart'; class AzureTtsService extends GetxService implements TtsService { static final AzureTtsService to = Get.put(AzureTtsService()); static const MethodChannel _channel = MethodChannel('azure_speech/tts'); - static const EventChannel _eventChannel = EventChannel('azure_speech/tts_events'); - - final GetStorage _storage = GetStorage(); + static const EventChannel _eventChannel = + EventChannel('azure_speech/tts_events'); + + // final GetStorage _storage = GetStorage(); bool _isInitialized = false; late final String _subscriptionKey; late final String _serviceRegion; // 当前设置 String _currentVoice = 'zh-CN-XiaoxiaoNeural'; - + // 语音合成队列 final List _textQueue = []; bool _isProcessingQueue = false; // 事件流 - final StreamController _eventStreamController = StreamController.broadcast(); + final StreamController _eventStreamController = + StreamController.broadcast(); StreamSubscription? _eventSubscription; // 可观察状态 @@ -53,9 +56,9 @@ class AzureTtsService extends GetxService implements TtsService { /// 从环境变量加载配置 void _loadConfig() { - final _env = _storage.read("ENV") as Map; - _subscriptionKey = _env['AZURE_SPEECH_KEY'] ?? ''; - _serviceRegion = _env['AZURE_SPEECH_REGION'] ?? ''; + // final _env = _storage.read("ENV") as Map; + _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; + _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; } /// 设置事件通道 @@ -74,50 +77,50 @@ class AzureTtsService extends GetxService implements TtsService { /// 处理TTS事件 void _handleTtsEvent(Map event) { final String eventType = event['type'] as String? ?? ''; - + // Logger.debug('收到TTS事件: $eventType, 数据: $event'); - + switch (eventType) { case 'synthesis_started': _isSpeaking.value = true; _eventStreamController.add(TtsEvent(type: TtsEventType.started)); break; - + case 'synthesizing': // _isSpeaking.value = true; // // 合成中事件暂时映射到started类型,因为TtsEventType中没有synthesizing类型 // _eventStreamController.add(TtsEvent(type: TtsEventType.started)); break; - + case 'synthesis_completed': _isSpeaking.value = false; _eventStreamController.add(TtsEvent(type: TtsEventType.completed)); - + // 如果有队列在处理中,继续处理下一项 if (_isProcessingQueue) { _processNextInQueue(); } break; - + case 'synthesis_canceled': _isSpeaking.value = false; final String reason = event['reason'] as String? ?? ''; - + _eventStreamController.add(TtsEvent.error('合成取消: $reason')); - + // 如果有队列在处理中,继续处理下一项(即使出错) if (_isProcessingQueue) { _processNextInQueue(); } break; - + case 'error': _isSpeaking.value = false; final String message = event['message'] as String? ?? '未知错误'; - + Logger.error('TTS错误: $message'); _eventStreamController.add(TtsEvent.error(message)); - + // 如果有队列在处理中,继续处理下一项(即使出错) if (_isProcessingQueue) { _processNextInQueue(); @@ -189,7 +192,7 @@ class AzureTtsService extends GetxService implements TtsService { final result = await _channel.invokeMethod('speakText', { 'text': text, }); - + return result == true; } catch (e) { Logger.error('语音合成失败: $e'); @@ -277,21 +280,24 @@ class AzureTtsService extends GetxService implements TtsService { // 等待队列处理完成 final completer = Completer(); - + // 监听事件流检查队列是否完成 final subscription = onEvent.listen((event) { // 当队列为空且不在处理中,且当前非说话状态,完成操作 - if (_textQueue.isEmpty && !_isProcessingQueue && !_isSpeaking.value && !completer.isCompleted) { + if (_textQueue.isEmpty && + !_isProcessingQueue && + !_isSpeaking.value && + !completer.isCompleted) { completer.complete(true); } }); - + // 如果已经处于空闲状态,直接完成 if (_textQueue.isEmpty && !_isProcessingQueue && !_isSpeaking.value) { subscription.cancel(); return true; } - + // 设置超时 final timeout = Timer(const Duration(seconds: 60), () { if (!completer.isCompleted) { @@ -299,14 +305,14 @@ class AzureTtsService extends GetxService implements TtsService { completer.complete(false); } }); - + // 等待完成 final result = await completer.future; - + // 清理 subscription.cancel(); timeout.cancel(); - + Logger.info('TTS队列播放完成'); return result; } catch (e) { @@ -320,11 +326,11 @@ class AzureTtsService extends GetxService implements TtsService { if (_textQueue.isEmpty || _isProcessingQueue) return; _isProcessingQueue = true; - + // 处理队列中的第一项 await _processNextInQueue(); } - + /// 处理队列中的下一项 Future _processNextInQueue() async { // 检查队列是否为空或已禁用 @@ -333,14 +339,14 @@ class AzureTtsService extends GetxService implements TtsService { _isProcessingQueue = false; return; } - + try { // 获取当前项但暂不从队列中移除 final text = _textQueue.first; - + // 开始合成 final success = await speakOnce(text); - + // 仅当合成操作失败时才从队列移除并继续处理下一项 // 成功的话会等待合成完成事件后自动处理下一项 if (!success) { @@ -353,12 +359,12 @@ class AzureTtsService extends GetxService implements TtsService { } } catch (e) { Logger.error('处理语音队列出错: $e'); - + // 出错时移除当前项并尝试继续 if (_textQueue.isNotEmpty) { _textQueue.removeAt(0); } - + // 如果队列中还有项目,继续处理 if (_textQueue.isNotEmpty) { await _processNextInQueue(); diff --git a/lib/data/services/spotify_service.dart b/lib/data/services/spotify_service.dart index 30adbfe97..f5f25d7fb 100644 --- a/lib/data/services/spotify_service.dart +++ b/lib/data/services/spotify_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:convert'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get_storage/get_storage.dart'; import 'package:spotify_sdk/spotify_sdk.dart'; @@ -12,7 +13,7 @@ import 'package:path_provider/path_provider.dart'; /// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件 class SpotifService { bool isInitialized = false; - final GetStorage _storage = GetStorage(); + // final GetStorage _storage = GetStorage(); late String SPOTIFY_CLIENT_ID; late String SPOTIFY_REDIRECT_URL; @@ -20,9 +21,9 @@ class SpotifService { /// 初始化OpenAI服务 Future initialize() async { if (!isInitialized) { - final _env = _storage.read("ENV") as Map; - SPOTIFY_CLIENT_ID = _env['SPOTIFY_CLIENT_ID'] ?? ''; - SPOTIFY_REDIRECT_URL = _env['SPOTIFY_REDIRECT_URL'] ?? ''; + // final _env = _storage.read("ENV") as Map; + SPOTIFY_CLIENT_ID = AppConfig.env('SPOTIFY_CLIENT_ID') ?? ''; + SPOTIFY_REDIRECT_URL = AppConfig.env('SPOTIFY_REDIRECT_URL') ?? ''; printInfo( info: "SpotifService----------- initialize SPOTIFY_CLIENT_ID:${SPOTIFY_CLIENT_ID} SPOTIFY_REDIRECT_URL:${SPOTIFY_REDIRECT_URL}"); diff --git a/lib/data/services/volcano_translation_service.dart b/lib/data/services/volcano_translation_service.dart index 11e19bb1c..0cbee7c01 100644 --- a/lib/data/services/volcano_translation_service.dart +++ b/lib/data/services/volcano_translation_service.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:math'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; @@ -22,7 +23,7 @@ class VolcanoTranslationService extends GetxService { // 语言管理器 final LanguageManager _languageManager = Get.find(); - final GetStorage _storage = GetStorage(); + // final GetStorage _storage = GetStorage(); // 构造函数 VolcanoTranslationService(); @@ -33,10 +34,13 @@ class VolcanoTranslationService extends GetxService { String? region, }) async { try { - final _env = _storage.read("ENV") as Map; - _accessKey = accessKey ?? _env['VOLCANO_TRANSLATION_ACCESS_KEY'] ?? ''; - _secretKey = secretKey ?? _env['VOLCANO_TRANSLATION_SECRET_KEY'] ?? ''; - _region = region ?? _env['VOLCANO_TRANSLATION_REGION'] ?? 'cn-north-1'; + // final _env = _storage.read("ENV") as Map; + _accessKey = + accessKey ?? AppConfig.env('VOLCANO_TRANSLATION_ACCESS_KEY') ?? ''; + _secretKey = + secretKey ?? AppConfig.env('VOLCANO_TRANSLATION_SECRET_KEY') ?? ''; + _region = + region ?? AppConfig.env('VOLCANO_TRANSLATION_REGION') ?? 'cn-north-1'; _service = 'translate'; _baseUrl = 'https://translate.volcengineapi.com'; diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index 02ec0467a..cc39b10f1 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -1,3 +1,4 @@ +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:deep_voice/data/models/appconfig_model.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; @@ -739,9 +740,10 @@ class LoginController extends GetxController { // 根据API设计调整 final resp = UserGetAppConfigResp.fromJson(response); // 存储用户信息(示例) - _storage.write('ENV', resp.env); - _storage.write('AGENTS', resp.agents); - _storage.write('MCPS', resp.mcps); + // _storage.write('ENV', resp.env); + // _storage.write('AGENTS', resp.agents); + // _storage.write('MCPS', resp.mcps); + AppConfig.initialize(resp); return true; } catch (e) { print('getAppConfig 取失败: $e'); diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 2af4890ea..e5fd78c59 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:amap_flutter/amap_flutter.dart'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:deep_voice/data/models/appconfig_model.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -132,10 +133,10 @@ class SplashController extends GetxController { final resp = UserGetAppConfigResp.fromJson(response); print('getAppConfig:进来了'); // 存储用户信息(示例) - _storage.write('ENV', resp.env); - _storage.write('AGENTS', resp.agents); - _storage.write('MCPS', resp.mcps); - + // _storage.write('ENV', resp.env); + // _storage.write('AGENTS', resp.agents); + // _storage.write('MCPS', resp.mcps); + AppConfig.initialize(resp); return true; } catch (e) { print('getAppConfig 取失败: $e'); diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt index d3b2efa9a..dce107f70 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt @@ -313,7 +313,7 @@ class MCPSubClient( "function" to functionMap )) - Log.d(TAG, "成功转换工具 ${tool.name()} 到OpenAI函数格式") + Log.d(TAG, "成功转换工具 ${tool.name()} 到OpenAI函数格式 $functionMap") } catch (e: Exception) { Log.e(TAG, "转换工具 ${tool.name()} 失败: ${e.message}") } diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt index 304771e21..43a9efcfc 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt @@ -337,6 +337,8 @@ class OpenAIService(private val context: Context? = null) { // 获取参数 val argumentsJson = functionCall.getString("arguments") + + Log.e(TAG, "自动处理MCP工具调用: name:${name} argumentsJson:${argumentsJson}") val arguments = mcpClient?.parseJsonArguments(argumentsJson) ?: mapOf() // 调用工具(统一通过MCPClient处理) @@ -607,7 +609,7 @@ class OpenAIService(private val context: Context? = null) { for (i in 0 until toolCalls.length()) { val toolCall = toolCalls.getJSONObject(i) val index = toolCall.getInt("index") - + Log.e(TAG, "解析道AI需要调用工具: $toolCall") // 创建或获取现有的工具调用信息 val toolCallInfo = finalToolCalls.getOrPut(index) { ToolCallInfo() }