Browse Source

add

newdev_shunjiawei
wolfplus 2 years ago
parent
commit
1fc33ee4e4
  1. 2
      lib/data/services/chat_history_service.dart
  2. 11
      lib/data/services/speech_impl/azure_asr_service.dart
  3. 7
      lib/data/services/speech_impl/azure_tts_service.dart
  4. 2
      lib/modules/chat/controllers/chat_controller.dart
  5. 144
      lib/modules/translation/controllers/translation_controller.dart
  6. 41
      lib/modules/translation/views/translation_view.dart

2
lib/data/services/chat_history_service.dart

@ -10,7 +10,7 @@ class ChatHistoryService extends GetxService {
// 存储键名 // 存储键名
static const String _historyKeyPrefix = 'chat_history_'; static const String _historyKeyPrefix = 'chat_history_';
static const int _maxHistoryCount = 20; // 每个对话最多保存的消息数 static const int _maxHistoryCount = 10; // 每个对话最多保存的消息数
@override @override
void onInit() { void onInit() {

11
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 _subscriptionKey;
late final String _serviceRegion; late final String _serviceRegion;
final List<String> _supportedLanguages = ['zh-CN']; final List<String> _defaultSupportedLanguages = ['zh-CN'];
@override @override
List<String> get supportedLanguages => _supportedLanguages; List<String> get supportedLanguages => _defaultSupportedLanguages;
// 连续识别相关 // 连续识别相关
bool _isContinuousRecognitionActive = false; bool _isContinuousRecognitionActive = false;
@ -64,10 +64,7 @@ class AzureAsrService extends GetxService implements AsrService {
List<String>? supportedLanguages, List<String>? supportedLanguages,
}) async { }) async {
try { try {
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { final List<String> languages = supportedLanguages ?? _defaultSupportedLanguages;
_supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages);
}
// 检查是否需要重新初始化 // 检查是否需要重新初始化
if (_isInitialized) { if (_isInitialized) {
@ -77,7 +74,7 @@ class AzureAsrService extends GetxService implements AsrService {
final bool result = await _channel.invokeMethod('initialize', { final bool result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey, 'subscriptionKey': _subscriptionKey,
'region': _serviceRegion, 'region': _serviceRegion,
'supportedLanguages': _supportedLanguages, 'supportedLanguages': languages,
}); });
_setupEventChannel(); _setupEventChannel();

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

@ -86,8 +86,11 @@ class AzureTtsService extends GetxService implements TtsService {
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
}) async { }) async {
if (_isInitialized) return true; // 检查是否需要重新初始化
if (_isInitialized) {
await dispose();
}
try { try {
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
Logger.error('未找到Azure语音服务配置'); Logger.error('未找到Azure语音服务配置');

2
lib/modules/chat/controllers/chat_controller.dart

@ -217,7 +217,7 @@ class ChatController extends GetxController {
if (scrollController.hasClients && messages.isNotEmpty) { if (scrollController.hasClients && messages.isNotEmpty) {
try { try {
scrollController.animateTo( scrollController.animateTo(
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent + 20,
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
curve: Curves.easeOut, curve: Curves.easeOut,
); );

144
lib/modules/translation/controllers/translation_controller.dart

@ -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((_) {

41
lib/modules/translation/views/translation_view.dart

@ -157,10 +157,6 @@ class TranslationView extends GetView<TranslationController> {
return Obx(() { return Obx(() {
final itemCount = controller.translationHistory.length; final itemCount = controller.translationHistory.length;
// 使用 currentItem 来判断是否显示当前项
final hasCurrentItem = controller.currentItem.value != null &&
controller.currentItem.value!.sourceText.isNotEmpty; // 只有当有实际内容时才显示当前项
// 计算底部内边距,确保内容不会被底部框遮住 // 计算底部内边距,确保内容不会被底部框遮住
final bottomPadding = controller.bottomBarHeight + 20.h; // 使用控制栏高度加上额外间距 final bottomPadding = controller.bottomBarHeight + 20.h; // 使用控制栏高度加上额外间距
@ -172,27 +168,18 @@ class TranslationView extends GetView<TranslationController> {
top: 16.h, top: 16.h,
bottom: bottomPadding bottom: bottomPadding
), ),
itemCount: itemCount + (hasCurrentItem ? 1 : 0), itemCount: itemCount,
itemBuilder: (context, index) { 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]; final historyItem = controller.translationHistory[index];
// 构建项目widget
return _buildTranslationItemWidget( return _buildTranslationItemWidget(
historyItem.sourceText, historyItem.sourceText,
historyItem.translatedText, historyItem.translatedText,
false, historyItem.isIntermediate, // 如果是中间结果,设置为当前项
item: historyItem, item: historyItem,
isIntermediate: false, // 历史项总是显示为最终结果 isIntermediate: historyItem.isIntermediate,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
); );
}, },
@ -203,7 +190,7 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildTranslationItemWidget( Widget _buildTranslationItemWidget(
String sourceText, String sourceText,
String translatedText, String translatedText,
bool isCurrent, bool isCurrentItem,
{ {
TranslationItem? item, TranslationItem? item,
bool isIntermediate = false, bool isIntermediate = false,
@ -228,10 +215,10 @@ class TranslationView extends GetView<TranslationController> {
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(16.r), padding: EdgeInsets.all(16.r),
child: Text( child: Text(
sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, sourceText.isEmpty && isCurrentItem ? '等待语音输入...' : sourceText,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: sourceText.isEmpty && isCurrent color: sourceText.isEmpty && isCurrentItem
? (isDarkMode ? Colors.grey[400] : Colors.grey) ? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black), : (isDarkMode ? Colors.white : Colors.black),
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
@ -252,10 +239,10 @@ class TranslationView extends GetView<TranslationController> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText, translatedText.isEmpty && isCurrentItem ? '翻译结果将显示在这里...' : translatedText,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: translatedText.isEmpty && isCurrent color: translatedText.isEmpty && isCurrentItem
? (isDarkMode ? Colors.grey[400] : Colors.grey) ? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black), : (isDarkMode ? Colors.white : Colors.black),
fontStyle: FontStyle.normal, fontStyle: FontStyle.normal,
@ -270,10 +257,10 @@ class TranslationView extends GetView<TranslationController> {
size: 20.sp, size: 20.sp,
), ),
onPressed: () { onPressed: () {
if (isCurrent) { if (item != null) {
controller.playTranslatedText(translatedText);
} else if (item != null) {
controller.playTranslationItem(item); controller.playTranslationItem(item);
} else {
controller.playTranslatedText(translatedText);
} }
}, },
), ),

Loading…
Cancel
Save