From 22b1e0423e8fb1a66c19c5d5d35266d4c6327707 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 26 Nov 2025 14:22:06 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(=E4=BC=9A=E8=AE=AE=E5=8A=A9=E6=89=8B):?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E6=9E=81=E9=80=9F=E7=89=88=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E8=AF=86=E5=88=AB=E6=9C=8D=E5=8A=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 MeetingTaskService2 作为火山引擎极速版语音识别服务实现 修改相关控制器和服务以支持双版本语音识别服务切换 根据音频时长自动选择标准版或极速版服务 --- lib/core/bindings/initial_binding.dart | 3 + .../meeting/meeting_task_service.dart | 1 + .../meeting/meeting_task_service2.dart | 1155 +++++++++++++++++ .../meeting/meeting_upload_service.dart | 39 +- .../meeting/summary_task_service.dart | 27 + .../meeting_details_controller.dart | 46 +- .../controllers/meeting_home_controller.dart | 3 + 7 files changed, 1261 insertions(+), 13 deletions(-) create mode 100644 lib/data/services/meeting/meeting_task_service2.dart diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index e25234505..3ea94689f 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -11,6 +11,7 @@ import '../../../data/services/ast_service.dart'; import '../../../data/services/speech_impl/azure_ast_service.dart'; import 'package:get/get.dart'; import '../../data/services/meeting/meeting_task_service.dart'; +import '../../data/services/meeting/meeting_task_service2.dart'; import '../../data/services/meeting/meeting_upload_service.dart'; import '../../data/services/novice_guide_service.dart'; import '../../data/services/speech_factory.dart'; @@ -70,6 +71,8 @@ class InitialBinding extends Bindings { // 会议助手任务服务 Get.lazyPut(() => MeetingTaskService(), fenix: true); + // 会议助手任务服务(极速识别版) + Get.lazyPut(() => MeetingTaskService2(), fenix: true); // 会议助手总结任务服务 Get.lazyPut(() => SummaryTaskService(), fenix: true); diff --git a/lib/data/services/meeting/meeting_task_service.dart b/lib/data/services/meeting/meeting_task_service.dart index 7b5014a6e..576668aa0 100644 --- a/lib/data/services/meeting/meeting_task_service.dart +++ b/lib/data/services/meeting/meeting_task_service.dart @@ -12,6 +12,7 @@ import '../../../data/models/appconfig.dart'; import 'summary_task_service.dart'; ///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。 +///火山引擎录音文件识别大模型-标准版 class MeetingTaskService extends GetxService { final GetStorage _storage = GetStorage(); diff --git a/lib/data/services/meeting/meeting_task_service2.dart b/lib/data/services/meeting/meeting_task_service2.dart new file mode 100644 index 000000000..e04681dff --- /dev/null +++ b/lib/data/services/meeting/meeting_task_service2.dart @@ -0,0 +1,1155 @@ +import 'dart:convert'; + +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:http/http.dart' as http; + +import '../db/sqflite_api.dart'; +import '../../../data/models/appconfig.dart'; +import '../../../core/utils/logger.dart'; +import '../../../core/utils/synchrodata.dart'; +import '../deapsound_ai_service.dart'; +import 'meeting_upload_service.dart'; +import 'meeting_task_service.dart'; +import 'summary_task_service.dart'; + +//火山引擎录音文件识别大模型-极速版 +class MeetingTaskService2 extends GetxService { + final GetStorage _storage = GetStorage(); + + MeetingUploadService get _uploadService => Get.find(); + SummaryTaskService get _summaryTaskService => Get.find(); + + List _taskList = []; + RxList taskFinishList = [].obs; + + bool _isTask = false; + + String _systemPrompt = ''; + String _userId = ''; + + late final String _appid; + late final String _token; + late final String _serviceUrl; + + int meetingId = 0; + RxInt tasktype = 1.obs; + List textList = []; + String summary = ''; + String overview = ''; + + @override + void onInit() { + super.onInit(); + _getAgent(); + _getTaskList(); + } + + void _getAgent() async { + Map userInfo = _storage.read("user_info"); + _userId = userInfo['user']['uid']; + _systemPrompt = AppConfig.isInitialized() + ? (AppConfig.agentsystemPrompt("meet_assistant") ?? '') + : ''; + final String base = AppConfig.env('VOLC_OPENSPEECH_BASE_URL_BIGMODEL') ?? + 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; + _appid = AppConfig.env('VOLC_OPENSPEECH_APP_ID') ?? ''; + _token = AppConfig.env('VOLC_OPENSPEECH_ACCESS_TOKEN') ?? ''; + _serviceUrl = + base.endsWith('/recognize/flash') ? base : (base + '/recognize/flash'); + } + + void _getTaskList() { + _taskList = _storage.read("meeting_task_list_v2") ?? []; + taskFinishList.value = _storage.read("meeting_task_finish_list_v2") ?? []; + _executeTask(); + } + + Future addTask( + int id, + String audiourl, { + bool isSpeaker = false, + String language = '', + String targetLanguage = '', + String prompt = '', + String outline = '', + }) async { + if (audiourl.isEmpty) { + Map uploadData = _uploadService.uploadList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (uploadData.isNotEmpty) { + if (uploadData['audiourl'].isEmpty) { + uploadData['isTask'] = true; + uploadData['template'] = { + 'isSpeaker': isSpeaker, + 'language': language, + 'targetLanguage': targetLanguage, + 'prompt': prompt, + 'outline': outline, + }; + _storage.write("meeting_upload_list", _uploadService.uploadList); + } else { + await addTaskList( + id, + uploadData['audiourl'], + isSpeaker: isSpeaker, + language: language, + targetLanguage: targetLanguage, + prompt: prompt, + outline: outline, + ); + } + } + } else { + await addTaskList( + id, + audiourl, + isSpeaker: isSpeaker, + language: language, + targetLanguage: targetLanguage, + prompt: prompt, + outline: outline, + ); + } + } + + Future addTaskList( + int id, + String audiourl, { + bool isSpeaker = false, + String language = '', + String targetLanguage = '', + String prompt = '', + String outline = '', + }) async { + await SqfliteApi.editMeeting(id, {'tasktype': 1}); + _taskList.add({ + 'id': id, + 'audiourl': audiourl, + 'tasktype': 1, + 'template': { + 'isSpeaker': isSpeaker, + 'language': language, + 'targetLanguage': targetLanguage, + 'prompt': prompt, + 'outline': outline, + }, + }); + taskFinishList.add({'id': id, 'tasktype': 1}); + _storage.write("meeting_task_list_v2", _taskList); + _storage.write("meeting_task_finish_list_v2", taskFinishList); + try { + final s1 = Get.find(); + Map taskFinishData1 = s1.taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData1.isNotEmpty) { + taskFinishData1['tasktype'] = 3; + s1.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list", s1.taskFinishList); + } catch (_) {} + _executeTask(); + } + + void _executeTask() async { + if (_isTask) return; + _isTask = true; + Logger.debug('Starting task execution. Task count: ${_taskList.length}'); + + while (_taskList.isNotEmpty) { + Logger.debug('Task execution loop. Remaining tasks: ${_taskList.length}'); + await Future.delayed(const Duration(seconds: 2)); + + // 创建任务列表的副本,避免在迭代过程中修改原列表 + List tasksCopy = List.from(_taskList); + + for (var element in tasksCopy) { + Logger.debug( + 'Processing task: ${element['id']} with audio: ${element['audiourl']}'); + + // 检查任务是否仍在列表中(可能已被其他地方移除) + if (_taskList.any((task) => task['id'] == element['id'])) { + try { + await _fileRecognize(element); + } catch (e) { + Logger.error('Failed to process task ${element['id']}: $e'); + // 确保失败的任务被移除,避免无限循环 + _taskList.removeWhere((task) => task['id'] == element['id']); + _storage.write("meeting_task_list_v2", _taskList); + + // 更新任务状态为失败 + await SqfliteApi.editMeeting(element['id'], {'tasktype': 0}); + } + } else { + Logger.debug( + 'Task ${element['id']} was already removed from task list'); + } + } + + // 如果没有任务被处理,避免无限循环 + if (_taskList.length == tasksCopy.length) { + Logger.warning( + 'No tasks were processed in this iteration. Breaking loop to avoid infinite execution.'); + break; + } + } + + _isTask = false; + Logger.debug( + 'Task execution completed. Remaining tasks: ${_taskList.length}'); + } + + void removeTask(int id, {bool isDeleteTask = false}) { + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + if (isDeleteTask || taskFinishData['tasktype'] >= 3) { + taskFinishList.remove(taskFinishData); + _storage.write("meeting_task_finish_list_v2", taskFinishList); + } + } + if (isDeleteTask) { + _taskList.removeWhere((element) => element['id'] == id); + _storage.write("meeting_task_list_v2", _taskList); + } + } + + Future> _recognizeAudio( + String audiourl, { + bool enableSpeakerInfo = true, + String language = '', + }) async { + String requestId = DateTime.now().millisecondsSinceEpoch.toString(); + + // 为每次请求生成新的headers - 适配大模型极速版API + Map requestHeaders = { + 'X-Api-App-Key': _appid, + 'X-Api-Access-Key': _token, + 'X-Api-Resource-Id': 'volc.bigasr.auc_turbo', + 'X-Api-Request-Id': requestId, + 'X-Api-Sequence': '-1', + 'Content-Type': 'application/json', + }; + + // 使用大模型极速版API的新请求格式 + final Map requestPayload = { + "user": {"uid": _appid}, + "audio": {"url": audiourl}, + "request": { + "model_name": "bigmodel", + "enable_itn": true, + "enable_punc": true, + "enable_ddc": true, + "enable_speaker_info": enableSpeakerInfo, + "word_info": 1, + "show_utterances": true + } + }; + + if (language.isNotEmpty) { + requestPayload['request']['language'] = language; + } + + final String requestBody = jsonEncode(requestPayload); + + try { + // 记录请求详情 + Logger.debug('API Request URL: $_serviceUrl'); + Logger.debug('API Request Headers: $requestHeaders'); + Logger.debug('API Request Body: $requestBody'); + + final response = await http.post( + Uri.parse(_serviceUrl), + headers: requestHeaders, + body: requestBody, + ); + + Logger.debug('API Response Status: ${response.statusCode}'); + Logger.debug('API Response Headers: ${response.headers}'); + + if (response.statusCode != 200) { + Logger.error( + 'Recognition failed with status: ${response.statusCode}, body: ${response.body}'); + throw Exception( + 'Recognition failed with status: ${response.statusCode}'); + } + + final respDic = jsonDecode(response.body); + Logger.debug('Recognition response: $respDic'); + + // 检查响应中是否包含资源相关信息 + if (respDic.containsKey('resource_id') || respDic.containsKey('model')) { + Logger.debug( + 'Resource verification - Resource ID used: volc.bigasr.auc_turbo'); + Logger.debug( + 'Response resource info: ${respDic['resource_id'] ?? respDic['model'] ?? 'Not specified in response'}'); + } + + return respDic; + } catch (e) { + Logger.error('Recognition error: ${e.toString()}'); + rethrow; + } + } + + String _getFileType(String path) { + List array = path.split('.'); + return array[array.length - 1]; + } + + int _parseTime(dynamic timeValue) { + if (timeValue == null) return 0; + + if (timeValue is int) { + return timeValue; + } else if (timeValue is double) { + return timeValue.round(); + } else if (timeValue is String) { + try { + double parsed = double.parse(timeValue); + return parsed.round(); + } catch (e) { + Logger.error('Failed to parse time value: $timeValue'); + return 0; + } + } else { + Logger.error( + 'Unknown time value type: ${timeValue.runtimeType}, value: $timeValue'); + return 0; + } + } + + Future _fileRecognize(Map item) async { + try { + Logger.debug('Starting recognition for audio: ${item['audiourl']}'); + // 识别语言选取:若设置了目标输出语言且与录音语言一致,优先让ASR自动识别,避免错配 + final String tplLang = + (((item['template'] ?? {})['language']) ?? '').toString(); + final String tplTarget = + (((item['template'] ?? {})['targetLanguage']) ?? '').toString(); + String asrLanguage = tplLang; + if (tplTarget.isNotEmpty && tplTarget == tplLang) { + asrLanguage = ''; + } + var respDic = await _recognizeAudio( + item['audiourl'], + enableSpeakerInfo: + (((item['template'] ?? {})['isSpeaker']) ?? false) as bool, + language: asrLanguage, + ); + + Logger.debug('Full API response structure: ${respDic.toString()}'); + Logger.debug('Response keys: ${respDic.keys.toList()}'); + + // 检查响应格式并提取utterances + List utterances = []; + + // 检查不同可能的响应格式 + if (respDic['result'] != null && + respDic['result']['utterances'] != null) { + utterances = respDic['result']['utterances']; + Logger.debug( + 'Found utterances in result.utterances: ${utterances.length} items'); + } else if (respDic['resp'] != null && + respDic['resp']['utterances'] != null) { + utterances = respDic['resp']['utterances']; + Logger.debug( + 'Found utterances in resp.utterances: ${utterances.length} items'); + } else if (respDic['utterances'] != null) { + utterances = respDic['utterances']; + Logger.debug( + 'Found utterances in root.utterances: ${utterances.length} items'); + } else if (respDic['data'] != null && + respDic['data']['utterances'] != null) { + utterances = respDic['data']['utterances']; + Logger.debug( + 'Found utterances in data.utterances: ${utterances.length} items'); + } else if (respDic['response'] != null && + respDic['response']['utterances'] != null) { + utterances = respDic['response']['utterances']; + Logger.debug( + 'Found utterances in response.utterances: ${utterances.length} items'); + } else { + // 检查是否有错误信息 + if (respDic['error'] != null) { + Logger.error('API returned error: ${respDic['error']}'); + } else if (respDic['message'] != null) { + Logger.error('API returned message: ${respDic['message']}'); + } else { + Logger.error( + 'Unknown response format. Available keys: ${respDic.keys.toList()}'); + // 尝试查找任何包含文本的字段 + respDic.forEach((key, value) { + Logger.debug( + 'Key: $key, Value type: ${value.runtimeType}, Value: $value'); + }); + } + } + + if (utterances.isNotEmpty) { + Logger.debug('Processing ${utterances.length} utterances'); + await _addTextList(item['id'], utterances, item['template']); + // 从任务列表中移除已完成的任务 + _taskList.removeWhere((element) => element['id'] == item['id']); + _storage.write("meeting_task_list_v2", _taskList); + Logger.debug( + 'Recognition completed successfully for item ${item['id']}'); + + // 更新UI状态 - 如果是当前会议,触发UI更新 + if (item['id'] == meetingId) { + tasktype.value = 2; // 转写完成,进入分析阶段 + } + } else { + Logger.error( + "No utterances found in response. Full response: $respDic"); + print( + '🔥🔥🔥 NO UTTERANCES FOUND - STARTING SILENT AUDIO CHECK 🔥🔥🔥'); + print('=== DEBUGGING SILENT AUDIO DETECTION ==='); + print('Response keys: ${respDic.keys.toList()}'); + print('Response code: ${respDic['code']}'); + print('Response message: ${respDic['message']}'); + + // 检查是否为无声音频 + bool isSilentAudio = _checkIfSilentAudio(respDic); + print('Silent audio detection result: $isSilentAudio'); + + if (isSilentAudio) { + print('=== SILENT AUDIO DETECTED - SETTING TASKTYPE TO -1 ==='); + // 设置无声音频状态 (使用特殊值 -1) + await SqfliteApi.editMeeting(item['id'], {'tasktype': -1}); + if (item['id'] == meetingId) { + print('Setting tasktype.value to -1 for current meeting'); + tasktype.value = -1; // 无声音频 + } + + // 更新taskFinishList中的状态 + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == item['id'], + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + taskFinishData['tasktype'] = -1; + taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", taskFinishList); + try { + final s1 = Get.find(); + Map taskFinishData1 = s1.taskFinishList.firstWhere( + (element) => element['id'] == item['id'], + orElse: () => {}, + ); + if (taskFinishData1.isNotEmpty) { + taskFinishData1['tasktype'] = 0; + s1.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list", s1.taskFinishList); + } catch (_) {} + try { + final s1 = Get.find(); + Map taskFinishData1 = s1.taskFinishList.firstWhere( + (element) => element['id'] == item['id'], + orElse: () => {}, + ); + if (taskFinishData1.isNotEmpty) { + taskFinishData1['tasktype'] = -1; + s1.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list", s1.taskFinishList); + } catch (_) {} + } else { + Logger.error('Recognition failed for unknown reason'); + // 识别失败,更新数据库状态为失败 + await SqfliteApi.editMeeting(item['id'], {'tasktype': 0}); + if (item['id'] == meetingId) { + tasktype.value = 0; // 识别失败 + } + + // 更新taskFinishList中的状态 + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == item['id'], + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + taskFinishData['tasktype'] = 0; + taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", taskFinishList); + } + + // 从任务列表中移除 + _taskList.removeWhere((element) => element['id'] == item['id']); + _storage.write("meeting_task_list_v2", _taskList); + } + } catch (e) { + Logger.error('Error occurred in _fileRecognize: ${e.toString()}'); + Logger.error('Stack trace: ${StackTrace.current}'); + + // 更新数据库状态为失败 + try { + await SqfliteApi.editMeeting(item['id'], {'tasktype': 0}); + } catch (dbError) { + Logger.error('Failed to update meeting status in database: $dbError'); + } + + // 出错时也要从任务列表中移除,避免无限重试 + _taskList.removeWhere((element) => element['id'] == item['id']); + _storage.write("meeting_task_list_v2", _taskList); + + // 更新UI状态 - 如果是当前会议,显示错误状态 + if (item['id'] == meetingId) { + tasktype.value = 0; // 识别失败 + } + } + } + + /// 检查是否为无声音频 + bool _checkIfSilentAudio(Map response) { + print('Checking if silent audio, response: ${response.toString()}'); + + // 检查API响应中的错误信息或状态码 + // 根据实际API响应格式调整检测逻辑 + + // 检查是否有错误码表示无声音频 + if (response['code'] != null) { + int code = response['code']; + print('Found response code: $code'); + // 常见的无声音频错误码 + if (code == 40001 || + code == 40002 || + code == 40003 || + code == 50001 || + code == 50002 || + code == 50003) { + print('Detected silent audio by error code: $code'); + return true; + } + } + + // 检查错误消息 + if (response['message'] != null) { + String message = response['message'].toString().toLowerCase(); + print('Found response message: $message'); + if (message.contains('silent') || + message.contains('no audio') || + message.contains('无声') || + message.contains('静音') || + message.contains('empty audio') || + message.contains('no speech') || + message.contains('audio too short') || + message.contains('insufficient audio')) { + print('Detected silent audio by message: $message'); + return true; + } + } + + // 检查result中的错误信息 + if (response['result'] != null && response['result']['error'] != null) { + String error = response['result']['error'].toString().toLowerCase(); + print('Found result error: $error'); + if (error.contains('silent') || + error.contains('no audio') || + error.contains('无声') || + error.contains('静音') || + error.contains('empty audio') || + error.contains('no speech') || + error.contains('audio too short')) { + print('Detected silent audio by result error: $error'); + return true; + } + } + + // 检查是否有音频时长信息且时长为0或极短 + if (response['result'] != null && response['result']['duration'] != null) { + double duration = (response['result']['duration'] as num).toDouble(); + print('Found audio duration: $duration seconds'); + if (duration <= 0.5) { + // 音频时长小于0.5秒认为是无声 + print('Detected silent audio by short duration: $duration'); + return true; + } + } + + // 检查utterances是否为空且没有其他明确的成功标识 + bool hasUtterances = false; + if (response['result'] != null && + response['result']['utterances'] != null) { + hasUtterances = (response['result']['utterances'] as List).isNotEmpty; + } else if (response['resp'] != null && + response['resp']['utterances'] != null) { + hasUtterances = (response['resp']['utterances'] as List).isNotEmpty; + } else if (response['utterances'] != null) { + hasUtterances = (response['utterances'] as List).isNotEmpty; + } else if (response['data'] != null && + response['data']['utterances'] != null) { + hasUtterances = (response['data']['utterances'] as List).isNotEmpty; + } else if (response['response'] != null && + response['response']['utterances'] != null) { + hasUtterances = (response['response']['utterances'] as List).isNotEmpty; + } + + // 检查文本内容是否为空 + String textContent = ''; + if (response['result'] != null && response['result']['text'] != null) { + textContent = response['result']['text'].toString().trim(); + } else if (response['text'] != null) { + textContent = response['text'].toString().trim(); + } else if (response['data'] != null && response['data']['text'] != null) { + textContent = response['data']['text'].toString().trim(); + } + + // 如果文本内容为空且音频时长大于1秒,很可能是无声音频 + if (textContent.isEmpty) { + double audioDuration = 0; + if (response['audio_info'] != null && + response['audio_info']['duration'] != null) { + audioDuration = (response['audio_info']['duration'] as num).toDouble() / + 1000; // 转换为秒 + } else if (response['result'] != null && + response['result']['additions'] != null && + response['result']['additions']['duration'] != null) { + audioDuration = + (response['result']['additions']['duration'] as num).toDouble() / + 1000; // 转换为秒 + } + + print('Found empty text content, audio duration: $audioDuration seconds'); + if (audioDuration > 1.0) { + // 音频时长大于1秒但没有文本内容,认为是无声音频 + print('Detected silent audio: empty text with sufficient duration'); + return true; + } + } + + // 如果没有utterances且响应状态正常,可能是无声音频 + if (!hasUtterances) { + // 检查是否有明确的成功状态码 + bool hasSuccessCode = false; + if (response['code'] != null) { + int code = response['code']; + hasSuccessCode = (code == 0 || code == 200); + } + + // 如果有成功状态码但没有utterances,很可能是无声音频 + if (hasSuccessCode) { + print('Detected silent audio: success code but no utterances'); + return true; + } + } + + print('Not detected as silent audio'); + return false; + } + + Future _addTextList(int id, List utterances, + [Map? templateData]) async { + Logger.debug('Processing ${utterances.length} utterances for meeting $id'); + String text = ''; + String personnel = ''; + List> list = []; + + for (int i = 0; i < utterances.length; i++) { + var element = utterances[i]; + Logger.debug('Processing utterance $i: ${element.toString()}'); + + String speaker = ''; + String content = ''; + int startTime = 0; + int endTime = 0; + + // 提取说话人信息 - 检查多种可能的字段名 + if (element['additions'] != null && element['additions'].isNotEmpty) { + if (element['additions']['speaker'] != null) { + speaker = 'Speaker ${element['additions']['speaker']}'; + } + } else if (element['speaker'] != null) { + speaker = 'Speaker ${element['speaker']}'; + } else if (element['speaker_id'] != null) { + speaker = 'Speaker ${element['speaker_id']}'; + } + + // 提取文本内容 - 检查多种可能的字段名 + if (element['text'] != null) { + content = element['text'].toString(); + } else if (element['content'] != null) { + content = element['content'].toString(); + } else if (element['transcript'] != null) { + content = element['transcript'].toString(); + } else { + Logger.error('No text content found in utterance: $element'); + continue; + } + + // 提取时间信息 - 检查多种可能的字段名和格式 + if (element['start_time'] != null) { + startTime = _parseTime(element['start_time']); + } else if (element['startTime'] != null) { + startTime = _parseTime(element['startTime']); + } else if (element['begin'] != null) { + startTime = _parseTime(element['begin']); + } + + if (element['end_time'] != null) { + endTime = _parseTime(element['end_time']); + } else if (element['endTime'] != null) { + endTime = _parseTime(element['endTime']); + } else if (element['end'] != null) { + endTime = _parseTime(element['end']); + } + + if (speaker.isNotEmpty) { + text += '[$speaker]: '; + if (!personnel.contains(speaker)) { + personnel += '[$speaker] '; + } + } + + list.add({ + 'meetingid': id, + 'content': content, + 'starttime': startTime, + 'endtime': endTime, + 'speaker': speaker, + }); + text += '$content\n'; + + Logger.debug( + 'Added utterance: speaker=$speaker, content=$content, start=$startTime, end=$endTime'); + } + + Logger.debug('Total processed utterances: ${list.length}'); + var result = await SqfliteApi.insertMeetingSpeaker(list); + if (result != null) { + // 初始用于总结/概览的文本,先用原始拼接文本作为默认值 + String summarySourceText = text; + // 根据目标语言决定是否需要翻译文本列表 + final String targetLanguage = + ((templateData?['targetLanguage']) ?? '').toString(); + final String sourceLanguage = + ((templateData?['language']) ?? '').toString(); + bool needTranslate = false; + if (targetLanguage.isNotEmpty) { + // 若未指定录音语言,默认认为需要翻译;否则当目标语言与录音语言主码不同则翻译 + if (sourceLanguage.isEmpty) { + needTranslate = true; + } else { + String srcPrefix = sourceLanguage.toLowerCase().substring(0, 2); + String tgtPrefix = targetLanguage.toLowerCase().substring(0, 2); + needTranslate = srcPrefix != tgtPrefix; + } + } + if (needTranslate) { + try { + // 保持在“正在转写”状态,先进行翻译为目标语言 + await _translateTextList(id, targetLanguage); + } catch (e) { + Logger.error('Translate transcript failed: ${e.toString()}'); + } + // 翻译完成后再写入 personnel、设置已完成并刷新 UI + await SqfliteApi.editMeeting( + id, {'tasktype': 2, 'personnel': personnel}); + List speakerList = await SqfliteApi.getMeetingSpeaker(id); + List newSpeakerList = List.from(speakerList); + Synchrodata.saveOperationRecord('update', { + 'original': jsonEncode(newSpeakerList), + 'id': id, + }); + if (id == meetingId) { + textList = newSpeakerList; + tasktype.value = 2; + } + try { + final s1 = Get.find(); + if (id == s1.meetingId) { + s1.textList = newSpeakerList; + s1.tasktype.value = 2; + } + Map taskFinishData1 = s1.taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData1.isNotEmpty) { + taskFinishData1['tasktype'] = 2; + s1.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list", s1.taskFinishList); + } catch (_) {} + try { + final s1 = Get.find(); + if (id == s1.meetingId) { + s1.textList = newSpeakerList; + s1.tasktype.value = 2; + } + Map taskFinishData1 = s1.taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData1.isNotEmpty) { + taskFinishData1['tasktype'] = 2; + s1.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list", s1.taskFinishList); + } catch (_) {} + // 用最新(已翻译)的文本重建总结/概览输入 + summarySourceText = ''; + for (var element in newSpeakerList) { + final String spk = (element['speaker'] ?? '').toString(); + final String cnt = (element['content'] ?? '').toString(); + if (spk.isNotEmpty) { + summarySourceText += '[$spk]: '; + } + summarySourceText += '$cnt\n'; + } + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + taskFinishData['tasktype'] = 2; + taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", taskFinishList); + } else { + // 原有中文直出流程 + await SqfliteApi.editMeeting( + id, {'tasktype': 2, 'personnel': personnel}); + List speakerList = await SqfliteApi.getMeetingSpeaker(id); + List newSpeakerList = List.from(speakerList); + Synchrodata.saveOperationRecord('update', { + 'original': jsonEncode(newSpeakerList), + 'id': id, + }); + if (id == meetingId) { + textList = newSpeakerList; + tasktype.value = 2; + } + // 仍使用最新DB中的文本构建总结/概览输入,避免与原始拼接文本不一致 + summarySourceText = ''; + for (var element in newSpeakerList) { + final String spk = (element['speaker'] ?? '').toString(); + final String cnt = (element['content'] ?? '').toString(); + if (spk.isNotEmpty) { + summarySourceText += '[$spk]: '; + } + summarySourceText += '$cnt\n'; + } + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + taskFinishData['tasktype'] = 2; + taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", taskFinishList); + } + // 使用统一的总结服务生成概览与总结 + final String tplPrompt = ((templateData?['prompt']) ?? '').toString(); + final String tplOutline = ((templateData?['outline']) ?? '').toString(); + final String outCode = (((templateData?['targetLanguage']) ?? (templateData?['language']) ?? '') as String).toLowerCase(); + String langDesc = ''; + if (outCode.startsWith('zh')) { + langDesc = '中文'; + } else if (outCode.startsWith('en')) { + langDesc = '英文'; + } else if (outCode.startsWith('ja')) { + langDesc = '日文'; + } else if (outCode.startsWith('ko')) { + langDesc = '韩文'; + } else if (outCode.startsWith('fr')) { + langDesc = '法文'; + } else if (outCode.startsWith('de')) { + langDesc = '德文'; + } else if (outCode.startsWith('es')) { + langDesc = '西班牙文'; + } else if (outCode.startsWith('ru')) { + langDesc = '俄文'; + } else if (outCode.startsWith('it')) { + langDesc = '意大利文'; + } else if (outCode.startsWith('pt')) { + langDesc = '葡萄牙文'; + } else if (outCode.startsWith('th')) { + langDesc = '泰文'; + } else if (outCode.startsWith('vi')) { + langDesc = '越南文'; + } + final String promptToUse = tplPrompt.isEmpty + ? (langDesc.isEmpty + ? '' + : '输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。') + : (langDesc.isEmpty + ? tplPrompt + : tplPrompt + ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'); + final String outlineToUse = tplOutline.isEmpty + ? (langDesc.isEmpty + ? '' + : '输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。') + : (langDesc.isEmpty + ? tplOutline + : tplOutline + ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'); + _summaryTaskService.addTaskList(id, summarySourceText, promptToUse, outlineToUse); + } + } + + Future _translateTextList(int id, String targetLanguage) async { + // 读取刚插入的文本条目 + List speakerList = await SqfliteApi.getMeetingSpeaker(id); + // 构造简洁的可翻译列表 + List toTranslate = speakerList + .map((e) => { + 'id': e['id'], + 'speaker': (e['speaker'] ?? '').toString(), + 'content': (e['content'] ?? '').toString(), + }) + .toList(); + + final String userJson = jsonEncode({ + 'targetLanguage': targetLanguage, + 'items': toTranslate, + }); + + // 根据目标语言构建提示语 + String tgtName = targetLanguage.toLowerCase(); + String langDesc = '目标语言'; + if (tgtName.startsWith('zh')) { + langDesc = '中文'; + } else if (tgtName.startsWith('en')) { + langDesc = '英文'; + } else if (tgtName.startsWith('ja')) { + langDesc = '日文'; + } else if (tgtName.startsWith('ko')) { + langDesc = '韩文'; + } else if (tgtName.startsWith('fr')) { + langDesc = '法文'; + } else if (tgtName.startsWith('de')) { + langDesc = '德文'; + } else if (tgtName.startsWith('es')) { + langDesc = '西班牙文'; + } else if (tgtName.startsWith('ru')) { + langDesc = '俄文'; + } else if (tgtName.startsWith('it')) { + langDesc = '意大利文'; + } else if (tgtName.startsWith('pt')) { + langDesc = '葡萄牙文'; + } else if (tgtName.startsWith('th')) { + langDesc = '泰文'; + } else if (tgtName.startsWith('vi')) { + langDesc = '越南文'; + } + + final String systemPrompt = '你是专业的翻译助手。请将每条items中的content精准翻译为' + + langDesc + + ',保持术语专业、语气自然。仅返回JSON数组,元素形如{"id":<原id>,"content":"<翻译后文本>"},不要额外说明,不要漏掉任何一条。'; + + final String? translated = await DeapsoundAIService().sendMessage( + messages: [ + {'role': 'system', 'content': systemPrompt}, + {'role': 'user', 'content': userJson}, + ], + ); + + if (translated == null || translated.isEmpty) return; + List arr; + try { + arr = jsonDecode(translated); + } catch (_) { + // 若模型返回包裹对象,尝试取字段 + try { + final obj = jsonDecode(translated); + arr = (obj is Map && obj['data'] is List) ? (obj['data'] as List) : []; + } catch (e) { + Logger.error('Parse translation JSON failed: ${e.toString()}'); + return; + } + } + if (arr.isEmpty) return; + // 批量更新 + for (var item in arr) { + if (item is Map && item['id'] != null) { + await SqfliteApi.editMeetingSpeaker([ + { + 'id': item['id'], + 'content': (item['content'] ?? '').toString(), + } + ]); + } + } + // 刷新缓存 + if (id == meetingId) { + textList = await SqfliteApi.getMeetingSpeaker(id); + // 主动触发监听刷新 UI(MeetingDetailsController 通过监听 tasktype 同步 textList) + try { + tasktype.refresh(); + } catch (_) {} + } + } + + void aiServiceMessage(int id, String text, [Map? templateData]) { + int awaitQuantity = 0; + String summaryText = ''; + String overviewText = ''; + // 模板参数透传 + final bool tplIsSpeaker = ((templateData?['isSpeaker']) ?? false) as bool; + final String tplLanguage = ((templateData?['language']) ?? '').toString(); + final String tplOutline = ((templateData?['outline']) ?? '').toString(); + final String tplPrompt = ((templateData?['prompt']) ?? '').toString(); + final String tplTargetLang = + ((templateData?['targetLanguage']) ?? '').toString(); + + // 构建系统prompt,如果有模板数据则整合模板prompt + String systemPrompt = + '您将扮演一名会议总结专家,拥有超强的信息归纳与细分能力。不仅能高效处理各类通用场景会议,精准提炼会议核心内容,为后续思维导图生成奠定坚实基础。若涉及人员,人员名称保留[]。'; + if (templateData != null) { + if (tplPrompt.isNotEmpty) { + systemPrompt += ' 特别要求:' + tplPrompt; + } + if (tplOutline.isNotEmpty) { + systemPrompt += ' 大纲:' + tplOutline; + } + // 当目标语言为空(自动识别)时,使用录音语言作为输出语言约束;否则使用目标语言 + final String outCode = + tplTargetLang.isNotEmpty ? tplTargetLang : tplLanguage; + if (outCode.isNotEmpty) { + final String t = outCode.toLowerCase(); + String langDesc = '目标语言'; + if (t.startsWith('zh')) { + langDesc = '中文'; + } else if (t.startsWith('en')) { + langDesc = '英文'; + } else if (t.startsWith('ja')) { + langDesc = '日文'; + } else if (t.startsWith('ko')) { + langDesc = '韩文'; + } else if (t.startsWith('fr')) { + langDesc = '法文'; + } else if (t.startsWith('de')) { + langDesc = '德文'; + } else if (t.startsWith('es')) { + langDesc = '西班牙文'; + } else if (t.startsWith('ru')) { + langDesc = '俄文'; + } else if (t.startsWith('it')) { + langDesc = '意大利文'; + } else if (t.startsWith('pt')) { + langDesc = '葡萄牙文'; + } else if (t.startsWith('hi')) { + langDesc = '印地文'; + } else if (t.startsWith('th')) { + langDesc = '泰文'; + } else if (t.startsWith('vi')) { + langDesc = '越南文'; + } + systemPrompt += ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'; + } + } + + DeapsoundAIService().sendMessage( + messages: [ + { + 'role': 'system', + 'content': systemPrompt, + }, + {'role': 'user', 'content': text}, + ], + ).then((value) { + awaitQuantity++; + summaryText = value ?? ''; + if (awaitQuantity >= 2) { + _aiServiceValue(id, summaryText, overviewText); + } + }); + // 构建第二个系统prompt,如果有模板数据则整合模板prompt + String systemPrompt2 = '您将扮演一名会议事件归纳专家,能准确抓住会议中事件的关键点,并每个事件只需一句话精简总结。'; + if (templateData != null) { + if (tplPrompt.isNotEmpty) { + systemPrompt2 += ' 特别要求:' + tplPrompt; + } + if (tplOutline.isNotEmpty) { + systemPrompt2 += ' 大纲:' + tplOutline; + } + final String outCode2 = + tplTargetLang.isNotEmpty ? tplTargetLang : tplLanguage; + if (outCode2.isNotEmpty) { + final String t = outCode2.toLowerCase(); + String langDesc = '目标语言'; + if (t.startsWith('zh')) { + langDesc = '中文'; + } else if (t.startsWith('en')) { + langDesc = '英文'; + } else if (t.startsWith('ja')) { + langDesc = '日文'; + } else if (t.startsWith('ko')) { + langDesc = '韩文'; + } else if (t.startsWith('fr')) { + langDesc = '法文'; + } else if (t.startsWith('de')) { + langDesc = '德文'; + } else if (t.startsWith('es')) { + langDesc = '西班牙文'; + } else if (t.startsWith('ru')) { + langDesc = '俄文'; + } else if (t.startsWith('it')) { + langDesc = '意大利文'; + } else if (t.startsWith('pt')) { + langDesc = '葡萄牙文'; + } else if (t.startsWith('hi')) { + langDesc = '印地文'; + } else if (t.startsWith('th')) { + langDesc = '泰文'; + } else if (t.startsWith('vi')) { + langDesc = '越南文'; + } + systemPrompt2 += ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'; + } + } + + DeapsoundAIService().sendMessage( + messages: [ + { + 'role': 'system', + 'content': systemPrompt2, + }, + {'role': 'user', 'content': text}, + ], + ).then((value) { + awaitQuantity++; + overviewText = value ?? ''; + if (awaitQuantity >= 2) { + _aiServiceValue(id, summaryText, overviewText); + } + }); + } + + void _aiServiceValue(int id, String summaryText, String overviewText) { + SqfliteApi.editMeeting(id, { + 'tasktype': 3, + 'summary': summaryText, + 'overview': overviewText, + }); + if (id == meetingId) { + summary = summaryText; + overview = overviewText; + tasktype.value = 3; + } + try { + final s1 = Get.find(); + if (id == s1.meetingId) { + s1.tasktype.value = 3; + } + } catch (_) {} + Map taskFinishData = taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData.isNotEmpty) { + taskFinishData['tasktype'] = 3; + taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", taskFinishList); + } +} diff --git a/lib/data/services/meeting/meeting_upload_service.dart b/lib/data/services/meeting/meeting_upload_service.dart index 63fde0f02..1ef2d59e2 100644 --- a/lib/data/services/meeting/meeting_upload_service.dart +++ b/lib/data/services/meeting/meeting_upload_service.dart @@ -5,6 +5,7 @@ import '../../../core/utils/logger.dart'; import '../../../core/utils/upload_oss.dart'; import '../db/sqflite_api.dart'; import 'meeting_task_service.dart'; +import 'meeting_task_service2.dart'; class MeetingUploadService extends GetxService { final _taskService = Get.find(); @@ -43,6 +44,12 @@ class MeetingUploadService extends GetxService { String rootDir, { bool isUpload = true, }) async { + bool flash = false; + try { + final details = await SqfliteApi.getMeetingDetails(id); + final int seconds = (details['seconds'] ?? 0) as int; + flash = seconds > 0 && seconds <= 7200; + } catch (_) {} uploadList.add({ 'id': id, 'filepath': filePath, @@ -53,6 +60,7 @@ class MeetingUploadService extends GetxService { 'language': '', 'prompt': '', 'outline': '', + 'useFlashASR': flash, }); _storage.write("meeting_upload_list", uploadList); if (isUpload) { @@ -82,15 +90,28 @@ class MeetingUploadService extends GetxService { {'audiourl': audiourl}, ); if (element['isTask']) { - await _taskService.addTaskList( - element['id'], - audiourl, - '', - element['isSpeaker'], - element['language'], - element['prompt'], - element['outline'], - ); + if (element['useFlashASR'] == true) { + final service2 = Get.find(); + await service2.addTaskList( + element['id'], + audiourl, + isSpeaker: element['isSpeaker'], + language: element['language'], + targetLanguage: '', + prompt: element['prompt'], + outline: element['outline'], + ); + } else { + await _taskService.addTaskList( + element['id'], + audiourl, + '', + element['isSpeaker'], + element['language'], + element['prompt'], + element['outline'], + ); + } } element['audiourl'] = audiourl; uploadProgress.value = 0.0; diff --git a/lib/data/services/meeting/summary_task_service.dart b/lib/data/services/meeting/summary_task_service.dart index 5657c8b74..c29741359 100644 --- a/lib/data/services/meeting/summary_task_service.dart +++ b/lib/data/services/meeting/summary_task_service.dart @@ -5,9 +5,11 @@ import '../../../core/utils/logger.dart'; import '../db/sqflite_api.dart'; import '../deapsound_ai_service.dart'; import 'meeting_task_service.dart'; +import 'meeting_task_service2.dart'; class SummaryTaskService extends GetxService { final _taskService = Get.find(); + final _taskService2 = Get.find(); final GetStorage _storage = GetStorage(); @@ -137,11 +139,24 @@ class SummaryTaskService extends GetxService { 'summary': summaryText, 'overview': overviewText, }); + + // 更新 V1 Service 状态 if (id == _taskService.meetingId) { summary = summaryText; overview = overviewText; _taskService.tasktype.value = 3; } + + // 更新 V2 Service 状态 + if (id == _taskService2.meetingId) { + summary = summaryText; + overview = overviewText; + _taskService2.summary = summaryText; + _taskService2.overview = overviewText; + _taskService2.tasktype.value = 3; + } + + // 更新 V1 任务完成列表 Map taskFinishData = _taskService.taskFinishList.firstWhere( (element) => element['id'] == id, orElse: () => {}, @@ -151,6 +166,18 @@ class SummaryTaskService extends GetxService { _taskService.taskFinishList.refresh(); } _storage.write("meeting_task_finish_list", _taskService.taskFinishList); + + // 更新 V2 任务完成列表 + Map taskFinishData2 = _taskService2.taskFinishList.firstWhere( + (element) => element['id'] == id, + orElse: () => {}, + ); + if (taskFinishData2.isNotEmpty) { + taskFinishData2['tasktype'] = 3; + _taskService2.taskFinishList.refresh(); + } + _storage.write("meeting_task_finish_list_v2", _taskService2.taskFinishList); + _taskList.removeWhere((element) => element['id'] == id); _storage.write("summary_task_list", _taskList); _isTask = false; diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index a65074b23..b7cd13d23 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -14,6 +14,7 @@ import 'package:path_provider/path_provider.dart'; import '../../../core/utils/synchrodata.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/meeting/meeting_task_service.dart'; +import '../../../data/services/meeting/meeting_task_service2.dart'; import '../../../data/services/meeting/meeting_upload_service.dart'; import '../../../data/services/meeting/summary_task_service.dart'; import '../model/meeting_details_model.dart'; @@ -28,9 +29,11 @@ class MeetingDetailsController extends GetxController late AnimationController lottieController; late final Worker _tasktypeListener; + late final Worker _tasktypeListenerV2; final _meetingUploadService = Get.find(); final _meetingTaskService = Get.find(); + final _meetingTaskService2 = Get.find(); final _summaryTaskService = Get.find(); final PlayerController playerController = PlayerController(); @@ -106,11 +109,25 @@ class MeetingDetailsController extends GetxController } meetingDetails.refresh(); }); + _tasktypeListenerV2 = ever(_meetingTaskService2.tasktype, (value) { + meetingDetails.value.tasktype = value; + if (_everType <= value) { + if (value == 2) { + textList.value = _meetingTaskService2.textList; + } else if (value == 3) { + meetingDetails.value.summary = _meetingTaskService2.summary; + meetingDetails.value.overview = _meetingTaskService2.overview; + summaryNumberModifications.value++; + } + } + meetingDetails.refresh(); + }); } @override void onClose() { _tasktypeListener.dispose(); + _tasktypeListenerV2.dispose(); _currentDurationSubscription?.cancel(); _playerStateSubscription?.cancel(); tabController.dispose(); @@ -122,6 +139,7 @@ class MeetingDetailsController extends GetxController void popInvokedWithResult(didPop, result) async { _meetingUploadService.removeUpload(id); _meetingTaskService.removeTask(id); + _meetingTaskService2.removeTask(id); _meetingTaskService.meetingId = 0; } @@ -257,10 +275,30 @@ class MeetingDetailsController extends GetxController bool isSpeaker, String language, String prompt, String outline) async { EasyLoading.show(); _everType = 2; - String audiourl = meetingData.value.audiourl; - String taskId = meetingDetails.value.taskid; - await _meetingTaskService.addTask( - id, audiourl, taskId, isSpeaker, language, prompt, outline); + final String audiourl = meetingData.value.audiourl; + final int seconds = meetingData.value.seconds; + if (seconds > 0 && seconds <= 7200) { + await _meetingTaskService2.addTask( + id, + audiourl, + isSpeaker: isSpeaker, + language: language, + targetLanguage: '', + prompt: prompt, + outline: outline, + ); + } else { + final String taskId = meetingDetails.value.taskid; + await _meetingTaskService.addTask( + id, + audiourl, + taskId, + isSpeaker, + language, + prompt, + outline, + ); + } meetingDetails.value.tasktype = 1; meetingDetails.refresh(); EasyLoading.dismiss(); diff --git a/lib/modules/meeting/controllers/meeting_home_controller.dart b/lib/modules/meeting/controllers/meeting_home_controller.dart index db2ae56cc..9ac1bb6e7 100644 --- a/lib/modules/meeting/controllers/meeting_home_controller.dart +++ b/lib/modules/meeting/controllers/meeting_home_controller.dart @@ -19,6 +19,7 @@ import '../../../core/utils/permission_util.dart'; import 'package:share_plus/share_plus.dart'; import '../../../data/services/meeting/meeting_task_service.dart'; +import '../../../data/services/meeting/meeting_task_service2.dart'; import '../../../data/services/meeting/meeting_upload_service.dart'; import '../../../data/services/meeting/summary_task_service.dart'; import '../../../routes/app_routes.dart'; @@ -29,6 +30,7 @@ class MeetingHomeController extends GetxController { final meetingController = Get.find(); final meetingUploadService = Get.find(); final meetingTaskService = Get.find(); + final meetingTaskService2 = Get.find(); final summaryTaskService = Get.find(); final AudioPlayer _audioPlayer = AudioPlayer(); @@ -585,6 +587,7 @@ class MeetingHomeController extends GetxController { _deleteFile(filepath); meetingUploadService.removeUpload(id, isDeleteTask: true); meetingTaskService.removeTask(id, isDeleteTask: true); + meetingTaskService2.removeTask(id, isDeleteTask: true); } // 删除文件 From de79513134f209b72a03dbf9c0b71a40c905888c Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 26 Nov 2025 14:28:00 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(=E4=BC=9A=E8=AE=AE):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BC=9A=E8=AE=AE=E6=9C=8D=E5=8A=A1=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=92=8C=E9=80=BB=E8=BE=91=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加对会议服务类型的本地存储功能,并根据存储类型调整任务处理逻辑 --- .../services/meeting/meeting_upload_service.dart | 1 + .../controllers/meeting_details_controller.dart | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/data/services/meeting/meeting_upload_service.dart b/lib/data/services/meeting/meeting_upload_service.dart index 1ef2d59e2..53a5f57d3 100644 --- a/lib/data/services/meeting/meeting_upload_service.dart +++ b/lib/data/services/meeting/meeting_upload_service.dart @@ -50,6 +50,7 @@ class MeetingUploadService extends GetxService { final int seconds = (details['seconds'] ?? 0) as int; flash = seconds > 0 && seconds <= 7200; } catch (_) {} + _storage.write("meeting_service_type_$id", flash ? 'fast' : 'standard'); uploadList.add({ 'id': id, 'filepath': filePath, diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index b7cd13d23..b197a8b86 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:path_provider/path_provider.dart'; import '../../../core/utils/synchrodata.dart'; @@ -93,10 +94,16 @@ class MeetingDetailsController extends GetxController ..duration = const Duration(seconds: 2); final Map args = Get.arguments ?? {}; id = args['id']; + final storage = GetStorage(); + final String svc = + (storage.read("meeting_service_type_$id") ?? '').toString(); + final bool isFast = svc == 'fast'; _meetingTaskService.meetingId = id; + _meetingTaskService2.meetingId = id; _getDataDetails(); _loadJS(); _tasktypeListener = ever(_meetingTaskService.tasktype, (value) { + if (isFast) return; meetingDetails.value.tasktype = value; if (_everType <= value) { if (value == 2) { @@ -110,13 +117,14 @@ class MeetingDetailsController extends GetxController meetingDetails.refresh(); }); _tasktypeListenerV2 = ever(_meetingTaskService2.tasktype, (value) { + if (!isFast) return; meetingDetails.value.tasktype = value; if (_everType <= value) { if (value == 2) { textList.value = _meetingTaskService2.textList; } else if (value == 3) { - meetingDetails.value.summary = _meetingTaskService2.summary; - meetingDetails.value.overview = _meetingTaskService2.overview; + meetingDetails.value.summary = _summaryTaskService.summary; + meetingDetails.value.overview = _summaryTaskService.overview; summaryNumberModifications.value++; } } @@ -141,6 +149,7 @@ class MeetingDetailsController extends GetxController _meetingTaskService.removeTask(id); _meetingTaskService2.removeTask(id); _meetingTaskService.meetingId = 0; + _meetingTaskService2.meetingId = 0; } void _getDataDetails() async { @@ -277,6 +286,9 @@ class MeetingDetailsController extends GetxController _everType = 2; final String audiourl = meetingData.value.audiourl; final int seconds = meetingData.value.seconds; + final storage = GetStorage(); + storage.write("meeting_service_type_$id", + (seconds > 0 && seconds <= 7200) ? 'fast' : 'standard'); if (seconds > 0 && seconds <= 7200) { await _meetingTaskService2.addTask( id, From f67d3f83b7102d5d1dbf0ddc102da45e424d7ae2 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 26 Nov 2025 14:34:10 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(meeting):=20=E4=BF=AE=E5=A4=8D=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=B1=BB=E5=9E=8B=E7=9B=91=E5=90=AC=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=92=8C=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正meeting_details_controller中任务类型监听逻辑,确保在快速模式下正确处理类型3的任务 调整meeting_task_service2中的代码格式,提高可读性 移除废弃的aiServiceMessage方法及相关代码 --- .../meeting/meeting_task_service2.dart | 172 +----------------- .../meeting_details_controller.dart | 4 +- 2 files changed, 8 insertions(+), 168 deletions(-) diff --git a/lib/data/services/meeting/meeting_task_service2.dart b/lib/data/services/meeting/meeting_task_service2.dart index e04681dff..b6b455c43 100644 --- a/lib/data/services/meeting/meeting_task_service2.dart +++ b/lib/data/services/meeting/meeting_task_service2.dart @@ -851,7 +851,10 @@ class MeetingTaskService2 extends GetxService { // 使用统一的总结服务生成概览与总结 final String tplPrompt = ((templateData?['prompt']) ?? '').toString(); final String tplOutline = ((templateData?['outline']) ?? '').toString(); - final String outCode = (((templateData?['targetLanguage']) ?? (templateData?['language']) ?? '') as String).toLowerCase(); + final String outCode = (((templateData?['targetLanguage']) ?? + (templateData?['language']) ?? + '') as String) + .toLowerCase(); String langDesc = ''; if (outCode.startsWith('zh')) { langDesc = '中文'; @@ -892,7 +895,8 @@ class MeetingTaskService2 extends GetxService { : (langDesc.isEmpty ? tplOutline : tplOutline + ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'); - _summaryTaskService.addTaskList(id, summarySourceText, promptToUse, outlineToUse); + _summaryTaskService.addTaskList( + id, summarySourceText, promptToUse, outlineToUse); } } @@ -988,168 +992,4 @@ class MeetingTaskService2 extends GetxService { } catch (_) {} } } - - void aiServiceMessage(int id, String text, [Map? templateData]) { - int awaitQuantity = 0; - String summaryText = ''; - String overviewText = ''; - // 模板参数透传 - final bool tplIsSpeaker = ((templateData?['isSpeaker']) ?? false) as bool; - final String tplLanguage = ((templateData?['language']) ?? '').toString(); - final String tplOutline = ((templateData?['outline']) ?? '').toString(); - final String tplPrompt = ((templateData?['prompt']) ?? '').toString(); - final String tplTargetLang = - ((templateData?['targetLanguage']) ?? '').toString(); - - // 构建系统prompt,如果有模板数据则整合模板prompt - String systemPrompt = - '您将扮演一名会议总结专家,拥有超强的信息归纳与细分能力。不仅能高效处理各类通用场景会议,精准提炼会议核心内容,为后续思维导图生成奠定坚实基础。若涉及人员,人员名称保留[]。'; - if (templateData != null) { - if (tplPrompt.isNotEmpty) { - systemPrompt += ' 特别要求:' + tplPrompt; - } - if (tplOutline.isNotEmpty) { - systemPrompt += ' 大纲:' + tplOutline; - } - // 当目标语言为空(自动识别)时,使用录音语言作为输出语言约束;否则使用目标语言 - final String outCode = - tplTargetLang.isNotEmpty ? tplTargetLang : tplLanguage; - if (outCode.isNotEmpty) { - final String t = outCode.toLowerCase(); - String langDesc = '目标语言'; - if (t.startsWith('zh')) { - langDesc = '中文'; - } else if (t.startsWith('en')) { - langDesc = '英文'; - } else if (t.startsWith('ja')) { - langDesc = '日文'; - } else if (t.startsWith('ko')) { - langDesc = '韩文'; - } else if (t.startsWith('fr')) { - langDesc = '法文'; - } else if (t.startsWith('de')) { - langDesc = '德文'; - } else if (t.startsWith('es')) { - langDesc = '西班牙文'; - } else if (t.startsWith('ru')) { - langDesc = '俄文'; - } else if (t.startsWith('it')) { - langDesc = '意大利文'; - } else if (t.startsWith('pt')) { - langDesc = '葡萄牙文'; - } else if (t.startsWith('hi')) { - langDesc = '印地文'; - } else if (t.startsWith('th')) { - langDesc = '泰文'; - } else if (t.startsWith('vi')) { - langDesc = '越南文'; - } - systemPrompt += ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'; - } - } - - DeapsoundAIService().sendMessage( - messages: [ - { - 'role': 'system', - 'content': systemPrompt, - }, - {'role': 'user', 'content': text}, - ], - ).then((value) { - awaitQuantity++; - summaryText = value ?? ''; - if (awaitQuantity >= 2) { - _aiServiceValue(id, summaryText, overviewText); - } - }); - // 构建第二个系统prompt,如果有模板数据则整合模板prompt - String systemPrompt2 = '您将扮演一名会议事件归纳专家,能准确抓住会议中事件的关键点,并每个事件只需一句话精简总结。'; - if (templateData != null) { - if (tplPrompt.isNotEmpty) { - systemPrompt2 += ' 特别要求:' + tplPrompt; - } - if (tplOutline.isNotEmpty) { - systemPrompt2 += ' 大纲:' + tplOutline; - } - final String outCode2 = - tplTargetLang.isNotEmpty ? tplTargetLang : tplLanguage; - if (outCode2.isNotEmpty) { - final String t = outCode2.toLowerCase(); - String langDesc = '目标语言'; - if (t.startsWith('zh')) { - langDesc = '中文'; - } else if (t.startsWith('en')) { - langDesc = '英文'; - } else if (t.startsWith('ja')) { - langDesc = '日文'; - } else if (t.startsWith('ko')) { - langDesc = '韩文'; - } else if (t.startsWith('fr')) { - langDesc = '法文'; - } else if (t.startsWith('de')) { - langDesc = '德文'; - } else if (t.startsWith('es')) { - langDesc = '西班牙文'; - } else if (t.startsWith('ru')) { - langDesc = '俄文'; - } else if (t.startsWith('it')) { - langDesc = '意大利文'; - } else if (t.startsWith('pt')) { - langDesc = '葡萄牙文'; - } else if (t.startsWith('hi')) { - langDesc = '印地文'; - } else if (t.startsWith('th')) { - langDesc = '泰文'; - } else if (t.startsWith('vi')) { - langDesc = '越南文'; - } - systemPrompt2 += ' 输出语言:' + langDesc + '。请全程使用' + langDesc + '输出。'; - } - } - - DeapsoundAIService().sendMessage( - messages: [ - { - 'role': 'system', - 'content': systemPrompt2, - }, - {'role': 'user', 'content': text}, - ], - ).then((value) { - awaitQuantity++; - overviewText = value ?? ''; - if (awaitQuantity >= 2) { - _aiServiceValue(id, summaryText, overviewText); - } - }); - } - - void _aiServiceValue(int id, String summaryText, String overviewText) { - SqfliteApi.editMeeting(id, { - 'tasktype': 3, - 'summary': summaryText, - 'overview': overviewText, - }); - if (id == meetingId) { - summary = summaryText; - overview = overviewText; - tasktype.value = 3; - } - try { - final s1 = Get.find(); - if (id == s1.meetingId) { - s1.tasktype.value = 3; - } - } catch (_) {} - Map taskFinishData = taskFinishList.firstWhere( - (element) => element['id'] == id, - orElse: () => {}, - ); - if (taskFinishData.isNotEmpty) { - taskFinishData['tasktype'] = 3; - taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list_v2", taskFinishList); - } } diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index b197a8b86..96996c26c 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -103,10 +103,10 @@ class MeetingDetailsController extends GetxController _getDataDetails(); _loadJS(); _tasktypeListener = ever(_meetingTaskService.tasktype, (value) { - if (isFast) return; + if (isFast && value != 3) return; meetingDetails.value.tasktype = value; if (_everType <= value) { - if (value == 2) { + if (!isFast && value == 2) { textList.value = _meetingTaskService.textList; } else if (value == 3) { meetingDetails.value.summary = _summaryTaskService.summary; From bd045b0c5f103185a92c147e7513481ff8ab8596 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 26 Nov 2025 14:55:30 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(=E4=BC=9A=E8=AE=AE=E4=BB=BB=E5=8A=A1):?= =?UTF-8?q?=20=E5=BC=95=E5=85=A5=E4=BB=BB=E5=8A=A1=E8=81=9A=E5=90=88?= =?UTF-8?q?=E5=99=A8=E7=BB=9F=E4=B8=80=E7=AE=A1=E7=90=86=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=8E=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构会议任务服务架构,新增 MeetingTaskAggregator 服务聚合器,统一管理标准版与极速版任务服务的状态与列表数据: 1. 移除服务间直接状态同步逻辑,改为通过聚合器统一对外暴露状态 2. 新增聚合器服务并在初始绑定中注册 3. 修改各控制器使用聚合器替代直接服务引用 4. 优化极速版服务日志输出为中文 5. 清理冗余的状态同步代码 --- lib/core/bindings/initial_binding.dart | 6 +- .../meeting/meeting_task_aggregator.dart | 105 ++++++++ .../meeting/meeting_task_service2.dart | 228 ++++++------------ .../meeting_details_controller.dart | 3 + .../controllers/meeting_home_controller.dart | 2 + .../meeting/views/meeting_home_view.dart | 4 +- 6 files changed, 190 insertions(+), 158 deletions(-) create mode 100644 lib/data/services/meeting/meeting_task_aggregator.dart diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 3ea94689f..2870209ff 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -12,6 +12,7 @@ import '../../../data/services/speech_impl/azure_ast_service.dart'; import 'package:get/get.dart'; import '../../data/services/meeting/meeting_task_service.dart'; import '../../data/services/meeting/meeting_task_service2.dart'; +import '../../data/services/meeting/meeting_task_aggregator.dart'; import '../../data/services/meeting/meeting_upload_service.dart'; import '../../data/services/novice_guide_service.dart'; import '../../data/services/speech_factory.dart'; @@ -71,8 +72,11 @@ class InitialBinding extends Bindings { // 会议助手任务服务 Get.lazyPut(() => MeetingTaskService(), fenix: true); - // 会议助手任务服务(极速识别版) + // 会议助手任务服务(极速识别版,2小时内音频) Get.lazyPut(() => MeetingTaskService2(), fenix: true); + // 会议助手任务服务聚合器(统一列表与状态) + Get.lazyPut(() => MeetingTaskAggregator(), + fenix: true); // 会议助手总结任务服务 Get.lazyPut(() => SummaryTaskService(), fenix: true); diff --git a/lib/data/services/meeting/meeting_task_aggregator.dart b/lib/data/services/meeting/meeting_task_aggregator.dart new file mode 100644 index 000000000..5de375cda --- /dev/null +++ b/lib/data/services/meeting/meeting_task_aggregator.dart @@ -0,0 +1,105 @@ +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; + +import 'meeting_task_service.dart'; +import 'meeting_task_service2.dart'; + +/// 会议助手任务服务聚合器(统一列表与状态) +class MeetingTaskAggregator extends GetxService { + final GetStorage _storage = GetStorage(); + + final MeetingTaskService s1 = Get.find(); + final MeetingTaskService2 s2 = Get.find(); + + RxList taskFinishList = [].obs; + RxInt tasktype = 1.obs; + int meetingId = 0; + List textList = []; + + Worker? _w1; + Worker? _w2; + Worker? _wTasktype; + + @override + void onInit() { + super.onInit(); + _w1 = ever(s1.taskFinishList, (_) => _rebuild()); + _w2 = ever(s2.taskFinishList, (_) => _rebuild()); + _rebuild(); + } + + void setMeeting(int id) { + meetingId = id; + final String svc = + (_storage.read("meeting_service_type_$id") ?? '').toString(); + final bool isFast = svc == 'fast'; + _wTasktype?.dispose(); + if (isFast) { + _wTasktype = ever(s2.tasktype, (value) { + tasktype.value = value; + if (value == 2) { + textList = s2.textList; + } + }); + } else { + _wTasktype = ever(s1.tasktype, (value) { + tasktype.value = value; + if (value == 2) { + textList = s1.textList; + } + }); + } + } + + void clearMeeting() { + meetingId = 0; + _wTasktype?.dispose(); + _wTasktype = null; + } + + void _rebuild() { + final Set ids = {}; + for (var e in s1.taskFinishList) { + if (e is Map && e['id'] is int) ids.add(e['id'] as int); + } + for (var e in s2.taskFinishList) { + if (e is Map && e['id'] is int) ids.add(e['id'] as int); + } + final List unified = []; + for (final id in ids) { + final String svc = + (_storage.read("meeting_service_type_$id") ?? '').toString(); + final Map a = s1.taskFinishList.firstWhere( + (x) => x['id'] == id, + orElse: () => {}, + ); + final Map b = s2.taskFinishList.firstWhere( + (x) => x['id'] == id, + orElse: () => {}, + ); + int va = a.isNotEmpty ? ((a['tasktype'] ?? 0) as int) : -999; + int vb = b.isNotEmpty ? ((b['tasktype'] ?? 0) as int) : -999; + int v; + if (svc == 'fast') { + if (va != -999 && vb != -999) { + v = va >= vb ? va : vb; + } else { + v = vb != -999 ? vb : va; + } + } else if (svc == 'standard') { + if (va != -999 && vb != -999) { + v = va >= vb ? va : vb; + } else { + v = va != -999 ? va : vb; + } + } else { + if (va == -999 && vb == -999) { + continue; + } + v = va == -999 ? vb : (vb == -999 ? va : (va >= vb ? va : vb)); + } + unified.add({'id': id, 'tasktype': v}); + } + taskFinishList.value = unified; + } +} diff --git a/lib/data/services/meeting/meeting_task_service2.dart b/lib/data/services/meeting/meeting_task_service2.dart index b6b455c43..8a414c076 100644 --- a/lib/data/services/meeting/meeting_task_service2.dart +++ b/lib/data/services/meeting/meeting_task_service2.dart @@ -10,7 +10,7 @@ import '../../../core/utils/logger.dart'; import '../../../core/utils/synchrodata.dart'; import '../deapsound_ai_service.dart'; import 'meeting_upload_service.dart'; -import 'meeting_task_service.dart'; +// 移除标准版服务桥接,改为通过聚合器对外暴露统一状态 import 'summary_task_service.dart'; //火山引擎录音文件识别大模型-极速版 @@ -140,43 +140,31 @@ class MeetingTaskService2 extends GetxService { taskFinishList.add({'id': id, 'tasktype': 1}); _storage.write("meeting_task_list_v2", _taskList); _storage.write("meeting_task_finish_list_v2", taskFinishList); - try { - final s1 = Get.find(); - Map taskFinishData1 = s1.taskFinishList.firstWhere( - (element) => element['id'] == id, - orElse: () => {}, - ); - if (taskFinishData1.isNotEmpty) { - taskFinishData1['tasktype'] = 3; - s1.taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list", s1.taskFinishList); - } catch (_) {} + // 由聚合器统一对外呈现状态,不直接写入标准版列表 _executeTask(); } void _executeTask() async { if (_isTask) return; _isTask = true; - Logger.debug('Starting task execution. Task count: ${_taskList.length}'); + Logger.debug('开始执行任务,任务数量:${_taskList.length}'); while (_taskList.isNotEmpty) { - Logger.debug('Task execution loop. Remaining tasks: ${_taskList.length}'); + Logger.debug('任务循环执行,剩余任务:${_taskList.length}'); await Future.delayed(const Duration(seconds: 2)); // 创建任务列表的副本,避免在迭代过程中修改原列表 List tasksCopy = List.from(_taskList); for (var element in tasksCopy) { - Logger.debug( - 'Processing task: ${element['id']} with audio: ${element['audiourl']}'); + Logger.debug('处理任务:${element['id']},音频:${element['audiourl']}'); // 检查任务是否仍在列表中(可能已被其他地方移除) if (_taskList.any((task) => task['id'] == element['id'])) { try { await _fileRecognize(element); } catch (e) { - Logger.error('Failed to process task ${element['id']}: $e'); + Logger.error('处理任务失败:${element['id']},错误:$e'); // 确保失败的任务被移除,避免无限循环 _taskList.removeWhere((task) => task['id'] == element['id']); _storage.write("meeting_task_list_v2", _taskList); @@ -185,22 +173,19 @@ class MeetingTaskService2 extends GetxService { await SqfliteApi.editMeeting(element['id'], {'tasktype': 0}); } } else { - Logger.debug( - 'Task ${element['id']} was already removed from task list'); + Logger.debug('任务 ${element['id']} 已从任务列表移除'); } } // 如果没有任务被处理,避免无限循环 if (_taskList.length == tasksCopy.length) { - Logger.warning( - 'No tasks were processed in this iteration. Breaking loop to avoid infinite execution.'); + Logger.warning('本次循环未处理任何任务,为避免无限执行终止循环'); break; } } _isTask = false; - Logger.debug( - 'Task execution completed. Remaining tasks: ${_taskList.length}'); + Logger.debug('任务执行完成,剩余任务:${_taskList.length}'); } void removeTask(int id, {bool isDeleteTask = false}) { @@ -260,9 +245,9 @@ class MeetingTaskService2 extends GetxService { try { // 记录请求详情 - Logger.debug('API Request URL: $_serviceUrl'); - Logger.debug('API Request Headers: $requestHeaders'); - Logger.debug('API Request Body: $requestBody'); + Logger.debug('API 请求地址:$_serviceUrl'); + Logger.debug('API 请求头:$requestHeaders'); + Logger.debug('API 请求体:$requestBody'); final response = await http.post( Uri.parse(_serviceUrl), @@ -270,30 +255,27 @@ class MeetingTaskService2 extends GetxService { body: requestBody, ); - Logger.debug('API Response Status: ${response.statusCode}'); - Logger.debug('API Response Headers: ${response.headers}'); + Logger.debug('API 响应状态码:${response.statusCode}'); + Logger.debug('API 响应头:${response.headers}'); if (response.statusCode != 200) { - Logger.error( - 'Recognition failed with status: ${response.statusCode}, body: ${response.body}'); - throw Exception( - 'Recognition failed with status: ${response.statusCode}'); + Logger.error('识别失败,状态码:${response.statusCode},响应体:${response.body}'); + throw Exception('识别失败,状态码:${response.statusCode}'); } final respDic = jsonDecode(response.body); - Logger.debug('Recognition response: $respDic'); + Logger.debug('识别接口响应:$respDic'); // 检查响应中是否包含资源相关信息 if (respDic.containsKey('resource_id') || respDic.containsKey('model')) { + Logger.debug('资源校验 - 使用的资源ID:volc.bigasr.auc_turbo'); Logger.debug( - 'Resource verification - Resource ID used: volc.bigasr.auc_turbo'); - Logger.debug( - 'Response resource info: ${respDic['resource_id'] ?? respDic['model'] ?? 'Not specified in response'}'); + '响应中的资源信息:${respDic['resource_id'] ?? respDic['model'] ?? '响应未包含'}'); } return respDic; } catch (e) { - Logger.error('Recognition error: ${e.toString()}'); + Logger.error('识别请求异常:${e.toString()}'); rethrow; } } @@ -315,19 +297,18 @@ class MeetingTaskService2 extends GetxService { double parsed = double.parse(timeValue); return parsed.round(); } catch (e) { - Logger.error('Failed to parse time value: $timeValue'); + Logger.error('时间值解析失败:$timeValue'); return 0; } } else { - Logger.error( - 'Unknown time value type: ${timeValue.runtimeType}, value: $timeValue'); + Logger.error('未知的时间值类型:${timeValue.runtimeType},值:$timeValue'); return 0; } } Future _fileRecognize(Map item) async { try { - Logger.debug('Starting recognition for audio: ${item['audiourl']}'); + Logger.debug('开始识别音频:${item['audiourl']}'); // 识别语言选取:若设置了目标输出语言且与录音语言一致,优先让ASR自动识别,避免错配 final String tplLang = (((item['template'] ?? {})['language']) ?? '').toString(); @@ -344,8 +325,8 @@ class MeetingTaskService2 extends GetxService { language: asrLanguage, ); - Logger.debug('Full API response structure: ${respDic.toString()}'); - Logger.debug('Response keys: ${respDic.keys.toList()}'); + Logger.debug('API 全量响应结构:${respDic.toString()}'); + Logger.debug('响应包含的键:${respDic.keys.toList()}'); // 检查响应格式并提取utterances List utterances = []; @@ -354,77 +335,67 @@ class MeetingTaskService2 extends GetxService { if (respDic['result'] != null && respDic['result']['utterances'] != null) { utterances = respDic['result']['utterances']; - Logger.debug( - 'Found utterances in result.utterances: ${utterances.length} items'); + Logger.debug('在 result.utterances 中找到 ${utterances.length} 条语句'); } else if (respDic['resp'] != null && respDic['resp']['utterances'] != null) { utterances = respDic['resp']['utterances']; - Logger.debug( - 'Found utterances in resp.utterances: ${utterances.length} items'); + Logger.debug('在 resp.utterances 中找到 ${utterances.length} 条语句'); } else if (respDic['utterances'] != null) { utterances = respDic['utterances']; - Logger.debug( - 'Found utterances in root.utterances: ${utterances.length} items'); + Logger.debug('在根级 utterances 中找到 ${utterances.length} 条语句'); } else if (respDic['data'] != null && respDic['data']['utterances'] != null) { utterances = respDic['data']['utterances']; - Logger.debug( - 'Found utterances in data.utterances: ${utterances.length} items'); + Logger.debug('在 data.utterances 中找到 ${utterances.length} 条语句'); } else if (respDic['response'] != null && respDic['response']['utterances'] != null) { utterances = respDic['response']['utterances']; - Logger.debug( - 'Found utterances in response.utterances: ${utterances.length} items'); + Logger.debug('在 response.utterances 中找到 ${utterances.length} 条语句'); } else { // 检查是否有错误信息 if (respDic['error'] != null) { - Logger.error('API returned error: ${respDic['error']}'); + Logger.error('接口返回错误:${respDic['error']}'); } else if (respDic['message'] != null) { - Logger.error('API returned message: ${respDic['message']}'); + Logger.error('接口返回消息:${respDic['message']}'); } else { - Logger.error( - 'Unknown response format. Available keys: ${respDic.keys.toList()}'); + Logger.error('未知的响应格式。可用键:${respDic.keys.toList()}'); // 尝试查找任何包含文本的字段 respDic.forEach((key, value) { - Logger.debug( - 'Key: $key, Value type: ${value.runtimeType}, Value: $value'); + Logger.debug('键:$key,值类型:${value.runtimeType},值:$value'); }); } } if (utterances.isNotEmpty) { - Logger.debug('Processing ${utterances.length} utterances'); + Logger.debug('开始处理 ${utterances.length} 条语句'); await _addTextList(item['id'], utterances, item['template']); // 从任务列表中移除已完成的任务 _taskList.removeWhere((element) => element['id'] == item['id']); _storage.write("meeting_task_list_v2", _taskList); - Logger.debug( - 'Recognition completed successfully for item ${item['id']}'); + Logger.debug('音频识别成功,会议ID:${item['id']}'); // 更新UI状态 - 如果是当前会议,触发UI更新 if (item['id'] == meetingId) { tasktype.value = 2; // 转写完成,进入分析阶段 } } else { - Logger.error( - "No utterances found in response. Full response: $respDic"); - print( - '🔥🔥🔥 NO UTTERANCES FOUND - STARTING SILENT AUDIO CHECK 🔥🔥🔥'); - print('=== DEBUGGING SILENT AUDIO DETECTION ==='); - print('Response keys: ${respDic.keys.toList()}'); - print('Response code: ${respDic['code']}'); - print('Response message: ${respDic['message']}'); + Logger.error('响应中未发现语句。完整响应:$respDic'); + Logger.debug('未检测到语句,开始执行无声音频检查'); + Logger.debug('无声音频检测调试信息'); + Logger.debug('响应键列表:${respDic.keys.toList()}'); + Logger.debug('响应代码:${respDic['code']}'); + Logger.debug('响应消息:${respDic['message']}'); // 检查是否为无声音频 bool isSilentAudio = _checkIfSilentAudio(respDic); - print('Silent audio detection result: $isSilentAudio'); + Logger.debug('无声检测结果:$isSilentAudio'); if (isSilentAudio) { - print('=== SILENT AUDIO DETECTED - SETTING TASKTYPE TO -1 ==='); + Logger.debug('检测到无声音频,设置任务类型为 -1'); // 设置无声音频状态 (使用特殊值 -1) await SqfliteApi.editMeeting(item['id'], {'tasktype': -1}); if (item['id'] == meetingId) { - print('Setting tasktype.value to -1 for current meeting'); + Logger.debug('当前会议设置 tasktype.value 为 -1'); tasktype.value = -1; // 无声音频 } @@ -438,32 +409,10 @@ class MeetingTaskService2 extends GetxService { taskFinishList.refresh(); } _storage.write("meeting_task_finish_list_v2", taskFinishList); - try { - final s1 = Get.find(); - Map taskFinishData1 = s1.taskFinishList.firstWhere( - (element) => element['id'] == item['id'], - orElse: () => {}, - ); - if (taskFinishData1.isNotEmpty) { - taskFinishData1['tasktype'] = 0; - s1.taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list", s1.taskFinishList); - } catch (_) {} - try { - final s1 = Get.find(); - Map taskFinishData1 = s1.taskFinishList.firstWhere( - (element) => element['id'] == item['id'], - orElse: () => {}, - ); - if (taskFinishData1.isNotEmpty) { - taskFinishData1['tasktype'] = -1; - s1.taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list", s1.taskFinishList); - } catch (_) {} + // 由聚合器统一对外呈现状态,不直接写入标准版列表 + // 由聚合器统一对外呈现状态,不直接写入标准版列表 } else { - Logger.error('Recognition failed for unknown reason'); + Logger.error('识别失败,原因未知'); // 识别失败,更新数据库状态为失败 await SqfliteApi.editMeeting(item['id'], {'tasktype': 0}); if (item['id'] == meetingId) { @@ -487,14 +436,14 @@ class MeetingTaskService2 extends GetxService { _storage.write("meeting_task_list_v2", _taskList); } } catch (e) { - Logger.error('Error occurred in _fileRecognize: ${e.toString()}'); - Logger.error('Stack trace: ${StackTrace.current}'); + Logger.error('文件识别流程发生异常:${e.toString()}'); + Logger.error('堆栈信息:${StackTrace.current}'); // 更新数据库状态为失败 try { await SqfliteApi.editMeeting(item['id'], {'tasktype': 0}); } catch (dbError) { - Logger.error('Failed to update meeting status in database: $dbError'); + Logger.error('数据库更新会议状态失败:$dbError'); } // 出错时也要从任务列表中移除,避免无限重试 @@ -510,7 +459,7 @@ class MeetingTaskService2 extends GetxService { /// 检查是否为无声音频 bool _checkIfSilentAudio(Map response) { - print('Checking if silent audio, response: ${response.toString()}'); + Logger.debug('检查是否为无声音频,响应:${response.toString()}'); // 检查API响应中的错误信息或状态码 // 根据实际API响应格式调整检测逻辑 @@ -518,7 +467,7 @@ class MeetingTaskService2 extends GetxService { // 检查是否有错误码表示无声音频 if (response['code'] != null) { int code = response['code']; - print('Found response code: $code'); + Logger.debug('检测到响应代码:$code'); // 常见的无声音频错误码 if (code == 40001 || code == 40002 || @@ -526,7 +475,7 @@ class MeetingTaskService2 extends GetxService { code == 50001 || code == 50002 || code == 50003) { - print('Detected silent audio by error code: $code'); + Logger.debug('根据错误码判断为无声音频:$code'); return true; } } @@ -534,7 +483,7 @@ class MeetingTaskService2 extends GetxService { // 检查错误消息 if (response['message'] != null) { String message = response['message'].toString().toLowerCase(); - print('Found response message: $message'); + Logger.debug('检测到响应消息:$message'); if (message.contains('silent') || message.contains('no audio') || message.contains('无声') || @@ -543,7 +492,7 @@ class MeetingTaskService2 extends GetxService { message.contains('no speech') || message.contains('audio too short') || message.contains('insufficient audio')) { - print('Detected silent audio by message: $message'); + Logger.debug('根据消息判断为无声音频:$message'); return true; } } @@ -551,7 +500,7 @@ class MeetingTaskService2 extends GetxService { // 检查result中的错误信息 if (response['result'] != null && response['result']['error'] != null) { String error = response['result']['error'].toString().toLowerCase(); - print('Found result error: $error'); + Logger.debug('检测到 result.error:$error'); if (error.contains('silent') || error.contains('no audio') || error.contains('无声') || @@ -559,7 +508,7 @@ class MeetingTaskService2 extends GetxService { error.contains('empty audio') || error.contains('no speech') || error.contains('audio too short')) { - print('Detected silent audio by result error: $error'); + Logger.debug('根据 result.error 判断为无声音频:$error'); return true; } } @@ -567,10 +516,10 @@ class MeetingTaskService2 extends GetxService { // 检查是否有音频时长信息且时长为0或极短 if (response['result'] != null && response['result']['duration'] != null) { double duration = (response['result']['duration'] as num).toDouble(); - print('Found audio duration: $duration seconds'); + Logger.debug('检测到音频时长:$duration 秒'); if (duration <= 0.5) { // 音频时长小于0.5秒认为是无声 - print('Detected silent audio by short duration: $duration'); + Logger.debug('根据时长过短判断为无声音频:$duration'); return true; } } @@ -618,10 +567,10 @@ class MeetingTaskService2 extends GetxService { 1000; // 转换为秒 } - print('Found empty text content, audio duration: $audioDuration seconds'); + Logger.debug('文本内容为空,音频时长:$audioDuration 秒'); if (audioDuration > 1.0) { // 音频时长大于1秒但没有文本内容,认为是无声音频 - print('Detected silent audio: empty text with sufficient duration'); + Logger.debug('判断为无声音频:文本为空且时长充足'); return true; } } @@ -637,25 +586,25 @@ class MeetingTaskService2 extends GetxService { // 如果有成功状态码但没有utterances,很可能是无声音频 if (hasSuccessCode) { - print('Detected silent audio: success code but no utterances'); + Logger.debug('判断为无声音频:成功状态码但无语句'); return true; } } - print('Not detected as silent audio'); + Logger.debug('未判定为无声音频'); return false; } Future _addTextList(int id, List utterances, [Map? templateData]) async { - Logger.debug('Processing ${utterances.length} utterances for meeting $id'); + Logger.debug('处理会议 $id 的 ${utterances.length} 条语句'); String text = ''; String personnel = ''; List> list = []; for (int i = 0; i < utterances.length; i++) { var element = utterances[i]; - Logger.debug('Processing utterance $i: ${element.toString()}'); + Logger.debug('处理第 $i 条语句:${element.toString()}'); String speaker = ''; String content = ''; @@ -681,7 +630,7 @@ class MeetingTaskService2 extends GetxService { } else if (element['transcript'] != null) { content = element['transcript'].toString(); } else { - Logger.error('No text content found in utterance: $element'); + Logger.error('语句中未找到文本内容:$element'); continue; } @@ -718,11 +667,10 @@ class MeetingTaskService2 extends GetxService { }); text += '$content\n'; - Logger.debug( - 'Added utterance: speaker=$speaker, content=$content, start=$startTime, end=$endTime'); + Logger.debug('已添加语句:说话人=$speaker,内容=$content,开始=$startTime,结束=$endTime'); } - Logger.debug('Total processed utterances: ${list.length}'); + Logger.debug('处理完成的语句总数:${list.length}'); var result = await SqfliteApi.insertMeetingSpeaker(list); if (result != null) { // 初始用于总结/概览的文本,先用原始拼接文本作为默认值 @@ -748,7 +696,7 @@ class MeetingTaskService2 extends GetxService { // 保持在“正在转写”状态,先进行翻译为目标语言 await _translateTextList(id, targetLanguage); } catch (e) { - Logger.error('Translate transcript failed: ${e.toString()}'); + Logger.error('转写文本翻译失败:${e.toString()}'); } // 翻译完成后再写入 personnel、设置已完成并刷新 UI await SqfliteApi.editMeeting( @@ -763,38 +711,8 @@ class MeetingTaskService2 extends GetxService { textList = newSpeakerList; tasktype.value = 2; } - try { - final s1 = Get.find(); - if (id == s1.meetingId) { - s1.textList = newSpeakerList; - s1.tasktype.value = 2; - } - Map taskFinishData1 = s1.taskFinishList.firstWhere( - (element) => element['id'] == id, - orElse: () => {}, - ); - if (taskFinishData1.isNotEmpty) { - taskFinishData1['tasktype'] = 2; - s1.taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list", s1.taskFinishList); - } catch (_) {} - try { - final s1 = Get.find(); - if (id == s1.meetingId) { - s1.textList = newSpeakerList; - s1.tasktype.value = 2; - } - Map taskFinishData1 = s1.taskFinishList.firstWhere( - (element) => element['id'] == id, - orElse: () => {}, - ); - if (taskFinishData1.isNotEmpty) { - taskFinishData1['tasktype'] = 2; - s1.taskFinishList.refresh(); - } - _storage.write("meeting_task_finish_list", s1.taskFinishList); - } catch (_) {} + // 由聚合器统一对外呈现状态,不直接写入标准版列表 + // 由聚合器统一对外呈现状态,不直接写入标准版列表 // 用最新(已翻译)的文本重建总结/概览输入 summarySourceText = ''; for (var element in newSpeakerList) { @@ -967,7 +885,7 @@ class MeetingTaskService2 extends GetxService { final obj = jsonDecode(translated); arr = (obj is Map && obj['data'] is List) ? (obj['data'] as List) : []; } catch (e) { - Logger.error('Parse translation JSON failed: ${e.toString()}'); + Logger.error('翻译结果 JSON 解析失败:${e.toString()}'); return; } } diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 96996c26c..b5111338a 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -18,6 +18,7 @@ import '../../../data/services/meeting/meeting_task_service.dart'; import '../../../data/services/meeting/meeting_task_service2.dart'; import '../../../data/services/meeting/meeting_upload_service.dart'; import '../../../data/services/meeting/summary_task_service.dart'; +import '../../../data/services/meeting/meeting_task_aggregator.dart'; import '../model/meeting_details_model.dart'; import '../model/meeting_model.dart'; @@ -100,6 +101,7 @@ class MeetingDetailsController extends GetxController final bool isFast = svc == 'fast'; _meetingTaskService.meetingId = id; _meetingTaskService2.meetingId = id; + Get.find().setMeeting(id); _getDataDetails(); _loadJS(); _tasktypeListener = ever(_meetingTaskService.tasktype, (value) { @@ -141,6 +143,7 @@ class MeetingDetailsController extends GetxController tabController.dispose(); playerController.dispose(); lottieController.dispose(); + Get.find().clearMeeting(); super.onClose(); } diff --git a/lib/modules/meeting/controllers/meeting_home_controller.dart b/lib/modules/meeting/controllers/meeting_home_controller.dart index 9ac1bb6e7..fcea611a4 100644 --- a/lib/modules/meeting/controllers/meeting_home_controller.dart +++ b/lib/modules/meeting/controllers/meeting_home_controller.dart @@ -20,6 +20,7 @@ import 'package:share_plus/share_plus.dart'; import '../../../data/services/meeting/meeting_task_service.dart'; import '../../../data/services/meeting/meeting_task_service2.dart'; +import '../../../data/services/meeting/meeting_task_aggregator.dart'; import '../../../data/services/meeting/meeting_upload_service.dart'; import '../../../data/services/meeting/summary_task_service.dart'; import '../../../routes/app_routes.dart'; @@ -31,6 +32,7 @@ class MeetingHomeController extends GetxController { final meetingUploadService = Get.find(); final meetingTaskService = Get.find(); final meetingTaskService2 = Get.find(); + final taskAggregator = Get.find(); final summaryTaskService = Get.find(); final AudioPlayer _audioPlayer = AudioPlayer(); diff --git a/lib/modules/meeting/views/meeting_home_view.dart b/lib/modules/meeting/views/meeting_home_view.dart index 81cd3b22b..f9a9dd988 100644 --- a/lib/modules/meeting/views/meeting_home_view.dart +++ b/lib/modules/meeting/views/meeting_home_view.dart @@ -320,8 +320,8 @@ class _MeetingHomeViewState extends State : const SizedBox(); }), Obx(() { - Map taskData = - _controller.meetingTaskService.taskFinishList.firstWhere( + Map taskData = _controller.taskAggregator.taskFinishList + .firstWhere( (element) => element['id'] == data.id, orElse: () => {}, ); From 450e35b8284d058141dbd08621babd9a49c7cac0 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 26 Nov 2025 16:53:39 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(meeting):=20=E4=BF=AE=E5=A4=8D=E4=BC=9A?= =?UTF-8?q?=E8=AE=AE=E4=BB=BB=E5=8A=A1=E7=B1=BB=E5=9E=8B=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在编辑会议任务类型时添加加载状态指示,并修复任务类型更新逻辑以根据服务类型区分更新不同的任务服务。同时优化了会议详情的刷新机制。 --- .../controllers/meeting_details_controller.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index b5111338a..927bb9f96 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -328,8 +328,20 @@ class MeetingDetailsController extends GetxController text += '${element['content']}\n'; } _everType = 3; + EasyLoading.show(); await SqfliteApi.editMeeting(id, {'tasktype': 2}); - _meetingTaskService.tasktype.value = 2; + meetingDetails.value.tasktype = 2; + meetingDetails.refresh(); + final storage = GetStorage(); + final String svc = + (storage.read("meeting_service_type_$id") ?? '').toString(); + final bool isFast = svc == 'fast'; + if (isFast) { + _meetingTaskService2.tasktype.value = 2; + } else { + _meetingTaskService.tasktype.value = 2; + } _summaryTaskService.addTaskList(id, text, prompt, outline); + EasyLoading.dismiss(); } }