Browse Source

上传获取服务器的配置信息

weicu
liwei1dao 2 years ago
parent
commit
676cabd4f0
  1. 30
      lib/data/models/appconfig_model.dart
  2. 37
      lib/data/models/appconfig_model.g.dart
  3. 9
      lib/data/services/network/api.dart
  4. 45
      lib/data/services/open_ai_service_adapter.dart
  5. 44
      lib/data/services/speech_impl/azure_asr_service.dart
  6. 56
      lib/data/services/speech_impl/azure_tts_service.dart
  7. 80
      lib/data/services/voice_interaction_service.dart
  8. 24
      lib/modules/login/controllers/login_controller.dart
  9. 23
      lib/modules/splash/controllers/splash_controller.dart
  10. 2
      local_plugins/open_ai_service/android/build.gradle.kts
  11. 24
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt
  12. 3
      pubspec.yaml

30
lib/data/models/appconfig_model.dart

@ -0,0 +1,30 @@
import 'package:json_annotation/json_annotation.dart';
part 'appconfig_model.g.dart'; // 生成的文件名
@JsonSerializable()
class UserGetAppConfigResp {
final Map<String, String> env;
final Map<String, DBMCPServer> mcps;
UserGetAppConfigResp({required this.env, required this.mcps});
factory UserGetAppConfigResp.fromJson(Map<String, dynamic> json) =>
_$UserGetAppConfigRespFromJson(json);
Map<String, dynamic> toJson() => _$UserGetAppConfigRespToJson(this);
}
@JsonSerializable()
class DBMCPServer {
final String servername;
final String url;
final List<String>? tools;
DBMCPServer({
required this.servername,
required this.url,
required this.tools,
});
factory DBMCPServer.fromJson(Map<String, dynamic> json) =>
_$DBMCPServerFromJson(json);
Map<String, dynamic> toJson() => _$DBMCPServerToJson(this);
}

37
lib/data/models/appconfig_model.g.dart

@ -0,0 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'appconfig_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UserGetAppConfigResp _$UserGetAppConfigRespFromJson(
Map<String, dynamic> json) =>
UserGetAppConfigResp(
env: Map<String, String>.from(json['env'] as Map),
mcps: (json['mcps'] as Map<String, dynamic>).map(
(k, e) => MapEntry(k, DBMCPServer.fromJson(e as Map<String, dynamic>)),
),
);
Map<String, dynamic> _$UserGetAppConfigRespToJson(
UserGetAppConfigResp instance) =>
<String, dynamic>{
'env': instance.env,
'mcps': instance.mcps,
};
DBMCPServer _$DBMCPServerFromJson(Map<String, dynamic> json) => DBMCPServer(
servername: json['servername'] as String,
url: json['url'] as String,
tools:
(json['tools'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
Map<String, dynamic> _$DBMCPServerToJson(DBMCPServer instance) =>
<String, dynamic>{
'servername': instance.servername,
'url': instance.url,
'tools': instance.tools,
};

9
lib/data/services/network/api.dart

@ -64,4 +64,13 @@ class Api {
params: params, params: params,
); );
} }
//app配置
static getappconfig([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_getappconfig',
params: params,
);
}
} }

45
lib/data/services/open_ai_service_adapter.dart

