You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
9.1 KiB

import 'dart:async';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/events/voice_interaction_event.dart';
import '../../core/utils/logger.dart';
import '../../modules/chat/models/message_model.dart';
import '../../routes/app_routes.dart';
import 'chat_history_service.dart';
/// 语音交互服务接口
///
/// 管理与平台原生语音交互服务的通信,提供统一的接口供应用使用
class VoiceInteractionService extends GetxService {
static VoiceInteractionService get to => Get.find<VoiceInteractionService>();
// 方法通道
static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction');
// 事件通道
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();
// 事件流
Stream<VoiceInteractionEvent> get eventStream => _eventStreamController.stream;
// 事件通道订阅
StreamSubscription? _eventSubscription;
// 标记是否初始化
bool _isInitialized = false;
// 配置信息
late String _azureSpeechKey;
late String _azureSpeechRegion;
late String _openaiApiKey;
late String _openaiBaseUrl;
late String _openaiModel;
// 聊天历史服务
late final ChatHistoryService _chatHistoryService;
/// 设置事件通道
void _setupEventChannel() {
_eventSubscription = _eventChannel
.receiveBroadcastStream()
.listen(_handleVoiceInteractionEvent, onError: (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'] ?? '';
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) {
Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
}
}
/// 处理来自原生层的事件
void _handleVoiceInteractionEvent(dynamic event) {
if (event is! Map) return;
final eventMap = event as Map<dynamic, dynamic>;
final String eventType = eventMap['type'] as String? ?? '';
// final int timestamp = eventMap['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch;
Logger.info('收到语音交互事件: $eventType');
switch (eventType) {
case 'recognition_started':
// 语音识别开始事件
final recognitionEvent = RecognitionStartedEvent(
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(recognitionEvent);
break;
case 'chat_history_updated':
// 聊天历史更新事件
final String agentId = eventMap['agentId'] as String? ?? '';
final String userMessage = eventMap['userMessage'] as String? ?? '';
final String assistantMessage = eventMap['assistantMessage'] as String? ?? '';
final chatHistoryEvent = ChatHistoryEvent(
agentId: agentId,
userMessage: userMessage,
assistantMessage: assistantMessage,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(chatHistoryEvent);
// 保存聊天历史到ChatHistoryService
_saveChatHistory(agentId, userMessage, assistantMessage, DateTime.now().millisecondsSinceEpoch);
break;
case 'enter_translation_mode':
// 进入翻译模式事件
Logger.info('收到进入翻译模式事件,正在导航到翻译界面');
_navigateToTranslation();
final translationModeEvent = GenericVoiceInteractionEvent(
type: 'enter_translation_mode',
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(translationModeEvent);
break;
}
}
/// 导航到翻译界面
void _navigateToTranslation() {
try {
// 使用GetX导航到翻译页面
Get.toNamed(Routes.translation);
Logger.info('已导航到翻译界面');
} catch (e) {
Logger.error('导航到翻译界面失败: $e');
}
}
/// 保存聊天历史
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) {
try {
// 检查参数有效性
if (userMessage.isEmpty) {
return;
}
// 创建用户消息和助手消息
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]);
// 保存更新后的历史记录
_chatHistoryService.saveHistory(agentId, existingMessages);
Logger.info('已保存聊天历史: agentId=$agentId');
} catch (e) {
Logger.error('保存聊天历史失败: $e');
}
}
/// 初始化服务
Future<bool> initialize() async {
if (_isInitialized) return true;
try {
Logger.info('正在初始化语音交互服务...');
// 获取聊天历史服务
try {
_chatHistoryService = Get.find<ChatHistoryService>();
} catch (e) {
Logger.warning('获取ChatHistoryService失败,将创建新实例');
_chatHistoryService = Get.put(ChatHistoryService());
}
// 设置事件通道
_setupEventChannel();
// 加载配置
_loadConfig();
// 检查服务是否已在运行
final bool running = await checkServiceStatus();
// 如果服务未运行,启动服务
if (!running) {
// 启动语音交互服务
final success = await startService();
if (!success) {
Logger.error('语音交互服务启动失败');
return false;
}
}
_isInitialized = true;
Logger.info('语音交互服务初始化完成');
return true;
} catch (e) {
Logger.error('语音交互服务初始化失败: $e');
return false;
}
}
/// 启动语音交互服务
Future<bool> startService() async {
try {
Logger.info('启动语音交互服务...');
// 使用已加载的配置信息
final result = await _channel.invokeMethod<bool>('startService', {
'azure_speech_key': _azureSpeechKey,
'azure_speech_region': _azureSpeechRegion,
'openai_api_key': _openaiApiKey,
'openai_base_url': _openaiBaseUrl,
'openai_model': _openaiModel,
}) ?? false;
if (result) {
_isServiceRunning.value = true;
Logger.info('语音交互服务已启动');
} else {
Logger.error('启动语音交互服务失败');
}
return result;
} catch (e) {
Logger.error('启动语音交互服务时发生错误: $e');
return false;
}
}
/// 停止语音交互服务
Future<bool> stopService() async {
try {
Logger.info('停止语音交互服务...');
final result = await _channel.invokeMethod<bool>('stopService') ?? false;
if (result) {
_isServiceRunning.value = false;
Logger.info('语音交互服务已停止');
} else {
Logger.error('停止语音交互服务失败');
}
return result;
} catch (e) {
Logger.error('停止语音交互服务时发生错误: $e');
return false;
}
}
/// 检查服务是否运行
Future<bool> checkServiceStatus() async {
try {
final bool result = await _channel.invokeMethod<bool>('isServiceRunning') ?? false;
_isServiceRunning.value = result;
return result;
} catch (e) {
Logger.error('检查服务状态时发生错误: $e');
return false;
}
}
/// 暂停语音交互
Future<bool> pauseVoiceInteraction() async {
try {
Logger.info('暂停后台语音交互...');
final result = await _channel.invokeMethod<bool>('pauseVoiceInteraction') ?? false;
if (result) {
Logger.info('语音交互已暂停');
} else {
Logger.error('暂停语音交互失败');
}
return result;
} catch (e) {
Logger.error('暂停语音交互时发生错误: $e');
return false;
}
}
/// 资源释放
@override
void onClose() {
_eventSubscription?.cancel();
_eventStreamController.close();
super.onClose();
}
}