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 int _maxHistoryCount = 20; // 每个对话最多保存的消息数
static const int _maxHistoryCount = 10; // 每个对话最多保存的消息数
@override
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 _serviceRegion;
final List<String> _supportedLanguages = ['zh-CN'];
final List<String> _defaultSupportedLanguages = ['zh-CN'];
@override
List<String> get supportedLanguages => _supportedLanguages;
List<String> get supportedLanguages => _defaultSupportedLanguages;
// 连续识别相关
bool _isContinuousRecognitionActive = false;
@ -64,10 +64,7 @@ class AzureAsrService extends GetxService implements AsrService {
List<String>? supportedLanguages,
}) async {
try {
if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages);
}
final List<String> 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();

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

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

2
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,
);

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 _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 = <TranslationItem>[].obs;
final Rx<TranslationItem?> currentItem = Rx<TranslationItem?>(null);
String? currentSessionId;
// 兼容旧版视图的计算属性
Rx<TranslationItem?> get currentItem {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) {
return Rx<TranslationItem?>(translationHistory.last);
}
return Rx<TranslationItem?>(null);
}
// UI相关
final ScrollController scrollController = ScrollController();
@ -126,7 +133,6 @@ class TranslationController extends GetxController {
Future<void> _initServices() async {
try {
await _translationService.initialize();
await _ttsService.initialize();
// 初始化 ASR 服务时,明确指定需要支持的语言
final List<String> 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<void> 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<String?> translateText(String sourceText) async {
Future<String?> 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<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((_) {

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

@ -157,10 +157,6 @@ class TranslationView extends GetView<TranslationController> {
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<TranslationController> {
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<TranslationController> {
Widget _buildTranslationItemWidget(
String sourceText,
String translatedText,
bool isCurrent,
bool isCurrentItem,
{
TranslationItem? item,
bool isIntermediate = false,
@ -228,10 +215,10 @@ class TranslationView extends GetView<TranslationController> {
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<TranslationController> {
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<TranslationController> {
size: 20.sp,
),
onPressed: () {
if (isCurrent) {
controller.playTranslatedText(translatedText);
} else if (item != null) {
if (item != null) {
controller.playTranslationItem(item);
} else {
controller.playTranslatedText(translatedText);
}
},
),

Loading…
Cancel
Save