@ -1,7 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
import 'package:deep_voice/data/models/appconfig_model.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
import 'package:open_ai_service/open_ai_service.dart'; import 'package:open_ai_service/open_ai_service.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'ai_service.dart'; import 'ai_service.dart';
@ -26,6 +28,7 @@ class OpenAIServiceAdapter implements AiService {
StreamSubscription<OpenAIEvent>? _eventSubscription; StreamSubscription<OpenAIEvent>? _eventSubscription;
final StreamController<StreamEvent> _tokenStreamController = final StreamController<StreamEvent> _tokenStreamController =
StreamController<StreamEvent>.broadcast(); StreamController<StreamEvent>.broadcast();
final GetStorage _storage = GetStorage();
/// 构造函数 /// 构造函数
OpenAIServiceAdapter() { OpenAIServiceAdapter() {
@ -97,9 +100,13 @@ class OpenAIServiceAdapter implements AiService {
Future<bool> initialize() async { Future<bool> initialize() async {
try { try {
// 从.env文件中读取配置 // 从.env文件中读取配置
final apiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; // final apiKey = dotenv.env['OPENAI_API_KEY'] ?? '';
final baseUrl = dotenv.env['OPENAI_BASE_URL'] ?? ''; // final baseUrl = dotenv.env['OPENAI_BASE_URL'] ?? '';
final model = dotenv.env['OPENAI_MODEL'] ?? ''; // final model = dotenv.env['OPENAI_MODEL'] ?? '';
final _env = _storage.read("ENV") as Map<String, String>;
final apiKey = _env['OPENAI_API_KEY'] ?? '';
final baseUrl = _env['OPENAI_BASE_URL'] ?? '';
final model = _env['OPENAI_MODEL'] ?? '';
printInfo(info: '从.env读取OpenAI配置'); printInfo(info: '从.env读取OpenAI配置');
printInfo(info: '基础URL: $baseUrl'); printInfo(info: '基础URL: $baseUrl');
@ -109,24 +116,24 @@ class OpenAIServiceAdapter implements AiService {
printError(info: '错误: OpenAI API密钥未配置,请在.env文件中设置OPENAI_API_KEY'); printError(info: '错误: OpenAI API密钥未配置,请在.env文件中设置OPENAI_API_KEY');
return false; return false;
} }
final _mcps = _storage.read("MCPS") as Map<String, DBMCPServer>;
// 读取.mcp.json文件 // 读取.mcp.json文件
String mcpConfig = ''; String mcpConfig = jsonEncode(_mcps);
try { // try {
// 从Flutter资源包中加载.mcp.json // // 从Flutter资源包中加载.mcp.json
mcpConfig = await rootBundle.loadString('.mcp.json'); // mcpConfig = await rootBundle.loadString('.mcp.json');
printInfo(info: '成功从资源包加载.mcp.json配置文件'); // printInfo(info: '成功从资源包加载.mcp.json配置文件');
// 验证JSON格式 // // 验证JSON格式
final jsonData = jsonDecode(mcpConfig); // final jsonData = jsonDecode(mcpConfig);
if (jsonData is Map && jsonData.containsKey('mcpServers')) { // if (jsonData is Map && jsonData.containsKey('mcpServers')) {
printInfo(info: '解析到有效的mcpServers配置'); // printInfo(info: '解析到有效的mcpServers配置');
} else { // } else {
printInfo(info: '.mcp.json内容格式不正确,期望包含mcpServers字段'); // printInfo(info: '.mcp.json内容格式不正确,期望包含mcpServers字段');
} // }
} catch (e) { // } catch (e) {
printError(info: '加载.mcp.json资源文件时出错: $e'); // printError(info: '加载.mcp.json资源文件时出错: $e');
} // }
// 初始化OpenAI服务 // 初始化OpenAI服务
final result = await _openAIService.initialize( final result = await _openAIService.initialize(

44
lib/data/services/speech_impl/azure_asr_service.dart

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../asr_service.dart'; import '../asr_service.dart';
@ -11,8 +12,9 @@ import '../asr_service.dart';
class AzureAsrService extends GetxService implements AsrService { class AzureAsrService extends GetxService implements AsrService {
static final AzureAsrService to = Get.put(AzureAsrService()); static final AzureAsrService to = Get.put(AzureAsrService());
static const MethodChannel _channel = MethodChannel('azure_speech/asr'); static const MethodChannel _channel = MethodChannel('azure_speech/asr');
static const EventChannel _eventChannel = EventChannel('azure_speech/asr_events'); static const EventChannel _eventChannel =
EventChannel('azure_speech/asr_events');
final GetStorage _storage = GetStorage();
bool _isInitialized = false; bool _isInitialized = false;
late final String _subscriptionKey; late final String _subscriptionKey;
late final String _serviceRegion; late final String _serviceRegion;
@ -40,23 +42,23 @@ class AzureAsrService extends GetxService implements AsrService {
/// 从环境变量加载配置 /// 从环境变量加载配置
void _loadConfig() { void _loadConfig() {
_subscriptionKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; final _env = _storage.read("ENV") as Map<String, String>;
_serviceRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; _subscriptionKey = _env['AZURE_SPEECH_KEY'] ?? '';
_serviceRegion = _env['AZURE_SPEECH_REGION'] ?? '';
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
throw Exception('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); throw Exception(
'未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
} }
} }
/// 设置事件通道 /// 设置事件通道
void _setupEventChannel() { void _setupEventChannel() {
_eventSubscription = _eventChannel _eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) {
.receiveBroadcastStream() if (event is Map) {
.listen((event) { _handleRecognitionEvent(event);
if (event is Map) { }
_handleRecognitionEvent(event); }, onError: _handleRecognitionError);
}
}, onError: _handleRecognitionError);
} }
@override @override
@ -64,7 +66,8 @@ class AzureAsrService extends GetxService implements AsrService {
List<String>? supportedLanguages, List<String>? supportedLanguages,
}) async { }) async {
try { try {
final List<String> languages = supportedLanguages ?? _defaultSupportedLanguages; final List<String> languages =
supportedLanguages ?? _defaultSupportedLanguages;
// 检查是否需要重新初始化 // 检查是否需要重新初始化
if (_isInitialized) { if (_isInitialized) {
@ -99,7 +102,8 @@ class AzureAsrService extends GetxService implements AsrService {
if (result is Map) { if (result is Map) {
final String text = result['text'] as String? ?? ''; final String text = result['text'] as String? ?? '';
final String detectedLanguage = result['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
result['detectedLanguage'] as String? ?? '';
_latestRecognizedText = text; _latestRecognizedText = text;
_latestDetectedLanguage = detectedLanguage; _latestDetectedLanguage = detectedLanguage;
@ -134,7 +138,8 @@ class AzureAsrService extends GetxService implements AsrService {
_eventStreamController = StreamController<RecognitionEvent>.broadcast(); _eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别 // 开始连续识别
final bool result = await _channel.invokeMethod('startContinuousRecognition'); final bool result =
await _channel.invokeMethod('startContinuousRecognition');
if (!result) { if (!result) {
_cleanupEventStream(); _cleanupEventStream();
@ -157,7 +162,8 @@ class AzureAsrService extends GetxService implements AsrService {
} }
try { try {
final bool result = await _channel.invokeMethod('stopContinuousRecognition'); final bool result =
await _channel.invokeMethod('stopContinuousRecognition');
_cleanupEventStream(); _cleanupEventStream();
return result; return result;
} catch (e) { } catch (e) {
@ -185,7 +191,8 @@ class AzureAsrService extends GetxService implements AsrService {
switch (eventType) { switch (eventType) {
case 'result': case 'result':
final String text = eventMap['text'] as String? ?? ''; final String text = eventMap['text'] as String? ?? '';
final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
eventMap['detectedLanguage'] as String? ?? '';
_latestRecognizedText = text; _latestRecognizedText = text;
_latestDetectedLanguage = detectedLanguage; _latestDetectedLanguage = detectedLanguage;
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
@ -197,7 +204,8 @@ class AzureAsrService extends GetxService implements AsrService {
case 'recognizing': case 'recognizing':
final String text = eventMap['text'] as String? ?? ''; final String text = eventMap['text'] as String? ?? '';
final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
eventMap['detectedLanguage'] as String? ?? '';
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.intermediateResult, type: RecognitionEventType.intermediateResult,
text: text, text: text,

56
lib/data/services/speech_impl/azure_tts_service.dart

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../tts_service.dart'; import '../tts_service.dart';
@ -13,7 +14,7 @@ import '../tts_service.dart';
class AzureTtsService extends GetxService implements TtsService { class AzureTtsService extends GetxService implements TtsService {
static final AzureTtsService to = Get.put(AzureTtsService()); static final AzureTtsService to = Get.put(AzureTtsService());
static const MethodChannel _channel = MethodChannel('azure_speech/tts'); static const MethodChannel _channel = MethodChannel('azure_speech/tts');
final GetStorage _storage = GetStorage();
bool _isInitialized = false; bool _isInitialized = false;
late final String _subscriptionKey; late final String _subscriptionKey;
late final String _serviceRegion; late final String _serviceRegion;
@ -71,25 +72,25 @@ class AzureTtsService extends GetxService implements TtsService {
@override @override
List<String> get supportedVoices => _supportedVoices; List<String> get supportedVoices => _supportedVoices;
AzureTtsService() { AzureTtsService() {
_loadConfig(); _loadConfig();
} }
/// 从环境变量加载配置 /// 从环境变量加载配置
void _loadConfig() { void _loadConfig() {
_subscriptionKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; final _env = _storage.read("ENV") as Map<String, String>;
_serviceRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; _subscriptionKey = _env['AZURE_SPEECH_KEY'] ?? '';
_serviceRegion = _env['AZURE_SPEECH_REGION'] ?? '';
} }
@override @override
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
}) async { }) async {
// 检查是否需要重新初始化 // 检查是否需要重新初始化
if (_isInitialized) { if (_isInitialized) {
await dispose(); await dispose();
} }
try { try {
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
@ -97,22 +98,17 @@ class AzureTtsService extends GetxService implements TtsService {
return false; return false;
} }
// 更新支持的语音列表(如果提供) // 更新支持的语音列表(如果提供)
final String language = supportedLanguages?.first ?? 'zh-CN'; final String language = supportedLanguages?.first ?? 'zh-CN';
final result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey,
'region': _serviceRegion,
'language': language,
});
final result = await _channel.invokeMethod('initialize', { _isInitialized = true;
'subscriptionKey': _subscriptionKey, return result;
'region': _serviceRegion,
'language': language,
});
_isInitialized = true;
return result;
} catch (e) { } catch (e) {
Logger.error('初始化失败: $e'); Logger.error('初始化失败: $e');
return false; return false;
@ -138,7 +134,8 @@ class AzureTtsService extends GetxService implements TtsService {
} }
/// 设置语音参数 /// 设置语音参数
Future<bool> setSpeechParams({int rate = 0, int pitch = 0, int volume = 100}) async { Future<bool> setSpeechParams(
{int rate = 0, int pitch = 0, int volume = 100}) async {
if (!_isInitialized) await initialize(); if (!_isInitialized) await initialize();
try { try {
@ -208,7 +205,8 @@ class AzureTtsService extends GetxService implements TtsService {
} }
/// 等待语音播放完成 /// 等待语音播放完成
Future<void> waitForSpeechCompletion({Duration timeout = const Duration(seconds: 30)}) async { Future<void> waitForSpeechCompletion(
{Duration timeout = const Duration(seconds: 30)}) async {
// 首先检查当前状态 // 首先检查当前状态
_checkSpeakingStatus(); _checkSpeakingStatus();
if (!_speakingCache) return; if (!_speakingCache) return;
@ -242,8 +240,7 @@ class AzureTtsService extends GetxService implements TtsService {
// 使用正则表达式匹配句子,包括结束符号 // 使用正则表达式匹配句子,包括结束符号
// 匹配任意字符,直到遇到句子结束符号 // 匹配任意字符,直到遇到句子结束符号
final sentenceRegex = RegExp( final sentenceRegex = RegExp(
r'([^。.!!??;;:\n\r]+[。.!!??;;::\n\r]|[^。.!!??;;:\n\r]+(?:\.{3,}|…)|[^。.!!??;;:\n\r]+["」』])' r'([^。.!!??;;:\n\r]+[。.!!??;;::\n\r]|[^。.!!??;;:\n\r]+(?:\.{3,}|…)|[^。.!!??;;:\n\r]+["」』])');
);
bool hasProcessed = false; bool hasProcessed = false;
@ -347,7 +344,9 @@ class AzureTtsService extends GetxService implements TtsService {
final item = _textQueue.removeAt(0); final item = _textQueue.removeAt(0);
if (item.rate != _currentRate || item.pitch != _currentPitch || item.volume != _currentVolume) { if (item.rate != _currentRate ||
item.pitch != _currentPitch ||
item.volume != _currentVolume) {
await setSpeechParams( await setSpeechParams(
rate: item.rate, rate: item.rate,
pitch: item.pitch, pitch: item.pitch,
@ -400,7 +399,11 @@ class AzureTtsService extends GetxService implements TtsService {
final pitchValue = (pitch ?? _currentPitch).clamp(-100, 100); final pitchValue = (pitch ?? _currentPitch).clamp(-100, 100);
final volumeValue = (volume ?? _currentVolume).clamp(0, 100); final volumeValue = (volume ?? _currentVolume).clamp(0, 100);
final rateStr = rateValue == 0 ? '0%' : rateValue < 0 ? '${(rateValue * 0.9).round()}%' : '$rateValue%'; final rateStr = rateValue == 0
? '0%'
: rateValue < 0
? '${(rateValue * 0.9).round()}%'
: '$rateValue%';
final pitchStr = pitchValue == 0 ? '0%' : '${(pitchValue * 0.5).round()}%'; final pitchStr = pitchValue == 0 ? '0%' : '${(pitchValue * 0.5).round()}%';
final volumeStr = "$volumeValue%"; final volumeStr = "$volumeValue%";
@ -448,7 +451,6 @@ class AzureTtsService extends GetxService implements TtsService {
return _speakingCache; return _speakingCache;
} }
} }
} }
/// 语音合成项目 /// 语音合成项目

80
lib/data/services/voice_interaction_service.dart

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

24
lib/modules/login/controllers/login_controller.dart

@ -1,3 +1,4 @@
import 'package:deep_voice/data/models/appconfig_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logger/web.dart'; import 'package:logger/web.dart';
@ -692,7 +693,8 @@ class LoginController extends GetxController {
//final prefs = await GetStorage(); //final prefs = await GetStorage();
await _storage.write( await _storage.write(
'user', jsonEncode(user.toJson())); // 使用 user.toJson() 'user', jsonEncode(user.toJson())); // 使用 user.toJson()
//获取app的配置信息
await getAppConfig();
Get.offAllNamed(Routes.pairing); Get.offAllNamed(Routes.pairing);
// } else { // } else {
// throw FormatException('API 响应缺少 user 字段'); // throw FormatException('API 响应缺少 user 字段');
@ -722,4 +724,24 @@ class LoginController extends GetxController {
colorText: Colors.blue, colorText: Colors.blue,
); );
} }
Future<bool> getAppConfig() async {
try {
print('getAppConfig:进来了');
//if (User.getname() != "") {
final response = await Api.getappconfig();
print('getAppConfig: $response');
if (response == null) return false;
print('getAppConfig:进来了');
// 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response);
// 存储用户信息(示例)
_storage.write('ENV', resp.env);
_storage.write('MCPS', resp.mcps);
return true;
} catch (e) {
print('getAppConfig 取失败: $e');
return false;
}
}
} }

23
lib/modules/splash/controllers/splash_controller.dart

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:amap_flutter/amap_flutter.dart'; import 'package:amap_flutter/amap_flutter.dart';
import 'package:deep_voice/data/models/appconfig_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
@ -88,6 +89,7 @@ class SplashController extends GetxController {
isLoggedIn = await getLoginToken(); isLoggedIn = await getLoginToken();
} }
if (isLoggedIn) { if (isLoggedIn) {
await getAppConfig();
Get.offAllNamed(Routes.pairing); Get.offAllNamed(Routes.pairing);
} else { } else {
Get.offAllNamed(Routes.login); // 跳转到登录页 Get.offAllNamed(Routes.login); // 跳转到登录页
@ -118,4 +120,25 @@ class SplashController extends GetxController {
return false; return false;
} }
} }
Future<bool> getAppConfig() async {
try {
print('getAppConfig:进来了');
//if (User.getname() != "") {
final response = await Api.getappconfig();
print('getAppConfig: $response');
if (response == null) return false;
// 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response);
print('getAppConfig:进来了');
// 存储用户信息(示例)
_storage.write('ENV', resp.env);
_storage.write('MCPS', resp.mcps);
return true;
} catch (e) {
print('getAppConfig 取失败: $e');
return false;
}
}
} }

2
local_plugins/open_ai_service/android/build.gradle.kts

@ -4,6 +4,7 @@ plugins {
// Kotlin Android 插件 // Kotlin Android 插件
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("com.github.johnrengelman.shadow") version "8.1.1" id("com.github.johnrengelman.shadow") version "8.1.1"
kotlin("plugin.serialization") version "1.9.24"
} }
android { android {
@ -58,4 +59,5 @@ dependencies {
implementation("io.ktor:ktor-client-okhttp:3.1.2") implementation("io.ktor:ktor-client-okhttp:3.1.2")
implementation("io.ktor:ktor-client-logging:3.1.2") implementation("io.ktor:ktor-client-logging:3.1.2")
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
} }

24
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt

@ -34,7 +34,17 @@ import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
import com.yunqiinnovation.open_ai_service.mcp.CustomSseClientTransport import com.yunqiinnovation.open_ai_service.mcp.CustomSseClientTransport
import io.ktor.util.valuesOf import io.ktor.util.valuesOf
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class ServerConfig(
val servername: String,
val url: String,
val tools: List<String>? = null
)
/** /**
* 工具类型枚举 * 工具类型枚举
*/ */
@ -484,15 +494,17 @@ class MCPClient(private val context: Context? = null) : AutoCloseable {
if (mcpConfigJson.isNotEmpty()) { if (mcpConfigJson.isNotEmpty()) {
val mapper = ObjectMapper() val mapper = ObjectMapper()
try { try {
val config = mapper.readValue(mcpConfigJson, Map::class.java) Log.d(TAG, "传入JSON:${mcpConfigJson}")
val configMap: Map<String, ServerConfig> = Json.decodeFromString(mcpConfigJson)
// val config = mapper.readValue(mcpConfigJson, Map::class.java)
// 处理mcpServers格式 // 处理mcpServers格式
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val mcpServers = config["mcpServers"] as? Map<String, Map<String, String>> // val mcpServers = config["mcpServers"] as? Map<String, Map<String, String>>
if (mcpServers != null && mcpServers.isNotEmpty()) { if (configMap.isNotEmpty()) {
// 从服务器配置中提取URL // 从服务器配置中提取URL
mcpServers.forEach { (serverId, serverConfig) -> configMap.forEach { (serverId, serverConfig) ->
val url = serverConfig["url"] val url = serverConfig.url
if (!url.isNullOrEmpty()) { if (!url.isNullOrEmpty()) {
serverMap[serverId] = url serverMap[serverId] = url
Log.d(TAG, "从配置中添加服务器 [$serverId]: $url") Log.d(TAG, "从配置中添加服务器 [$serverId]: $url")

3
pubspec.yaml

@ -13,6 +13,7 @@ dependencies:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
get: ^4.6.6 get: ^4.6.6
json_annotation: ^4.9.0
get_storage: ^2.1.1 get_storage: ^2.1.1
http: ^1.3.0 http: ^1.3.0
flutter_screenutil: ^5.9.0 flutter_screenutil: ^5.9.0
@ -80,6 +81,8 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.3.3 # 用于生成代码 flutter pub run build_runner build
json_serializable: ^6.6.0 # 用于自动生成代码
flutter: flutter:
uses-material-design: true uses-material-design: true

Loading…
Cancel
Save