import 'dart:convert'; import 'dart:io'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; import '../../../core/utils/logger.dart'; import '../../../core/utils/synchrodata.dart'; import '../db/sqflite_api.dart'; import '../deapsound_ai_service.dart'; import 'meeting_upload_service.dart'; import '../usage_stats_service.dart'; import '../../models/meeting_usage_stats.dart'; ///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。 class MeetingTaskService extends GetxService { final GetStorage _storage = GetStorage(); MeetingUploadService get _uploadService => Get.find(); List _taskList = []; //待处理的会议任务列表 RxList taskFinishList = [].obs; //已处理的会议任务列表 bool _isTask = false; //是否有正在处理的任务 String _userId = ''; //字节跳动(火山引擎)语音服务配置 final String _appid = '2316081933'; final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; final String _cluster = 'volc_auc_video_flash'; final String _cluster1 = 'volc.bigasr.auc'; final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; // final String _serviceUrl1 = 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; //大模型录音文件识别api地址 Map _headers = {}; Map _headers1 = {}; int meetingId = 0; RxInt tasktype = 1.obs; //任务类型,1-转写中,2-总结中,3-完成 List textList = []; //转写结果列表 String summary = ''; //AI总结内容 String overview = ''; //AI概览内容 @override void onInit() { super.onInit(); _headers = { 'Authorization': 'Bearer; $_token', 'Content-Type': 'application/json', }; _headers1 = { 'X-Api-App-Key': _appid, 'X-Api-Access-Key': _token, 'X-Api-Resource-Id': _cluster1, 'X-Api-Request-Id': '', 'X-Api-Sequence': '-1', }; _getAgent(); _getTaskList(); } void _getAgent() async { Map userInfo = _storage.read("user_info"); _userId = userInfo['user']['uid']; } void _getTaskList() { _taskList = _storage.read("meeting_task_list") ?? []; taskFinishList.value = _storage.read("meeting_task_finish_list") ?? []; _executeTask(); } ///添加会议任务 ///[id] 会议ID ///[audiourl] 音频URL ///[taskid] 任务ID ///[isSpeaker] 是否为发言人 ///[language] 语言 ///[prompt] 提示词 ///[outline] 大纲 Future addTask(int id, String audiourl, String taskid, bool isSpeaker, String language, 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['isSpeaker'] = isSpeaker; uploadData['language'] = language; uploadData['prompt'] = prompt; uploadData['outline'] = outline; } else { await addTaskList(id, uploadData['audiourl'], taskid, isSpeaker, language, prompt, outline); } } } else { await addTaskList( id, audiourl, taskid, isSpeaker, language, prompt, outline); } } ///添加会议任务列表 ///[id] 会议ID ///[audiourl] 音频URL ///[taskId] 任务ID ///[isSpeaker] 是否为发言人 ///[language] 语言 ///[prompt] 提示词 ///[outline] 大纲 Future addTaskList(int id, String audiourl, String taskId, bool isSpeaker, String language, String prompt, String outline) async { if (taskId.isEmpty) { taskId = await _submitTask(audiourl, isSpeaker, language); } await SqfliteApi.editMeeting(id, {'tasktype': 1, 'taskid': taskId}); try { final detail = await SqfliteApi.getMeetingDetails(id); final seconds = (detail['seconds'] ?? 0) as int; final filepath = (detail['filepath'] ?? '') as String; int fileSizeBytes = 0; if (filepath.isNotEmpty) { try { fileSizeBytes = await File(filepath).length(); } catch (_) {} } final record = MeetingAsrRecord( id: taskId, meetingId: id, taskId: taskId, audioSeconds: seconds, fileSizeBytes: fileSizeBytes, language: language, submitTimestamp: DateTime.now().millisecondsSinceEpoch, status: asrStatusProcessing, transcriptBytes: 0, completeTimestamp: 0, ); Get.find().addMeetingAsrRecord(record); } catch (_) {} _taskList.add({ 'id': id, 'taskid': taskId, 'tasktype': 1, 'prompt': prompt, 'outline': outline, }); taskFinishList.add({ 'id': id, 'tasktype': 1, }); _storage.write("meeting_task_list", _taskList); _storage.write("meeting_task_finish_list", taskFinishList); _executeTask(); } ///执行会议任务 void _executeTask() async { if (_isTask) return; _isTask = true; while (_taskList.isNotEmpty) { await Future.delayed(const Duration(seconds: 2)); Logger.info("lxm--轮询---会议任务列表$_taskList"); for (var task in List.from(_taskList)) { await _fileRecognize(task); } } _isTask = false; } 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", taskFinishList); } } if (isDeleteTask) { _taskList.removeWhere((element) => element['id'] == id); _storage.write("meeting_task_list", _taskList); } } // Future _submitTask( String audiourl, bool isSpeaker, String language) async { String format = _getFileType(audiourl); final taskId = DateTime.now().millisecondsSinceEpoch.toString(); _headers1['X-Api-Request-Id'] = taskId; Logger.info("lxm--大模型asr请求头$_headers1"); final requestBody1 = jsonEncode({ "user": { "uid": _userId, }, "audio": { "url": audiourl, "language": language, "format": format, }, "request": { "model_name": "bigmodel", // 模型名称,目前只有bigmodel "model_version": "400", // 模型版本,400模型性能略有提升,且ITN有较大优化 //"enable_itn": true, // 启用文本规范化,默认为true "enable_punc": true, // 启用标点,默认为false //"enable_ddc": false, // 启用语义顺滑,默认为false "enable_speaker_info": isSpeaker, // 启用说话人聚类分离,10人以内效果较好 //"enable_channel_split": false, // 启用双声道识别,默认为false //"show_utterances": true, // 输出语音停顿、分句、分词信息 //"show_speech_rate": false, // 分句信息携带语速 //"show_volume": false, // 分句信息携带音量 //"enable_lid": false, // 启用语种识别 } }); Logger.info("lxm--大模型asr请求体$requestBody1"); final requestBody = jsonEncode({ "app": { "appid": _appid, "token": _token, "cluster": _cluster, }, "user": { "uid": _userId, }, "audio": { "format": format, "url": audiourl, }, "additions": { "language": language, "with_speaker_info": isSpeaker ? "True" : "False", } }); try { final response = await http.post( Uri.parse('$_serviceUrl1/submit'), headers: _headers1, body: requestBody1, ); Logger.info("lxm--大模型asr响应体${response.body}"); Logger.info("lxm--大模型asr上传响应头${response.headers['x-tt-logid']}"); Logger.info( "lxm--大模型asr上传服务端返回的状态码${response.headers['x-api-status-code']}"); Logger.info("lxm--大模型asr上传服务端返回的消息${response.headers['x-api-message']}"); if (response.statusCode != 200) { throw Exception('Submit failed with status: ${response.statusCode}'); } //final respDic = jsonDecode(response.body); //final taskId = respDic['resp']['id'] as String; //Logger.debug('taskId: $taskId'); return taskId; } catch (e) { Logger.error('Submit error: ${e.toString()}'); rethrow; } } String _getFileType(String path) { List array = path.split('.'); return array[array.length - 1]; } ///轮询语音转文字任务状态 ///[taskId] 任务ID Future> _queryTask(String taskId) async { final queryBody = jsonEncode({ "appid": _appid, "token": _token, "id": taskId, "cluster": _cluster, }); try { _headers1['X-Api-Request-Id'] = taskId; final response = await http.post( Uri.parse('$_serviceUrl1/query'), headers: _headers1, body: jsonEncode({}), ); // Logger.info("lxm---大模型asr响应体${response.body}"); // Logger.info("lxm---大模型asr响应头${response.headers}"); // Logger.info("lxm---大模型asr响应状态码${response.statusCode}"); // Logger.info("lxm---大模型asr响应体logid${response.headers['x-tt-logid']}"); // Logger.info("lxm---大模型asr服务端返回的状态码${response.headers['x-api-status-code']}"); // Logger.info("lxm---大模型asr服务端返回的消息${response.headers['x-api-message']}"); if (response.statusCode != 200) { throw Exception('Query failed with status: ${response.statusCode}'); } final respDic = jsonDecode(response.body); return respDic; } catch (e) { Logger.error('Query error: ${e.toString()}'); rethrow; } } ///轮询语音转文字任务状态,若任务完成则处理转写结果 ///[item] 包含任务ID、会议ID、AI总结提示词、AI概览提示词的Map Future _fileRecognize(Map item) async { try { var respDic = await _queryTask(item['taskid']); //var code = respDic['resp']['code'] as int; Logger.info("lxm---大模型asr响应体结果$respDic['result']['text']"); if (respDic['result']['text'] != null) { await _addTextList( item['id'], //会议ID respDic['result']['utterances'], //语音转文字结果 item['prompt'], //AI总结提示词 item['outline'], //AI概览提示词 ); return; } else { Logger.debug("FileRecognize failed"); return; } } catch (e) { Logger.error('Error occurred: ${e.toString()}'); } } ///处理语音识别完成后的转写结果,包括数据库存储、状态更新和触发AI分析 ///[id] 会议ID ///[utterances] 语音转文字结果 ///[prompt] AI总结提示词 ///[outline] AI概览提示词 Future _addTextList( int id, List utterances, String prompt, String outline) async { String text = ''; String personnel = ''; List> list = []; // 处理转写结果,将每个句子的开始时间、结束时间、发言人和内容存储到列表中 for (var element in utterances) { String speaker = ''; if (element['additions'].isNotEmpty) { speaker = 'Speaker ${element['additions']['speaker']}'; text += '[$speaker]: '; if (!personnel.contains(speaker)) { personnel += '[$speaker] '; } } list.add({ 'meetingid': id, 'content': element['text'], 'starttime': element['start_time'], 'endtime': element['end_time'], 'speaker': speaker, }); text += '${element['text']}\n'; } // 存储处理后的转写结果到数据库 var result = await SqfliteApi.insertMeetingSpeaker(list); if (result != null) { // 更新会议状态为“总结中” 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; } _taskList.removeWhere((element) => element['id'] == id); Map taskFinishData = taskFinishList.firstWhere( (element) => element['id'] == id, orElse: () => {}, ); if (taskFinishData.isNotEmpty) { taskFinishData['tasktype'] = 2; taskFinishList.refresh(); } _storage.write("meeting_task_list", _taskList); _storage.write("meeting_task_finish_list", taskFinishList); } try { final details = await SqfliteApi.getMeetingDetails(id); final tid = (details['taskid'] ?? '') as String; final transcriptBytes = utf8.encode(text).length; Get.find().completeMeetingAsrRecord( id: tid, transcriptBytes: transcriptBytes, completeTimestamp: DateTime.now().millisecondsSinceEpoch, ); } catch (_) {} // 触发AI分析,分别请求总结和概览 aiServiceMessage(id, text, prompt, outline); } ///触发AI分析,分别请求总结和概览 ///[id] 会议ID ///[text] 语音转文字结果 ///[prompt] AI总结提示词 ///[outline] AI概览提示词 void aiServiceMessage(int id, String text, String prompt, String outline) { int awaitQuantity = 0; String summaryText = ''; String overviewText = ''; DeapsoundAIService().sendMessage( messages: [ { 'role': 'system', 'content': prompt, }, {'role': 'user', 'content': text}, ], ).then((value) { awaitQuantity++; summaryText = value; Logger.info("lxm---大模型asr总结结果$summaryText"); if (awaitQuantity >= 2) { _aiServiceValue(id, summaryText, overviewText); } }); DeapsoundAIService().sendMessage( messages: [ { 'role': 'system', 'content': outline, }, {'role': 'user', 'content': text}, ], ).then((value) { awaitQuantity++; overviewText = value; Logger.info("lxm---大模型asr概览结果$overviewText"); if (awaitQuantity >= 2) { _aiServiceValue(id, summaryText, overviewText); } }); } ///处理AI分析完成后的结果,包括数据库存储和状态更新 ///[id] 会议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; } Map taskFinishData = taskFinishList.firstWhere( (element) => element['id'] == id, orElse: () => {}, ); if (taskFinishData.isNotEmpty) { taskFinishData['tasktype'] = 3; taskFinishList.refresh(); } _storage.write("meeting_task_finish_list", taskFinishList); } }