From 1fc33ee4e40863c430afddfd3b94b4adfd8a3a4b Mon Sep 17 00:00:00 2001 From: wolfplus Date: Mon, 24 Mar 2025 11:18:27 +0000 Subject: [PATCH] add --- lib/data/services/chat_history_service.dart | 2 +- .../speech_impl/azure_asr_service.dart | 11 +- .../speech_impl/azure_tts_service.dart | 7 +- .../chat/controllers/chat_controller.dart | 2 +- .../controllers/translation_controller.dart | 144 ++++++++++-------- .../translation/views/translation_view.dart | 41 ++--- 6 files changed, 109 insertions(+), 98 deletions(-) diff --git a/lib/data/services/chat_history_service.dart b/lib/data/services/chat_history_service.dart index 8d14209d5..8211075fc 100644 --- a/lib/data/services/chat_history_service.dart +++ b/lib/data/services/chat_history_service.dart @@ -10,7 +10,7 @@ class ChatHistoryService extends GetxService { // 存储键名 static const String _historyKeyPrefix = 'chat_history_'; - static const int _maxHistoryCount = 20; // 每个对话最多保存的消息数 + static const int _maxHistoryCount = 10; // 每个对话最多保存的消息数 @override void onInit() { diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index ed444566c..ffb67ed46 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -17,9 +17,9 @@ class AzureAsrService extends GetxService implements AsrService { late final String _subscriptionKey; late final String _serviceRegion; - final List _supportedLanguages = ['zh-CN']; + final List _defaultSupportedLanguages = ['zh-CN']; @override - List get supportedLanguages => _supportedLanguages; + List get supportedLanguages => _defaultSupportedLanguages; // 连续识别相关 bool _isContinuousRecognitionActive = false; @@ -64,10 +64,7 @@ class AzureAsrService extends GetxService implements AsrService { List? supportedLanguages, }) async { try { - if (supportedLanguages != null && supportedLanguages.isNotEmpty) { - _supportedLanguages.clear(); - _supportedLanguages.addAll(supportedLanguages); - } + final List languages = supportedLanguages ?? _defaultSupportedLanguages; // 检查是否需要重新初始化 if (_isInitialized) { @@ -77,7 +74,7 @@ class AzureAsrService extends GetxService implements AsrService { final bool result = await _channel.invokeMethod('initialize', { 'subscriptionKey': _subscriptionKey, 'region': _serviceRegion, - 'supportedLanguages': _supportedLanguages, + 'supportedLanguages': languages, }); _setupEventChannel(); diff --git a/lib/data/services/speech_impl/azure_tts_service.dart b/lib/data/services/speech_impl/azure_tts_service.dart index d9aed08ee..ae5ab9369 100644 --- a/lib/data/services/speech_impl/azure_tts_service.dart +++ b/lib/data/services/speech_impl/azure_tts_service.dart @@ -86,8 +86,11 @@ class AzureTtsService extends GetxService implements TtsService { Future initialize({ List? supportedLanguages, }) async { - if (_isInitialized) return true; - + // 检查是否需要重新初始化 + if (_isInitialized) { + await dispose(); + } + try { if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { Logger.error('未找到Azure语音服务配置'); diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index f3efa2e69..bcad7634a 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -217,7 +217,7 @@ class ChatController extends GetxController { if (scrollController.hasClients && messages.isNotEmpty) { try { scrollController.animateTo( - scrollController.position.maxScrollExtent, + scrollController.position.maxScrollExtent + 20, duration: Duration(milliseconds: 300), curve: Curves.easeOut, ); diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index df5cfaa14..f46d48bca 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -23,7 +23,7 @@ class TranslationController extends GetxController { static const String _historyKey = 'translation_history'; static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; - static const int _maxHistoryItems = 100; + static const int _maxHistoryItems = 10; // 状态变量 final isRecognizing = false.obs; @@ -39,9 +39,16 @@ class TranslationController extends GetxController { // 翻译历史和当前项 final translationHistory = [].obs; - final Rx currentItem = Rx(null); String? currentSessionId; + // 兼容旧版视图的计算属性 + Rx get currentItem { + if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { + return Rx(translationHistory.last); + } + return Rx(null); + } + // UI相关 final ScrollController scrollController = ScrollController(); @@ -126,7 +133,6 @@ class TranslationController extends GetxController { Future _initServices() async { try { await _translationService.initialize(); - await _ttsService.initialize(); // 初始化 ASR 服务时,明确指定需要支持的语言 final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; @@ -174,7 +180,7 @@ class TranslationController extends GetxController { currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); // 初始化新的翻译项 - currentItem.value = TranslationItem( + final newItem = TranslationItem( sourceText: '', translatedText: '', sourceLanguageCode: sourceLanguageCode.value, @@ -182,8 +188,12 @@ class TranslationController extends GetxController { timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: true, + isIntermediate: true, ); + // 添加到历史记录 + translationHistory.add(newItem); + // 开始连续语音识别 final recognitionStream = await _asrService.startContinuousRecognition(); isRecognizing.value = true; @@ -227,21 +237,20 @@ class TranslationController extends GetxController { isRecognizing.value = false; // 如果当前有中间结果,将其作为最终结果处理 - if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) { + if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { // 标记为最终结果 - currentItem.value!.isIntermediate = false; + translationHistory.last.isIntermediate = false; + translationHistory.refresh(); // 如果还没有翻译结果,尝试翻译 - if (currentItem.value!.translatedText.isEmpty) { - await translateText(currentItem.value!.sourceText); + if (translationHistory.last.translatedText.isEmpty) { + await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); } - translationHistory.add(currentItem.value!); saveTranslationHistory(); } currentSessionId = null; - currentItem.value = null; } catch (e) { Logger.error('停止语音识别失败: $e'); } @@ -251,9 +260,9 @@ class TranslationController extends GetxController { void handleIntermediateResult(String text) { if (text.isEmpty) return; - if (currentItem.value == null) { + if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) { currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); - currentItem.value = TranslationItem( + final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: sourceLanguageCode.value, @@ -263,13 +272,14 @@ class TranslationController extends GetxController { isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isIntermediate: true, ); + + translationHistory.add(newItem); // 添加新项后滚动到底部 _scrollToBottom(); } else { // 更新当前项 - currentItem.value!.sourceText = text; - currentItem.value!.isIntermediate = true; - currentItem.refresh(); + translationHistory.last.sourceText = text; + translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); } @@ -277,45 +287,52 @@ class TranslationController extends GetxController { // 防抖处理翻译请求 _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { - if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) { - translateText(currentItem.value!.sourceText); + if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { + translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); } }); } // 处理最终识别结果 Future handleFinalResult(String text) async { - if (text.isEmpty || currentItem.value == null) return; + if (text.isEmpty || translationHistory.isEmpty) return; - // 更新当前项,但不立即添加到历史记录 - currentItem.value!.sourceText = text; - currentItem.value!.isIntermediate = false; - currentItem.refresh(); - // 更新后滚动到底部 - _scrollToBottom(); - - // 先翻译,保持UI稳定 - final translationResult = await translateText(text); - - // 翻译完成后再添加到历史记录 - if (currentItem.value != null) { - translationHistory.add(currentItem.value!); - saveTranslationHistory(); - // 添加到历史记录后滚动到底部 + // 更新当前项 + if (translationHistory.last.isIntermediate) { + translationHistory.last.sourceText = text; + translationHistory.last.isIntermediate = false; + translationHistory.refresh(); + // 更新后滚动到底部 _scrollToBottom(); + saveTranslationHistory(); - // 播放TTS - if (translationResult != null && isTtsEnabled.value) { - await playTranslatedText(translationResult); - } + // 使用项目的时间戳进行翻译 + translateText(text, translationHistory.last.timestamp); + } else { + // 如果最后一项不是中间结果,创建新项 + final newItem = TranslationItem( + sourceText: text, + translatedText: '', + sourceLanguageCode: sourceLanguageCode.value, + targetLanguageCode: targetLanguageCode.value, + timestamp: DateTime.now(), + sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(), + isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, + isIntermediate: false, + ); + + translationHistory.add(newItem); + translationHistory.refresh(); + _scrollToBottom(); + saveTranslationHistory(); - // 重置当前项,准备下一次输入 - currentItem.value = null; + // 使用新项目的时间戳进行翻译 + translateText(text, newItem.timestamp); } } // 翻译文本 - Future translateText(String sourceText) async { + Future translateText(String sourceText, DateTime timestamp) async { if (sourceText.isEmpty) return null; isTranslating.value = true; @@ -335,27 +352,27 @@ class TranslationController extends GetxController { ); if (translationResult != null) { - // 更新翻译结果 - if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) { - // 更新当前项 - currentItem.value!.translatedText = translationResult; - currentItem.value!.sourceLanguageCode = detectedSourceLanguageCode; - currentItem.value!.targetLanguageCode = detectedTargetLanguageCode; - currentItem.refresh(); + // 查找匹配的项目 - 优先使用timestamp查找 + int index = -1; + + index = translationHistory.indexWhere((item) => + item.timestamp.isAtSameMomentAs(timestamp)); + if (index != -1) { + // 更新历史项 + translationHistory[index].translatedText = translationResult; + translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; + translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; + translationHistory.refresh(); + saveTranslationHistory(); // 更新后滚动到底部 _scrollToBottom(); - } else { - // 更新历史记录 - final index = translationHistory.indexWhere((item) => item.sourceText == sourceText); - if (index != -1) { - translationHistory[index].translatedText = translationResult; - translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; - translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; - translationHistory.refresh(); - saveTranslationHistory(); - // 更新后滚动到底部 - _scrollToBottom(); + + // 播放TTS + if (isTtsEnabled.value && !translationHistory[index].isIntermediate) { + await playTranslatedText(translationResult); } + } else { + Logger.info('未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); } } } catch (e) { @@ -428,7 +445,7 @@ class TranslationController extends GetxController { // 滚动到底部 void _scrollToBottom() { - if (scrollController.hasClients && (translationHistory.isNotEmpty || currentItem.value != null)) { + if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { scrollController.animateTo( scrollController.position.maxScrollExtent, @@ -541,7 +558,14 @@ class TranslationController extends GetxController { if (historyJson != null) { try { final List historyList = jsonDecode(historyJson); - translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); + final List allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); + + // 只保留最新的10条记录 + if (allHistory.length > _maxHistoryItems) { + translationHistory.value = allHistory.sublist(allHistory.length - _maxHistoryItems); + } else { + translationHistory.value = allHistory; + } // 使用微任务确保在视图完全渲染后滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 727eb72b2..befc39503 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -157,10 +157,6 @@ class TranslationView extends GetView { return Obx(() { final itemCount = controller.translationHistory.length; - // 使用 currentItem 来判断是否显示当前项 - final hasCurrentItem = controller.currentItem.value != null && - controller.currentItem.value!.sourceText.isNotEmpty; // 只有当有实际内容时才显示当前项 - // 计算底部内边距,确保内容不会被底部框遮住 final bottomPadding = controller.bottomBarHeight + 20.h; // 使用控制栏高度加上额外间距 @@ -172,27 +168,18 @@ class TranslationView extends GetView { top: 16.h, bottom: bottomPadding ), - itemCount: itemCount + (hasCurrentItem ? 1 : 0), + itemCount: itemCount, itemBuilder: (context, index) { - // Current item (being translated) - if (hasCurrentItem && index == itemCount) { - return _buildTranslationItemWidget( - controller.currentItem.value!.sourceText, - controller.currentItem.value!.translatedText, - true, - isIntermediate: controller.currentItem.value!.isIntermediate, - isDarkMode: isDarkMode, - ); - } - - // History items + // 获取当前项 final historyItem = controller.translationHistory[index]; + + // 构建项目widget return _buildTranslationItemWidget( historyItem.sourceText, historyItem.translatedText, - false, + historyItem.isIntermediate, // 如果是中间结果,设置为当前项 item: historyItem, - isIntermediate: false, // 历史项总是显示为最终结果 + isIntermediate: historyItem.isIntermediate, isDarkMode: isDarkMode, ); }, @@ -203,7 +190,7 @@ class TranslationView extends GetView { Widget _buildTranslationItemWidget( String sourceText, String translatedText, - bool isCurrent, + bool isCurrentItem, { TranslationItem? item, bool isIntermediate = false, @@ -228,10 +215,10 @@ class TranslationView extends GetView { width: double.infinity, padding: EdgeInsets.all(16.r), child: Text( - sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, + sourceText.isEmpty && isCurrentItem ? '等待语音输入...' : sourceText, style: TextStyle( fontSize: 16.sp, - color: sourceText.isEmpty && isCurrent + color: sourceText.isEmpty && isCurrentItem ? (isDarkMode ? Colors.grey[400] : Colors.grey) : (isDarkMode ? Colors.white : Colors.black), fontWeight: FontWeight.normal, @@ -252,10 +239,10 @@ class TranslationView extends GetView { children: [ Expanded( child: Text( - translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText, + translatedText.isEmpty && isCurrentItem ? '翻译结果将显示在这里...' : translatedText, style: TextStyle( fontSize: 16.sp, - color: translatedText.isEmpty && isCurrent + color: translatedText.isEmpty && isCurrentItem ? (isDarkMode ? Colors.grey[400] : Colors.grey) : (isDarkMode ? Colors.white : Colors.black), fontStyle: FontStyle.normal, @@ -270,10 +257,10 @@ class TranslationView extends GetView { size: 20.sp, ), onPressed: () { - if (isCurrent) { - controller.playTranslatedText(translatedText); - } else if (item != null) { + if (item != null) { controller.playTranslationItem(item); + } else { + controller.playTranslatedText(translatedText); } }, ),