diff --git a/lib/data/services/meeting/meeting_task_service.dart b/lib/data/services/meeting/meeting_task_service.dart index 50b7a5def..f062c2acc 100644 --- a/lib/data/services/meeting/meeting_task_service.dart +++ b/lib/data/services/meeting/meeting_task_service.dart @@ -10,29 +10,35 @@ import '../db/sqflite_api.dart'; import '../deapsound_ai_service.dart'; import 'meeting_upload_service.dart'; +///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。 class MeetingTaskService extends GetxService { final GetStorage _storage = GetStorage(); MeetingUploadService get _uploadService => Get.find(); - List _taskList = []; - RxList taskFinishList = [].obs; + List _taskList = []; //待处理的会议任务列表 + RxList taskFinishList = [].obs; //已处理的会议任务列表 - bool _isTask = false; + bool _isTask = false; //是否有正在处理的任务 String _userId = ''; +//字节跳动(火山引擎)语音服务配置 final String _appid = '2316081933'; final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; - final String _cluster = 'volc_auc_tele_flash'; - final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; + 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; - List textList = []; - String summary = ''; - String overview = ''; + RxInt tasktype = 1.obs; //任务类型,1-转写中,2-总结中,3-完成 + List textList = []; //转写结果列表 + String summary = ''; //AI总结内容 + String overview = ''; //AI概览内容 @override void onInit() { @@ -41,6 +47,14 @@ class MeetingTaskService extends GetxService { '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': DateTime.now().millisecondsSinceEpoch.toString(), + // 'X-Api-Sequence': '-1', + // }; + _getAgent(); _getTaskList(); } @@ -56,6 +70,14 @@ class MeetingTaskService extends GetxService { _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) { @@ -81,6 +103,14 @@ class MeetingTaskService extends GetxService { } } + ///添加会议任务列表 + ///[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) { @@ -103,14 +133,15 @@ class MeetingTaskService extends GetxService { _executeTask(); } + ///执行会议任务 void _executeTask() async { if (_isTask) return; _isTask = true; while (_taskList.isNotEmpty) { await Future.delayed(const Duration(seconds: 2)); - - for (var element in List.from(_taskList)) { - await _fileRecognize(element); + Logger.info("lxm--轮询---会议任务列表$_taskList"); + for (var task in List.from(_taskList)) { + await _fileRecognize(task); } } _isTask = false; @@ -133,9 +164,46 @@ class MeetingTaskService extends GetxService { } } +// Future _submitTask( String audiourl, bool isSpeaker, String language) async { String format = _getFileType(audiourl); + final taskId = DateTime.now().millisecondsSinceEpoch.toString(); + + _headers1 = { + 'X-Api-App-Key': _appid, + 'X-Api-Access-Key': _token, + 'X-Api-Resource-Id': _cluster1, + 'X-Api-Request-Id': taskId, + 'X-Api-Sequence': '-1', + }; + print("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, // 启用语种识别 + } + }); + + print("lxm--大模型asr请求体$requestBody1"); final requestBody = jsonEncode({ "app": { @@ -158,18 +226,23 @@ class MeetingTaskService extends GetxService { try { final response = await http.post( - Uri.parse('$_serviceUrl/submit'), - headers: _headers, - body: requestBody, + Uri.parse('$_serviceUrl1/submit'), + headers: _headers1, + body: requestBody1, ); + print("lxm--大模型asr响应体${response.body}"); + print("lxm--大模型asr上传响应头${response.headers['x-tt-logid']}"); + print("lxm--大模型asr上传服务端返回的状态码${response.headers['x-api-status-code']}"); + print("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'); + //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()}'); @@ -182,6 +255,8 @@ class MeetingTaskService extends GetxService { return array[array.length - 1]; } + ///轮询语音转文字任务状态 + ///[taskId] 任务ID Future> _queryTask(String taskId) async { final queryBody = jsonEncode({ "appid": _appid, @@ -192,11 +267,18 @@ class MeetingTaskService extends GetxService { try { final response = await http.post( - Uri.parse('$_serviceUrl/query'), - headers: _headers, - body: queryBody, + Uri.parse('$_serviceUrl1/query'), + headers: _headers1, + body: jsonEncode({}), ); + // print("lxm---大模型asr响应体${response.body}"); + // print("lxm---大模型asr响应头${response.headers}"); + // print("lxm---大模型asr响应状态码${response.statusCode}"); + // print("lxm---大模型asr响应体logid${response.headers['x-tt-logid']}"); + // print("lxm---大模型asr服务端返回的状态码${response.headers['x-api-status-code']}"); + // print("lxm---大模型asr服务端返回的消息${response.headers['x-api-message']}"); + if (response.statusCode != 200) { throw Exception('Query failed with status: ${response.statusCode}'); } @@ -209,20 +291,24 @@ class MeetingTaskService extends GetxService { } } + ///轮询语音转文字任务状态,若任务完成则处理转写结果 + ///[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; + //var code = respDic['resp']['code'] as int; + + print("lxm---大模型asr响应体结果$respDic['result']['text']"); - if (code == 1000) { + if (respDic['result']['text'] != null) { await _addTextList( - item['id'], - respDic['resp']['utterances'], - item['prompt'], - item['outline'], + item['id'], //会议ID + respDic['result']['utterances'], //语音转文字结果 + item['prompt'], //AI总结提示词 + item['outline'], //AI概览提示词 ); return; - } else if (code < 2000) { + } else { Logger.debug("FileRecognize failed"); return; } @@ -231,11 +317,18 @@ class MeetingTaskService extends GetxService { } } + ///处理语音识别完成后的转写结果,包括数据库存储、状态更新和触发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) { @@ -254,8 +347,10 @@ class MeetingTaskService extends GetxService { }); text += '${element['text']}\n'; } + // 存储处理后的转写结果到数据库 var result = await SqfliteApi.insertMeetingSpeaker(list); if (result != null) { + // 更新会议状态为“总结中” await SqfliteApi.editMeeting( id, {'tasktype': 2, 'personnel': personnel}, @@ -282,9 +377,16 @@ class MeetingTaskService extends GetxService { _storage.write("meeting_task_list", _taskList); _storage.write("meeting_task_finish_list", taskFinishList); } + + // 触发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 = ''; @@ -321,6 +423,10 @@ class MeetingTaskService extends GetxService { }); } + ///处理AI分析完成后的结果,包括数据库存储和状态更新 + ///[id] 会议ID + ///[summaryText] 总结结果 + ///[overviewText] 概览结果 void _aiServiceValue(int id, String summaryText, String overviewText) { SqfliteApi.editMeeting(id, { 'tasktype': 3, diff --git a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart index 7f594cdc6..6700edd92 100644 --- a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import '../../../../routes/app_routes.dart'; import '../../controllers/meeting_home_controller.dart'; +///会议底部导航栏抽屉,提供会议列表、会议详情、会议设置 class NavigationBarBottomSheet extends GetView { const NavigationBarBottomSheet({super.key}); diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index 15444981a..cd21f85a5 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -17,6 +17,7 @@ import 'bottomSheet/share_bottom_sheet.dart'; import 'tabs/speech_tab.dart'; import 'tabs/summary_tab.dart'; +///会议音频详情页面,提供会议音频播放、会议音频分享、会议音频更多操作 class MeetingDetailsView extends GetView { const MeetingDetailsView({super.key}); @@ -40,10 +41,9 @@ class MeetingDetailsView extends GetView { preferredSize: const Size.fromHeight(kToolbarHeight), child: Obx( () => AppBar( - backgroundColor: - isDarkMode - ? (controller.isTop.value ? Colors.black : Colors.grey[900]) - : (controller.isTop.value ? Colors.grey[50] : Colors.grey[200]), + backgroundColor: isDarkMode + ? (controller.isTop.value ? Colors.black : Colors.grey[900]) + : (controller.isTop.value ? Colors.grey[50] : Colors.grey[200]), surfaceTintColor: Colors.transparent, elevation: 0.5, leading: controller.isSpeakerText.value @@ -202,7 +202,8 @@ class MeetingDetailsView extends GetView { Get.bottomSheet( const ShareBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -234,7 +235,8 @@ class MeetingDetailsView extends GetView { Get.bottomSheet( const MoreBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -301,10 +303,14 @@ class MeetingDetailsView extends GetView { child: Obx( () => Row( children: [ - _iconItem(Icons.summarize, 'overview'.tr, 0), // 对应中文:概览 - _iconItem(Icons.message, 'transcription'.tr, 1), // 对应中文:转写 - _iconItem(Icons.article, 'summary'.tr, 2), // 对应中文:总结 - _iconItem(Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图 + _iconItem( + Icons.summarize, 'overview'.tr, 0), // 对应中文:概览 + _iconItem(Icons.message, 'transcription'.tr, + 1), // 对应中文:转写 + _iconItem( + Icons.article, 'summary'.tr, 2), // 对应中文:总结 + _iconItem( + Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图 ], ), ), diff --git a/lib/modules/meeting/views/meeting_home_view.dart b/lib/modules/meeting/views/meeting_home_view.dart index f1c9ed0f8..81cd3b22b 100644 --- a/lib/modules/meeting/views/meeting_home_view.dart +++ b/lib/modules/meeting/views/meeting_home_view.dart @@ -11,6 +11,7 @@ import '../model/meeting_model.dart'; import '../utils/meeting_ui_utils.dart'; import 'bottomSheet/edit_title_bottom_sheet.dart'; +///会议主页面,提供会议列表、会议详情、会议设置 class MeetingHomeView extends StatefulWidget { const MeetingHomeView({super.key}); diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index a2aa30687..27cbdd32f 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -9,6 +9,7 @@ import '../utils/meeting_ui_utils.dart'; import 'bottomSheet/filtrate_drawer.dart'; import 'bottomSheet/navigation_bar_bottom_sheet.dart'; +///meeting模块主容器页面,提供底部导航栏、浮动按钮、侧板抽屉 class MeetingView extends GetView { const MeetingView({super.key});