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); } }