Browse Source

feat(meeting_task): 添加大模型语音识别标准版支持并优化任务处理流程

- 新增对大模型语音识别API的支持,包括新的服务地址和请求头配置
- 优化任务处理流程,添加详细的日志记录和调试信息
- 完善代码注释和文档说明,提高代码可读性
- 修复任务状态轮询逻辑,确保正确处理转写结果
weicu
lxm 12 months ago
parent
commit
6d2d31b963
  1. 162
      lib/data/services/meeting/meeting_task_service.dart
  2. 1
      lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart
  3. 26
      lib/modules/meeting/views/meeting_details_view.dart
  4. 1
      lib/modules/meeting/views/meeting_home_view.dart
  5. 1
      lib/modules/meeting/views/meeting_view.dart

162
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<MeetingUploadService>();
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<String, String> _headers = {};
Map<String, String> _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<void> 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<void> 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<String> _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<Map<String, dynamic>> _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<void> _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<void> _addTextList(
int id, List utterances, String prompt, String outline) async {
String text = '';
String personnel = '';
List<Map<String, Object?>> 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,

1
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<MeetingHomeController> {
const NavigationBarBottomSheet({super.key});

26
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<MeetingDetailsController> {
const MeetingDetailsView({super.key});
@ -40,10 +41,9 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
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<MeetingDetailsController> {
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<MeetingDetailsController> {
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<MeetingDetailsController> {
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), // 对应中文:思维导图
],
),
),

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

1
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<MeetingController> {
const MeetingView({super.key});

Loading…
Cancel
Save