|
|
@ -23,7 +23,7 @@ class TranslationController extends GetxController { |
|
|
static const String _historyKey = 'translation_history'; |
|
|
static const String _historyKey = 'translation_history'; |
|
|
static const String _sourceLanguageKey = 'translation_source_language'; |
|
|
static const String _sourceLanguageKey = 'translation_source_language'; |
|
|
static const String _targetLanguageKey = 'translation_target_language'; |
|
|
static const String _targetLanguageKey = 'translation_target_language'; |
|
|
static const int _maxHistoryItems = 100; |
|
|
static const int _maxHistoryItems = 10; |
|
|
|
|
|
|
|
|
// 状态变量 |
|
|
// 状态变量 |
|
|
final isRecognizing = false.obs; |
|
|
final isRecognizing = false.obs; |
|
|
@ -39,9 +39,16 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
// 翻译历史和当前项 |
|
|
// 翻译历史和当前项 |
|
|
final translationHistory = <TranslationItem>[].obs; |
|
|
final translationHistory = <TranslationItem>[].obs; |
|
|
final Rx<TranslationItem?> currentItem = Rx<TranslationItem?>(null); |
|
|
|
|
|
String? currentSessionId; |
|
|
String? currentSessionId; |
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧版视图的计算属性 |
|
|
|
|
|
Rx<TranslationItem?> get currentItem { |
|
|
|
|
|
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { |
|
|
|
|
|
return Rx<TranslationItem?>(translationHistory.last); |
|
|
|
|
|
} |
|
|
|
|
|
return Rx<TranslationItem?>(null); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// UI相关 |
|
|
// UI相关 |
|
|
final ScrollController scrollController = ScrollController(); |
|
|
final ScrollController scrollController = ScrollController(); |
|
|
|
|
|
|
|
|
@ -126,7 +133,6 @@ class TranslationController extends GetxController { |
|
|
Future<void> _initServices() async { |
|
|
Future<void> _initServices() async { |
|
|
try { |
|
|
try { |
|
|
await _translationService.initialize(); |
|
|
await _translationService.initialize(); |
|
|
await _ttsService.initialize(); |
|
|
|
|
|
|
|
|
|
|
|
// 初始化 ASR 服务时,明确指定需要支持的语言 |
|
|
// 初始化 ASR 服务时,明确指定需要支持的语言 |
|
|
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; |
|
|
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; |
|
|
@ -174,7 +180,7 @@ class TranslationController extends GetxController { |
|
|
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
|
|
|
|
|
|
// 初始化新的翻译项 |
|
|
// 初始化新的翻译项 |
|
|
currentItem.value = TranslationItem( |
|
|
final newItem = TranslationItem( |
|
|
sourceText: '', |
|
|
sourceText: '', |
|
|
translatedText: '', |
|
|
translatedText: '', |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
@ -182,8 +188,12 @@ class TranslationController extends GetxController { |
|
|
timestamp: DateTime.now(), |
|
|
timestamp: DateTime.now(), |
|
|
sessionId: currentSessionId!, |
|
|
sessionId: currentSessionId!, |
|
|
isFirstInSession: true, |
|
|
isFirstInSession: true, |
|
|
|
|
|
isIntermediate: true, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 添加到历史记录 |
|
|
|
|
|
translationHistory.add(newItem); |
|
|
|
|
|
|
|
|
// 开始连续语音识别 |
|
|
// 开始连续语音识别 |
|
|
final recognitionStream = await _asrService.startContinuousRecognition(); |
|
|
final recognitionStream = await _asrService.startContinuousRecognition(); |
|
|
isRecognizing.value = true; |
|
|
isRecognizing.value = true; |
|
|
@ -227,21 +237,20 @@ class TranslationController extends GetxController { |
|
|
isRecognizing.value = false; |
|
|
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) { |
|
|
if (translationHistory.last.translatedText.isEmpty) { |
|
|
await translateText(currentItem.value!.sourceText); |
|
|
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
translationHistory.add(currentItem.value!); |
|
|
|
|
|
saveTranslationHistory(); |
|
|
saveTranslationHistory(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
currentSessionId = null; |
|
|
currentSessionId = null; |
|
|
currentItem.value = null; |
|
|
|
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('停止语音识别失败: $e'); |
|
|
Logger.error('停止语音识别失败: $e'); |
|
|
} |
|
|
} |
|
|
@ -251,9 +260,9 @@ class TranslationController extends GetxController { |
|
|
void handleIntermediateResult(String text) { |
|
|
void handleIntermediateResult(String text) { |
|
|
if (text.isEmpty) return; |
|
|
if (text.isEmpty) return; |
|
|
|
|
|
|
|
|
if (currentItem.value == null) { |
|
|
if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) { |
|
|
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
currentItem.value = TranslationItem( |
|
|
final newItem = TranslationItem( |
|
|
sourceText: text, |
|
|
sourceText: text, |
|
|
translatedText: '', |
|
|
translatedText: '', |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
@ -263,13 +272,14 @@ class TranslationController extends GetxController { |
|
|
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, |
|
|
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, |
|
|
isIntermediate: true, |
|
|
isIntermediate: true, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
translationHistory.add(newItem); |
|
|
// 添加新项后滚动到底部 |
|
|
// 添加新项后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
} else { |
|
|
} else { |
|
|
// 更新当前项 |
|
|
// 更新当前项 |
|
|
currentItem.value!.sourceText = text; |
|
|
translationHistory.last.sourceText = text; |
|
|
currentItem.value!.isIntermediate = true; |
|
|
translationHistory.refresh(); |
|
|
currentItem.refresh(); |
|
|
|
|
|
// 更新后滚动到底部 |
|
|
// 更新后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
} |
|
|
} |
|
|
@ -277,45 +287,52 @@ class TranslationController extends GetxController { |
|
|
// 防抖处理翻译请求 |
|
|
// 防抖处理翻译请求 |
|
|
_translationDebounceTimer?.cancel(); |
|
|
_translationDebounceTimer?.cancel(); |
|
|
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { |
|
|
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { |
|
|
if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) { |
|
|
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { |
|
|
translateText(currentItem.value!.sourceText); |
|
|
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 处理最终识别结果 |
|
|
// 处理最终识别结果 |
|
|
Future<void> handleFinalResult(String text) async { |
|
|
Future<void> handleFinalResult(String text) async { |
|
|
if (text.isEmpty || currentItem.value == null) return; |
|
|
if (text.isEmpty || translationHistory.isEmpty) return; |
|
|
|
|
|
|
|
|
// 更新当前项,但不立即添加到历史记录 |
|
|
// 更新当前项 |
|
|
currentItem.value!.sourceText = text; |
|
|
if (translationHistory.last.isIntermediate) { |
|
|
currentItem.value!.isIntermediate = false; |
|
|
translationHistory.last.sourceText = text; |
|
|
currentItem.refresh(); |
|
|
translationHistory.last.isIntermediate = false; |
|
|
// 更新后滚动到底部 |
|
|
translationHistory.refresh(); |
|
|
_scrollToBottom(); |
|
|
// 更新后滚动到底部 |
|
|
|
|
|
|
|
|
// 先翻译,保持UI稳定 |
|
|
|
|
|
final translationResult = await translateText(text); |
|
|
|
|
|
|
|
|
|
|
|
// 翻译完成后再添加到历史记录 |
|
|
|
|
|
if (currentItem.value != null) { |
|
|
|
|
|
translationHistory.add(currentItem.value!); |
|
|
|
|
|
saveTranslationHistory(); |
|
|
|
|
|
// 添加到历史记录后滚动到底部 |
|
|
|
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
|
|
|
saveTranslationHistory(); |
|
|
|
|
|
|
|
|
// 播放TTS |
|
|
// 使用项目的时间戳进行翻译 |
|
|
if (translationResult != null && isTtsEnabled.value) { |
|
|
translateText(text, translationHistory.last.timestamp); |
|
|
await playTranslatedText(translationResult); |
|
|
} 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<String?> translateText(String sourceText) async { |
|
|
Future<String?> translateText(String sourceText, DateTime timestamp) async { |
|
|
if (sourceText.isEmpty) return null; |
|
|
if (sourceText.isEmpty) return null; |
|
|
|
|
|
|
|
|
isTranslating.value = true; |
|
|
isTranslating.value = true; |
|
|
@ -335,27 +352,27 @@ class TranslationController extends GetxController { |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (translationResult != null) { |
|
|
if (translationResult != null) { |
|
|
// 更新翻译结果 |
|
|
// 查找匹配的项目 - 优先使用timestamp查找 |
|
|
if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) { |
|
|
int index = -1; |
|
|
// 更新当前项 |
|
|
|
|
|
currentItem.value!.translatedText = translationResult; |
|
|
index = translationHistory.indexWhere((item) => |
|
|
currentItem.value!.sourceLanguageCode = detectedSourceLanguageCode; |
|
|
item.timestamp.isAtSameMomentAs(timestamp)); |
|
|
currentItem.value!.targetLanguageCode = detectedTargetLanguageCode; |
|
|
if (index != -1) { |
|
|
currentItem.refresh(); |
|
|
// 更新历史项 |
|
|
|
|
|
translationHistory[index].translatedText = translationResult; |
|
|
|
|
|
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; |
|
|
|
|
|
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; |
|
|
|
|
|
translationHistory.refresh(); |
|
|
|
|
|
saveTranslationHistory(); |
|
|
// 更新后滚动到底部 |
|
|
// 更新后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
} else { |
|
|
|
|
|
// 更新历史记录 |
|
|
// 播放TTS |
|
|
final index = translationHistory.indexWhere((item) => item.sourceText == sourceText); |
|
|
if (isTtsEnabled.value && !translationHistory[index].isIntermediate) { |
|
|
if (index != -1) { |
|
|
await playTranslatedText(translationResult); |
|
|
translationHistory[index].translatedText = translationResult; |
|
|
|
|
|
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; |
|
|
|
|
|
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; |
|
|
|
|
|
translationHistory.refresh(); |
|
|
|
|
|
saveTranslationHistory(); |
|
|
|
|
|
// 更新后滚动到底部 |
|
|
|
|
|
_scrollToBottom(); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Logger.info('未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -428,7 +445,7 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
// 滚动到底部 |
|
|
void _scrollToBottom() { |
|
|
void _scrollToBottom() { |
|
|
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentItem.value != null)) { |
|
|
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { |
|
|
try { |
|
|
try { |
|
|
scrollController.animateTo( |
|
|
scrollController.animateTo( |
|
|
scrollController.position.maxScrollExtent, |
|
|
scrollController.position.maxScrollExtent, |
|
|
@ -541,7 +558,14 @@ class TranslationController extends GetxController { |
|
|
if (historyJson != null) { |
|
|
if (historyJson != null) { |
|
|
try { |
|
|
try { |
|
|
final List<dynamic> historyList = jsonDecode(historyJson); |
|
|
final List<dynamic> historyList = jsonDecode(historyJson); |
|
|
translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); |
|
|
final List<TranslationItem> 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((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
|