Browse Source

Merge branch 'new_dev' of github.com:deepcloud2048/deep_voice into new_dev

weicu
lxm 9 months ago
parent
commit
6b83c843a7
  1. 14
      lib/core/bindings/initial_binding.dart
  2. 131
      lib/core/utils/synchrodata.dart
  3. 7
      lib/data/services/account_service.dart
  4. 29
      lib/data/services/db/sqflite_api.dart
  5. 105
      lib/data/services/meeting/meeting_task_aggregator.dart
  6. 563
      lib/data/services/meeting/meeting_task_service.dart
  7. 971
      lib/data/services/meeting/meeting_task_service2.dart
  8. 62
      lib/data/services/meeting/meeting_upload_service.dart
  9. 187
      lib/data/services/meeting/summary_task_service.dart
  10. 59
      lib/data/services/network/api.dart
  11. 122
      lib/modules/meeting/controllers/meeting_details_controller.dart
  12. 78
      lib/modules/meeting/controllers/meeting_home_controller.dart
  13. 48
      lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart
  14. 2
      lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart
  15. 23
      lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart
  16. 5
      lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart
  17. 9
      lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart
  18. 44
      lib/modules/meeting/views/meeting_details_view.dart
  19. 57
      lib/modules/meeting/views/meeting_home_view.dart
  20. 8
      lib/modules/splash/controllers/splash_controller.dart

14
lib/core/bindings/initial_binding.dart

@ -1,4 +1,4 @@
import '../../data/services/meeting/summary_task_service.dart'; import '../../data/services/meeting/meeting_task_service.dart';
import '/data/services/pay_service.dart'; import '/data/services/pay_service.dart';
import '/data/services/qq_music.dart'; import '/data/services/qq_music.dart';
@ -10,9 +10,6 @@ import '../../../data/services/navigation_manager.dart';
import '../../../data/services/ast_service.dart'; import '../../../data/services/ast_service.dart';
import '../../../data/services/speech_impl/azure_ast_service.dart'; import '../../../data/services/speech_impl/azure_ast_service.dart';
import 'package:get/get.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/meeting/meeting_upload_service.dart';
import '../../data/services/novice_guide_service.dart'; import '../../data/services/novice_guide_service.dart';
import '../../data/services/speech_factory.dart'; import '../../data/services/speech_factory.dart';
@ -72,15 +69,6 @@ class InitialBinding extends Bindings {
// 会议助手任务服务 // 会议助手任务服务
Get.lazyPut<MeetingTaskService>(() => MeetingTaskService(), fenix: true); Get.lazyPut<MeetingTaskService>(() => MeetingTaskService(), fenix: true);
// 会议助手任务服务(极速识别版,2小时内音频)
Get.lazyPut<MeetingTaskService2>(() => MeetingTaskService2(), fenix: true);
// 会议助手任务服务聚合器(统一列表与状态)
Get.lazyPut<MeetingTaskAggregator>(() => MeetingTaskAggregator(),
fenix: true);
// 会议助手总结任务服务
Get.lazyPut<SummaryTaskService>(() => SummaryTaskService(), fenix: true);
// 会议助手音频上传 // 会议助手音频上传
Get.lazyPut<MeetingUploadService>(() => MeetingUploadService(), Get.lazyPut<MeetingUploadService>(() => MeetingUploadService(),
fenix: true); fenix: true);

131
lib/core/utils/synchrodata.dart

@ -1,9 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import '../../data/services/db/database_helper.dart'; import '../../data/services/db/database_helper.dart';
import '../../data/services/db/sqflite_api.dart'; import '../../data/services/db/sqflite_api.dart';
import '../../data/services/meeting/meeting_task_service.dart';
import '../../data/services/network/api.dart'; import '../../data/services/network/api.dart';
class Synchrodata { class Synchrodata {
@ -76,35 +78,35 @@ class Synchrodata {
static Future uploadOperationRecords({bool isSynchro = false}) async { static Future uploadOperationRecords({bool isSynchro = false}) async {
if (_isSynchro) return; if (_isSynchro) return;
_isSynchro = true; _isSynchro = true;
if (_records.isNotEmpty || _delids.isNotEmpty) { // if (_records.isNotEmpty || _delids.isNotEmpty) {
// 获取需要修改的全部数据 // // 获取需要修改的全部数据
List putRecords = []; // List putRecords = [];
for (var element in _records) { // for (var element in _records) {
Map result = {'details': {}}; // Map result = {'details': {}};
List speakerList = []; // List speakerList = [];
if (!_delids.contains(element['id'])) { // if (!_delids.contains(element['id'])) {
result = await SqfliteApi.getMeetingDetails(element['number']); // result = await SqfliteApi.getMeetingDetails(element['number']);
speakerList = await SqfliteApi.getMeetingSpeaker(element['number']); // speakerList = await SqfliteApi.getMeetingSpeaker(element['number']);
} // }
putRecords.add({ // putRecords.add({
...Map<String, dynamic>.from(result), // ...Map<String, dynamic>.from(result),
...Map<String, dynamic>.from(result['details']), // ...Map<String, dynamic>.from(result['details']),
'rtype': result['type'], // 'rtype': result['type'],
'original': jsonEncode(speakerList), // 'original': jsonEncode(speakerList),
...Map<String, dynamic>.from(element), // ...Map<String, dynamic>.from(element),
}); // });
} // }
var result = await Api.putOperationRecord({ // var result = await Api.putOperationRecord({
'delids': _delids, // 'delids': _delids,
'records': putRecords, // 'records': putRecords,
}); // });
if (result != null) { // if (result != null) {
_records = []; // _records = [];
_delids = []; // _delids = [];
_storage.write("operation_records", _records); // _storage.write("operation_records", _records);
_storage.write("operation_delids", _delids); // _storage.write("operation_delids", _delids);
} // }
} // }
if (isSynchro) { if (isSynchro) {
await _synchrodata(); await _synchrodata();
} }
@ -113,32 +115,40 @@ class Synchrodata {
// 同步数据 // 同步数据
static Future _synchrodata() async { static Future _synchrodata() async {
var result = await Api.getOperationRecord(); var result = await Api.getOperationAllRecord();
List records = result['records'] ?? []; List records = result['records'] ?? [];
if (records.isEmpty) return; if (records.isEmpty) return;
final meetingTaskService = Get.find<MeetingTaskService>();
List taskList = [];
final db = await DatabaseHelper().database; final db = await DatabaseHelper().database;
await db.transaction((txn) async { await db.transaction((txn) async {
for (var element in records) { for (var element in records) {
if (element['state'] > 0 && element['state'] < 5) {
taskList.add({
'id': element['id'],
'tasktype': element['state'],
});
}
final List existList = await txn.query( final List existList = await txn.query(
'meeting', 'meeting',
where: 'id = ?', where: 'id = ?',
whereArgs: [element['number']], whereArgs: [element['id']],
); );
Map<String, Object?> meetingData = { Map<String, Object?> meetingData = {
'id': element['number'], 'id': element['id'],
'title': element['title'] ?? '', 'title': element['title'] ?? '',
'type': element['rtype'] ?? '', 'type': element['rtype'] ?? '',
'seconds': element['seconds'] ?? 0, 'seconds': element['seconds'] ?? 0,
'audiourl': element['audiourl'] ?? '', 'audiourl': element['audiourl'] ?? '',
'tasktype': element['tasktype'] ?? 0, 'tasktype': element['state'] ?? 0,
'creationtime': element['creationtime'] ?? 0, 'creationtime': (element['creationtime'] ?? 0) * 1000,
}; };
Map<String, Object?> meetingDetailsData = { Map<String, Object?> meetingDetailsData = {
'meetingid': element['number'], 'meetingid': element['id'],
'address': element['address'] ?? '', 'address': element['address'] ?? '',
'personnel': element['personnel'] ?? '', 'personnel': element['personnel'] ?? '',
'taskid': element['taskid'] ?? '', 'taskid': element['taskid'] ?? '',
'tasktype': element['tasktype'] ?? 0, 'tasktype': element['state'] ?? 0,
'overview': element['overview'] ?? '', 'overview': element['overview'] ?? '',
'summary': element['summary'] ?? '', 'summary': element['summary'] ?? '',
'mindmap': '', 'mindmap': '',
@ -148,13 +158,13 @@ class Synchrodata {
'meeting', 'meeting',
meetingData, meetingData,
where: 'id = ?', where: 'id = ?',
whereArgs: [element['number']], whereArgs: [element['id']],
); );
await txn.update( await txn.update(
'meetingdetails', 'meetingdetails',
meetingDetailsData, meetingDetailsData,
where: 'meetingid = ?', where: 'meetingid = ?',
whereArgs: [element['number']], whereArgs: [element['id']],
); );
} else { } else {
await txn.insert('meeting', { await txn.insert('meeting', {
@ -163,48 +173,21 @@ class Synchrodata {
}); });
await txn.insert('meetingdetails', meetingDetailsData); await txn.insert('meetingdetails', meetingDetailsData);
} }
if (element['original'].isNotEmpty) { if (element['translate'].isNotEmpty) {
final List existSpeakerList = await txn.query( List speakerList = jsonDecode(element['translate']);
await txn.delete(
'meetingspeaker', 'meetingspeaker',
where: 'meetingid = ?', where: 'meetingid = ?',
whereArgs: [element['number']], whereArgs: [element['id']],
); );
List speakerList = jsonDecode(element['original']); final batch = txn.batch();
if (existSpeakerList.isNotEmpty) { for (var item in speakerList) {
final batch = txn.batch(); batch.insert('meetingspeaker', item);
for (var i = 0; i < existSpeakerList.length; i++) {
Map item = speakerList[i] ?? {};
if (item.isNotEmpty) {
batch.update(
'meetingspeaker',
{
'meetingid': item['meetingid'],
'content': item['content'],
'starttime': item['starttime'],
'endtime': item['endtime'],
'speaker': item['speaker'],
},
where: 'id = ?',
whereArgs: [existSpeakerList[i]['id']],
);
}
}
await batch.commit();
} else {
final batch = txn.batch();
for (var item in speakerList) {
batch.insert('meetingspeaker', {
'meetingid': item['meetingid'],
'content': item['content'],
'starttime': item['starttime'],
'endtime': item['endtime'],
'speaker': item['speaker'],
});
}
await batch.commit();
} }
await batch.commit();
} }
} }
}); });
meetingTaskService.startTask(taskList);
} }
} }

7
lib/data/services/account_service.dart

@ -3,11 +3,10 @@ import 'package:get_storage/get_storage.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import '../../core/utils/synchrodata.dart'; import '../../core/utils/synchrodata.dart';
import '../../modules/login/controllers/googleLogin.dart'; import '../../modules/login/controllers/googleLogin.dart';
import '../../modules/login/controllers/facebookLogin.dart';
import '../services/network/api.dart'; import '../services/network/api.dart';
import 'db/database_helper.dart'; import 'db/database_helper.dart';
import 'meeting/meeting_task_service.dart';
import '../../data/models/user_Info.dart'; import '../../data/models/user_Info.dart';
import 'meeting/meeting_upload_service.dart';
/// 账号管理服务 /// 账号管理服务
/// ///
@ -84,8 +83,8 @@ class AccountService extends GetxService {
Future<AccountOperationResult> signOut() async { Future<AccountOperationResult> signOut() async {
try { try {
// 清除会议记录任务列表 // 清除会议记录任务列表
final taskService = Get.find<MeetingTaskService>(); final uploadService = Get.find<MeetingUploadService>();
taskService.cancelTask(); uploadService.cancelUpload();
// 上传会议记录操作记录 // 上传会议记录操作记录
await Synchrodata.uploadOperationRecords(); await Synchrodata.uploadOperationRecords();

29
lib/data/services/db/sqflite_api.dart

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../core/utils/synchrodata.dart';
import 'database_helper.dart'; import 'database_helper.dart';
class SqfliteApi { class SqfliteApi {
@ -58,11 +57,10 @@ class SqfliteApi {
static Future insertMeeting(Map<String, Object?> meeting) async { static Future insertMeeting(Map<String, Object?> meeting) async {
final db = await DatabaseHelper().database; final db = await DatabaseHelper().database;
try { try {
int id = 0;
await db.transaction((txn) async { await db.transaction((txn) async {
id = await txn.insert('meeting', meeting); await txn.insert('meeting', meeting);
await txn.insert('meetingdetails', { await txn.insert('meetingdetails', {
'meetingid': id, 'meetingid': meeting['id'],
'address': '', 'address': '',
'personnel': '', 'personnel': '',
'taskid': '', 'taskid': '',
@ -71,12 +69,8 @@ class SqfliteApi {
'summary': '', 'summary': '',
'mindmap': '', 'mindmap': '',
}); });
Synchrodata.saveOperationRecord('insert', {
...meeting,
'id': id,
});
}); });
return id; return true;
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(
'error'.tr, // 错误 'error'.tr, // 错误
@ -99,10 +93,6 @@ class SqfliteApi {
where: 'meetingid = ?', where: 'meetingid = ?',
whereArgs: [id], whereArgs: [id],
); );
Synchrodata.saveOperationRecord('update', {
...meeting,
'id': id,
});
return true; return true;
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(
@ -168,14 +158,16 @@ class SqfliteApi {
} }
} }
// 删除商务会议助手转写内容
static Future deleteMeetingSpeaker(id) async {
final db = await DatabaseHelper().database;
await db.delete('meetingspeaker', where: 'meetingid = ?', whereArgs: [id]);
}
// 修改商务会议助手标题 // 修改商务会议助手标题
static Future editMeetingTitle(id, Map<String, Object?> meeting) async { static Future editMeetingTitle(id, Map<String, Object?> meeting) async {
final db = await DatabaseHelper().database; final db = await DatabaseHelper().database;
await db.update('meeting', meeting, where: 'id = ?', whereArgs: [id]); await db.update('meeting', meeting, where: 'id = ?', whereArgs: [id]);
Synchrodata.saveOperationRecord('update', {
...meeting,
'id': id,
});
} }
// 删除商务会议助手 // 删除商务会议助手
@ -187,9 +179,6 @@ class SqfliteApi {
.delete('meetingdetails', where: 'meetingid = ?', whereArgs: [id]); .delete('meetingdetails', where: 'meetingid = ?', whereArgs: [id]);
await txn await txn
.delete('meetingspeaker', where: 'meetingid = ?', whereArgs: [id]); .delete('meetingspeaker', where: 'meetingid = ?', whereArgs: [id]);
Synchrodata.saveOperationRecord('delete', {
'id': id,
});
}); });
} }

105
lib/data/services/meeting/meeting_task_aggregator.dart

@ -1,105 +0,0 @@
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<MeetingTaskService>();
final MeetingTaskService2 s2 = Get.find<MeetingTaskService2>();
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<int> 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;
}
}

563
lib/data/services/meeting/meeting_task_service.dart

@ -1,133 +1,34 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:get/get.dart'; 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 '../../models/meeting_usage_stats.dart';
import '../db/sqflite_api.dart'; import '../db/sqflite_api.dart';
import '../usage_stats_service.dart'; import '../network/api.dart';
import 'meeting_upload_service.dart'; import 'meeting_upload_service.dart';
import '../../../data/models/appconfig.dart';
import 'summary_task_service.dart';
///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。
///火山引擎录音文件识别大模型-标准版
class MeetingTaskService extends GetxService { class MeetingTaskService extends GetxService {
final GetStorage _storage = GetStorage();
MeetingUploadService get _uploadService => Get.find<MeetingUploadService>(); MeetingUploadService get _uploadService => Get.find<MeetingUploadService>();
SummaryTaskService get _summaryTaskService => Get.find<SummaryTaskService>();
List _taskList = []; //待处理的会议任务列表 List _taskList = []; //待处理的会议任务列表
RxList taskFinishList = [].obs; //已处理的会议任务列表
bool _isTask = false; //是否有正在处理的任务 bool _isTask = false; //是否有正在处理的任务
String _userId = ''; RxInt updateListState = 0.obs; //是否更新首页列表
// final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; //
// final String _cluster = 'volc_auc_video_flash';
//字节跳动(火山引擎)语音服务配置
// final String _appid = '2316081933';
// final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En';
// final String _cluster1 = 'volc.bigasr.auc';
// final String _serviceUrl1 =
// 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; //大模型录音文件识别api地址
late final String _appid;
late final String _token;
late final String _cluster1;
late final String _serviceUrl1;
late final String _modelName;
late final String _modelVersion;
Map<String, String> _headers = {};
Map<String, String> _headers1 = {};
int meetingId = 0; int meetingId = 0;
RxInt tasktype = 1.obs; //任务类型,1-转写中,2-总结中,3-完成 RxInt tasktype = 1.obs; //任务类型,0-初始,1-等待转写中,2-转写中,3-等待总结中,4-总结中,5-完成
List textList = []; //转写结果列表 List textList = [];
String personnel = '';
@override String summary = '';
void onInit() { String overview = '';
super.onInit();
Future<void> addTask(
_initialize(); int id,
String audiourl,
_getAgent(); String formlanguage,
_getTaskList(); String tolanguage,
} bool isdistinguishspeaker,
int templateid,
void cancelTask() { ) async {
_isTask = false;
_taskList.clear();
_storage.remove("meeting_task_list");
_storage.remove("meeting_task_finish_list");
_uploadService.cancelUpload();
_summaryTaskService.cancelTask();
}
Future<bool> _initialize() async {
try {
_appid = AppConfig.env('VOLC_OPENSPEECH_APP_ID') ?? '';
_token = AppConfig.env('VOLC_OPENSPEECH_ACCESS_TOKEN') ?? '';
_cluster1 = AppConfig.env('VOLC_OPENSPEECH_CLUSTER_BIGMODEL') ?? '';
_serviceUrl1 = AppConfig.env('VOLC_OPENSPEECH_BASE_URL_BIGMODEL') ?? '';
_modelName = AppConfig.env('VOLC_OPENSPEECH_MODEL_NAME') ?? 'bigmodel';
_modelVersion = AppConfig.env('VOLC_OPENSPEECH_MODEL_VERSION') ?? '400';
if (_appid.isEmpty ||
_token.isEmpty ||
_cluster1.isEmpty ||
_serviceUrl1.isEmpty ||
_modelName.isEmpty ||
_modelVersion.isEmpty) {
Logger.error('初始化会议任务服务失败:配置参数为空');
return false;
}
_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',
};
} catch (e) {
Logger.error('初始化会议任务服务失败:$e');
return false;
}
return true;
}
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<void> addTask(int id, String audiourl, String taskid, bool isSpeaker,
String language, String prompt, String outline) async {
if (audiourl.isEmpty) { if (audiourl.isEmpty) {
Map uploadData = _uploadService.uploadList.firstWhere( Map uploadData = _uploadService.uploadList.firstWhere(
(element) => element['id'] == id, (element) => element['id'] == id,
@ -136,343 +37,169 @@ class MeetingTaskService extends GetxService {
if (uploadData.isNotEmpty) { if (uploadData.isNotEmpty) {
if (uploadData['audiourl'].isEmpty) { if (uploadData['audiourl'].isEmpty) {
uploadData['isTask'] = true; uploadData['isTask'] = true;
uploadData['isSpeaker'] = isSpeaker; uploadData['formlanguage'] = formlanguage;
uploadData['language'] = language; uploadData['tolanguage'] = tolanguage;
uploadData['prompt'] = prompt; uploadData['isdistinguishspeaker'] = isdistinguishspeaker;
uploadData['outline'] = outline; uploadData['templateid'] = templateid;
} else { } else {
await addTaskList(id, uploadData['audiourl'], taskid, isSpeaker, await submitTask(
language, prompt, outline); id,
formlanguage,
tolanguage,
isdistinguishspeaker,
templateid,
);
} }
} }
} else { } else {
await addTaskList( await submitTask(
id, audiourl, taskid, isSpeaker, language, prompt, outline); id,
formlanguage,
tolanguage,
isdistinguishspeaker,
templateid,
);
} }
} }
///添加会议任务列表 Future<void> submitTask(
///[id] 会议ID int id,
///[audiourl] 音频URL String formlanguage,
///[taskId] 任务ID String tolanguage,
///[isSpeaker] 是否为发言人 bool isdistinguishspeaker,
///[language] 语言 int templateid,
///[prompt] 提示词 ) async {
///[outline] 大纲 await Api.startTaskEchomeet({
Future<void> addTaskList(int id, String audiourl, String taskId, 'id': id,
bool isSpeaker, String language, String prompt, String outline) async { 'formlanguage': formlanguage,
if (taskId.isEmpty) { 'tolanguage': tolanguage,
taskId = await _submitTask(audiourl, isSpeaker, language); 'isdistinguishspeaker': isdistinguishspeaker,
} 'templateid': templateid,
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,
);
try {
final svc = Get.isRegistered<UsageStatsService>()
? Get.find<UsageStatsService>()
: Get.put(UsageStatsService(), permanent: true);
svc.addMeetingAsrRecord(record);
} catch (e) {
Logger.w('MeetingASRrecord', '提交用量记录失败: ' + e.toString());
}
} catch (_) {}
_taskList.add({ _taskList.add({
'id': id, 'id': id,
'taskid': taskId,
'tasktype': 1, 'tasktype': 1,
'prompt': prompt,
'outline': outline,
}); });
taskFinishList.add({ await SqfliteApi.editMeetingTitle(
id,
{'tasktype': 1},
);
updateListState.value++;
_executeTask();
}
Future<void> refreshTask(int id, int templateid) async {
tasktype.value = 3;
await Api.refreshTaskEchomeet({
'id': id, 'id': id,
'tasktype': 1, 'templateid': templateid,
});
_taskList.add({
'id': id,
'tasktype': 3,
}); });
_storage.write("meeting_task_list", _taskList); await SqfliteApi.editMeetingTitle(
_storage.write("meeting_task_finish_list", taskFinishList); id,
{'tasktype': 3},
);
updateListState.value++;
_executeTask(); _executeTask();
} }
///执行会议任务 Future<void> readTask(int id) async {
await Api.readRecordEchomeet({'id': id});
await SqfliteApi.editMeetingTitle(
id,
{'tasktype': 6},
);
updateListState.value++;
}
void _executeTask() async { void _executeTask() async {
if (_isTask) return; if (_isTask) return;
_isTask = true; _isTask = true;
while (_taskList.isNotEmpty) { while (_taskList.isNotEmpty) {
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 5));
Logger.info("lxm--轮询---会议任务列表$_taskList"); await _queryTask();
for (var task in List.from(_taskList)) {
await _fileRecognize(task);
}
} }
_isTask = false; _isTask = false;
} }
void removeTask(int id, {bool isDeleteTask = false}) { Future<void> removeTask(int id) async {
Map taskFinishData = taskFinishList.firstWhere( _taskList.removeWhere((element) => element['id'] == id);
(element) => element['id'] == id, await Api.delRecordEchomeet({
orElse: () => {}, 'ids': [id],
);
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);
_summaryTaskService.removeTask(id);
}
}
//
Future<String> _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": _modelName, // 模型名称,目前只有bigmodel
"model_version": _modelVersion, // 模型版本,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) { void startTask(List data) {
List<String> array = path.split('.'); _taskList = data;
return array[array.length - 1]; _executeTask();
}
///轮询语音转文字任务状态
///[taskId] 任务ID
Future<Map<String, dynamic>> _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;
}
} }
///轮询语音转文字任务状态,若任务完成则处理转写结果 void stopTask() {
///[item] 包含任务ID、会议ID、AI总结提示词、AI概览提示词的Map _taskList.clear();
Future<void> _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 &&
respDic['result']['text'] != '') {
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分析 Future<void> _queryTask() async {
///[id] 会议ID if (_taskList.isEmpty) return;
///[utterances] 语音转文字结果 List ids = _taskList.map((e) => e['id']).toList();
///[prompt] AI总结提示词 var response = await Api.getMultitermRecord({
///[outline] AI概览提示词 'ids': ids,
Future<void> _addTextList( });
int id, List utterances, String prompt, String outline) async { List queryList = response['records'] ?? [];
String text = ''; for (var element in queryList) {
String personnel = ''; int id = element['id'];
List<Map<String, Object?>> list = []; int newTasktype = element['state'];
Map taskData = _taskList.firstWhere(
// 处理转写结果,将每个句子的开始时间、结束时间、发言人和内容存储到列表中 (i) => i['id'] == id,
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: () => {}, orElse: () => {},
); );
if (taskFinishData.isNotEmpty) { if (taskData.isNotEmpty) {
taskFinishData['tasktype'] = 2; int oldTasktype = taskData['tasktype'];
taskFinishList.refresh(); if (oldTasktype != newTasktype) {
if (oldTasktype < 3 && newTasktype >= 3) {
// 转写完成
List<dynamic> decodeList = jsonDecode(element['translate']);
List<Map<String, Object?>> speakerList =
decodeList.map((e) => Map<String, Object?>.from(e)).toList();
await SqfliteApi.deleteMeetingSpeaker(id);
await SqfliteApi.insertMeetingSpeaker(speakerList);
await SqfliteApi.editMeeting(
id,
{'tasktype': newTasktype},
);
if (id == meetingId) {
textList = speakerList;
tasktype.value = newTasktype;
}
}
if (oldTasktype < 5 && newTasktype >= 5) {
// 总结完成
await SqfliteApi.editMeeting(id, {
'tasktype': newTasktype,
'personnel': element['personnel'] ?? '',
'summary': element['summary'] ?? '',
'overview': element['overview'] ?? '',
});
if (id == meetingId) {
personnel = element['personnel'] ?? '';
summary = element['summary'] ?? '';
overview = element['overview'] ?? '';
tasktype.value = newTasktype;
}
}
await SqfliteApi.editMeetingTitle(
id,
{'tasktype': newTasktype},
);
taskData['tasktype'] = newTasktype;
updateListState.value++;
}
} }
_storage.write("meeting_task_list", _taskList); if (newTasktype >= 5) {
_storage.write("meeting_task_finish_list", taskFinishList); _taskList.removeWhere((i) => i['id'] == id);
}
try {
final details = await SqfliteApi.getMeetingDetails(id);
final tid = (details['taskid'] ?? '') as String;
final transcriptBytes = utf8.encode(text).length;
try {
final svc = Get.isRegistered<UsageStatsService>()
? Get.find<UsageStatsService>()
: Get.put(UsageStatsService(), permanent: true);
svc.completeMeetingAsrRecord(
id: tid,
transcriptBytes: transcriptBytes,
completeTimestamp: DateTime.now().millisecondsSinceEpoch,
);
} catch (e) {
Logger.w('MeetingASRrecord', '完成用量记录失败: ' + e.toString());
} }
} catch (_) {} }
// 触发AI分析,分别请求总结和概览
_summaryTaskService.addTaskList(id, text, prompt, outline);
} }
} }

971
lib/data/services/meeting/meeting_task_service2.dart

@ -1,971 +0,0 @@
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 '../db/sqflite_api.dart';
import '../../../data/models/appconfig.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/synchrodata.dart';
import '../usage_stats_service.dart';
import '../../../data/models/meeting_usage_stats.dart';
import '../deapsound_ai_service.dart';
import 'meeting_upload_service.dart';
// 移除标准版服务桥接,改为通过聚合器对外暴露统一状态
import 'summary_task_service.dart';
//火山引擎录音文件识别大模型-极速版
class MeetingTaskService2 extends GetxService {
final GetStorage _storage = GetStorage();
MeetingUploadService get _uploadService => Get.find<MeetingUploadService>();
SummaryTaskService get _summaryTaskService => Get.find<SummaryTaskService>();
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<void> 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<void> addTaskList(
int id,
String audiourl, {
bool isSpeaker = false,
String language = '',
String targetLanguage = '',
String prompt = '',
String outline = '',
}) async {
final String taskId = DateTime.now().millisecondsSinceEpoch.toString();
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,
);
try {
final svc = Get.isRegistered<UsageStatsService>()
? Get.find<UsageStatsService>()
: Get.put(UsageStatsService(), permanent: true);
svc.addMeetingAsrRecord(record);
} catch (e) {
Logger.w('MeetingASRrecord', '提交用量记录失败: ' + e.toString());
}
} catch (_) {}
_taskList.add({
'id': id,
'audiourl': audiourl,
'tasktype': 1,
'taskid': taskId,
'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);
// 由聚合器统一对外呈现状态,不直接写入标准版列表
_executeTask();
}
void _executeTask() async {
if (_isTask) return;
_isTask = true;
Logger.debug('开始执行任务,任务数量:${_taskList.length}');
while (_taskList.isNotEmpty) {
Logger.debug('任务循环执行,剩余任务:${_taskList.length}');
await Future.delayed(const Duration(seconds: 2));
// 创建任务列表的副本,避免在迭代过程中修改原列表
List tasksCopy = List.from(_taskList);
for (var element in tasksCopy) {
Logger.debug('处理任务:${element['id']},音频:${element['audiourl']}');
// 检查任务是否仍在列表中(可能已被其他地方移除)
if (_taskList.any((task) => task['id'] == element['id'])) {
try {
await _fileRecognize(element);
} catch (e) {
Logger.error('处理任务失败:${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('任务 ${element['id']} 已从任务列表移除');
}
}
// 如果没有任务被处理,避免无限循环
if (_taskList.length == tasksCopy.length) {
Logger.warning('本次循环未处理任何任务,为避免无限执行终止循环');
break;
}
}
_isTask = false;
Logger.debug('任务执行完成,剩余任务:${_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<Map<String, dynamic>> _recognizeAudio(
String audiourl, {
bool enableSpeakerInfo = true,
String language = '',
String? requestIdOverride,
}) async {
String requestId =
requestIdOverride ?? DateTime.now().millisecondsSinceEpoch.toString();
// 为每次请求生成新的headers - 适配大模型极速版API
Map<String, String> 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<String, dynamic> 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 请求地址:$_serviceUrl');
Logger.debug('API 请求头:$requestHeaders');
Logger.debug('API 请求体:$requestBody');
final response = await http.post(
Uri.parse(_serviceUrl),
headers: requestHeaders,
body: requestBody,
);
Logger.debug('API 响应状态码:${response.statusCode}');
Logger.debug('API 响应头:${response.headers}');
if (response.statusCode != 200) {
Logger.error('识别失败,状态码:${response.statusCode},响应体:${response.body}');
throw Exception('识别失败,状态码:${response.statusCode}');
}
final respDic = jsonDecode(response.body);
Logger.debug('识别接口响应:$respDic');
// 检查响应中是否包含资源相关信息
if (respDic.containsKey('resource_id') || respDic.containsKey('model')) {
Logger.debug('资源校验 - 使用的资源ID:volc.bigasr.auc_turbo');
Logger.debug(
'响应中的资源信息:${respDic['resource_id'] ?? respDic['model'] ?? '响应未包含'}');
}
return respDic;
} catch (e) {
Logger.error('识别请求异常:${e.toString()}');
rethrow;
}
}
String _getFileType(String path) {
List<String> 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('时间值解析失败:$timeValue');
return 0;
}
} else {
Logger.error('未知的时间值类型:${timeValue.runtimeType},值:$timeValue');
return 0;
}
}
Future<void> _fileRecognize(Map item) async {
try {
Logger.debug('开始识别音频:${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,
requestIdOverride: (item['taskid'] ?? '').toString().isEmpty
? null
: (item['taskid'] ?? '').toString(),
);
Logger.debug('API 全量响应结构:${respDic.toString()}');
Logger.debug('响应包含的键:${respDic.keys.toList()}');
// 检查响应格式并提取utterances
List utterances = [];
// 检查不同可能的响应格式
if (respDic['result'] != null &&
respDic['result']['utterances'] != null) {
utterances = respDic['result']['utterances'];
Logger.debug('在 result.utterances 中找到 ${utterances.length} 条语句');
} else if (respDic['resp'] != null &&
respDic['resp']['utterances'] != null) {
utterances = respDic['resp']['utterances'];
Logger.debug('在 resp.utterances 中找到 ${utterances.length} 条语句');
} else if (respDic['utterances'] != null) {
utterances = respDic['utterances'];
Logger.debug('在根级 utterances 中找到 ${utterances.length} 条语句');
} else if (respDic['data'] != null &&
respDic['data']['utterances'] != null) {
utterances = respDic['data']['utterances'];
Logger.debug('在 data.utterances 中找到 ${utterances.length} 条语句');
} else if (respDic['response'] != null &&
respDic['response']['utterances'] != null) {
utterances = respDic['response']['utterances'];
Logger.debug('在 response.utterances 中找到 ${utterances.length} 条语句');
} else {
// 检查是否有错误信息
if (respDic['error'] != null) {
Logger.error('接口返回错误:${respDic['error']}');
} else if (respDic['message'] != null) {
Logger.error('接口返回消息:${respDic['message']}');
} else {
Logger.error('未知的响应格式。可用键:${respDic.keys.toList()}');
// 尝试查找任何包含文本的字段
respDic.forEach((key, value) {
Logger.debug('键:$key,值类型:${value.runtimeType},值:$value');
});
}
}
if (utterances.isNotEmpty) {
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('音频识别成功,会议ID:${item['id']}');
// 更新UI状态 - 如果是当前会议,触发UI更新
if (item['id'] == meetingId) {
tasktype.value = 2; // 转写完成,进入分析阶段
}
} else {
Logger.error('响应中未发现语句。完整响应:$respDic');
Logger.debug('未检测到语句,开始执行无声音频检查');
Logger.debug('无声音频检测调试信息');
Logger.debug('响应键列表:${respDic.keys.toList()}');
Logger.debug('响应代码:${respDic['code']}');
Logger.debug('响应消息:${respDic['message']}');
// 检查是否为无声音频
bool isSilentAudio = _checkIfSilentAudio(respDic);
Logger.debug('无声检测结果:$isSilentAudio');
if (isSilentAudio) {
Logger.debug('检测到无声音频,设置任务类型为 -1');
// 设置无声音频状态 (使用特殊值 -1)
await SqfliteApi.editMeeting(item['id'], {'tasktype': -1});
if (item['id'] == meetingId) {
Logger.debug('当前会议设置 tasktype.value 为 -1');
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);
// 由聚合器统一对外呈现状态,不直接写入标准版列表
// 由聚合器统一对外呈现状态,不直接写入标准版列表
} else {
Logger.error('识别失败,原因未知');
// 识别失败,更新数据库状态为失败
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('文件识别流程发生异常:${e.toString()}');
Logger.error('堆栈信息:${StackTrace.current}');
// 更新数据库状态为失败
try {
await SqfliteApi.editMeeting(item['id'], {'tasktype': 0});
} catch (dbError) {
Logger.error('数据库更新会议状态失败:$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<String, dynamic> response) {
Logger.debug('检查是否为无声音频,响应:${response.toString()}');
// 检查API响应中的错误信息或状态码
// 根据实际API响应格式调整检测逻辑
// 检查是否有错误码表示无声音频
if (response['code'] != null) {
int code = response['code'];
Logger.debug('检测到响应代码:$code');
// 常见的无声音频错误码
if (code == 40001 ||
code == 40002 ||
code == 40003 ||
code == 50001 ||
code == 50002 ||
code == 50003) {
Logger.debug('根据错误码判断为无声音频:$code');
return true;
}
}
// 检查错误消息
if (response['message'] != null) {
String message = response['message'].toString().toLowerCase();
Logger.debug('检测到响应消息:$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')) {
Logger.debug('根据消息判断为无声音频:$message');
return true;
}
}
// 检查result中的错误信息
if (response['result'] != null && response['result']['error'] != null) {
String error = response['result']['error'].toString().toLowerCase();
Logger.debug('检测到 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')) {
Logger.debug('根据 result.error 判断为无声音频:$error');
return true;
}
}
// 检查是否有音频时长信息且时长为0或极短
if (response['result'] != null && response['result']['duration'] != null) {
double duration = (response['result']['duration'] as num).toDouble();
Logger.debug('检测到音频时长:$duration 秒');
if (duration <= 0.5) {
// 音频时长小于0.5秒认为是无声
Logger.debug('根据时长过短判断为无声音频:$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; // 转换为秒
}
Logger.debug('文本内容为空,音频时长:$audioDuration 秒');
if (audioDuration > 1.0) {
// 音频时长大于1秒但没有文本内容,认为是无声音频
Logger.debug('判断为无声音频:文本为空且时长充足');
return true;
}
}
// 如果没有utterances且响应状态正常,可能是无声音频
if (!hasUtterances) {
// 检查是否有明确的成功状态码
bool hasSuccessCode = false;
if (response['code'] != null) {
int code = response['code'];
hasSuccessCode = (code == 0 || code == 200);
}
// 如果有成功状态码但没有utterances,很可能是无声音频
if (hasSuccessCode) {
Logger.debug('判断为无声音频:成功状态码但无语句');
return true;
}
}
Logger.debug('未判定为无声音频');
return false;
}
Future<void> _addTextList(int id, List utterances,
[Map? templateData]) async {
Logger.debug('处理会议 $id 的 ${utterances.length} 条语句');
String text = '';
String personnel = '';
List<Map<String, Object?>> list = [];
for (int i = 0; i < utterances.length; i++) {
var element = utterances[i];
Logger.debug('处理第 $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('语句中未找到文本内容:$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('已添加语句:说话人=$speaker,内容=$content,开始=$startTime,结束=$endTime');
}
Logger.debug('处理完成的语句总数:${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('转写文本翻译失败:${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;
}
// 由聚合器统一对外呈现状态,不直接写入标准版列表
// 由聚合器统一对外呈现状态,不直接写入标准版列表
// 用最新(已翻译)的文本重建总结/概览输入
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 + '输出。');
try {
final details = await SqfliteApi.getMeetingDetails(id);
final tid = (details['taskid'] ?? '') as String;
final transcriptBytes = utf8.encode(summarySourceText).length;
final svc = Get.isRegistered<UsageStatsService>()
? Get.find<UsageStatsService>()
: Get.put(UsageStatsService(), permanent: true);
svc.completeMeetingAsrRecord(
id: tid,
transcriptBytes: transcriptBytes,
completeTimestamp: DateTime.now().millisecondsSinceEpoch,
);
} catch (e) {
Logger.w('MeetingASRrecord', '完成用量记录失败: ' + e.toString());
}
_summaryTaskService.addTaskList(
id, summarySourceText, promptToUse, outlineToUse);
}
}
Future<void> _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<dynamic> 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('翻译结果 JSON 解析失败:${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 (_) {}
}
}
}

62
lib/data/services/meeting/meeting_upload_service.dart

@ -4,10 +4,10 @@ import 'package:get_storage/get_storage.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart'; import '../../../core/utils/upload_oss.dart';
import '../network/api.dart';
import '../usage_stats_service.dart'; import '../usage_stats_service.dart';
import '../db/sqflite_api.dart'; import '../db/sqflite_api.dart';
import 'meeting_task_service.dart'; import 'meeting_task_service.dart';
import 'meeting_task_service2.dart';
/// 管理会议音频文件上传服务 /// 管理会议音频文件上传服务
class MeetingUploadService extends GetxService { class MeetingUploadService extends GetxService {
@ -47,26 +47,20 @@ class MeetingUploadService extends GetxService {
String rootDir, { String rootDir, {
bool isUpload = true, bool isUpload = true,
}) async { }) async {
bool flash = false;
int fileSize = 0; int fileSize = 0;
try { try {
fileSize = await File(filePath).length(); //获取文件大小 fileSize = await File(filePath).length();
final details = await SqfliteApi.getMeetingDetails(id);
final int seconds = (details['seconds'] ?? 0) as int;
flash = seconds > 0 && seconds <= 7200;
} catch (_) {} } catch (_) {}
_storage.write("meeting_service_type_$id", flash ? 'fast' : 'standard');
uploadList.add({ uploadList.add({
'id': id, 'id': id,
'filepath': filePath, 'filepath': filePath,
'rootdir': rootDir, 'rootdir': rootDir,
'audiourl': '', 'audiourl': '',
'isTask': false, 'isTask': false,
'isSpeaker': true, 'formlanguage': '',
'language': '', 'tolanguage': '',
'prompt': '', 'isdistinguishspeaker': true,
'outline': '', 'templateid': 0,
'useFlashASR': flash,
'fileSizeBytes': fileSize, 'fileSizeBytes': fileSize,
}); });
_storage.write("meeting_upload_list", uploadList); _storage.write("meeting_upload_list", uploadList);
@ -85,18 +79,20 @@ class MeetingUploadService extends GetxService {
if (element['audiourl'].isEmpty) { if (element['audiourl'].isEmpty) {
meetingId.value = element['id']; meetingId.value = element['id'];
try { try {
int _sent = 0; int totalProgress = 0;
int _total = 0;
final audiourl = await UploadOss.upload( final audiourl = await UploadOss.upload(
filepath: element['filepath'], filepath: element['filepath'],
rootDir: element['rootdir'], rootDir: element['rootdir'],
onSendProgress: (int count, int total) { onSendProgress: (int count, int total) {
// uploadProgress.value = count / total; // uploadProgress.value = count / total;
_sent = count; totalProgress = total;
_total = total;
uploadProgress.value = total > 0 ? count / total : 0.0; uploadProgress.value = total > 0 ? count / total : 0.0;
}, },
); );
await Api.upRecordEchomeet({
'id': element['id'],
'audiourl': audiourl,
});
await SqfliteApi.editMeetingTitle( await SqfliteApi.editMeetingTitle(
element['id'], element['id'],
{'audiourl': audiourl}, {'audiourl': audiourl},
@ -110,7 +106,8 @@ class MeetingUploadService extends GetxService {
try { try {
fileSizeBytes = element['fileSizeBytes'] ?? 0; fileSizeBytes = element['fileSizeBytes'] ?? 0;
} catch (_) {} } catch (_) {}
final transferred = _total > 0 ? _total : fileSizeBytes; final transferred =
totalProgress > 0 ? totalProgress : fileSizeBytes;
svc.recordMeetingObjectStorage( svc.recordMeetingObjectStorage(
meetingId: element['id'], meetingId: element['id'],
storageUrl: audiourl, storageUrl: audiourl,
@ -119,31 +116,16 @@ class MeetingUploadService extends GetxService {
uploadTimestamp: DateTime.now().millisecondsSinceEpoch, uploadTimestamp: DateTime.now().millisecondsSinceEpoch,
); );
} catch (e) { } catch (e) {
Logger.w('MeetingOSrecord', '对象存储用量记录失败: ' + e.toString()); Logger.w('MeetingOSrecord', '对象存储用量记录失败: $e');
} }
if (element['isTask']) { if (element['isTask']) {
if (element['useFlashASR'] == true) { await _taskService.submitTask(
final service2 = Get.find<MeetingTaskService2>(); element['id'],
await service2.addTaskList( element['formlanguage'],
element['id'], element['tolanguage'],
audiourl, element['isdistinguishspeaker'],
isSpeaker: element['isSpeaker'], element['templateid'],
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; element['audiourl'] = audiourl;
uploadProgress.value = 0.0; uploadProgress.value = 0.0;

187
lib/data/services/meeting/summary_task_service.dart

@ -1,187 +0,0 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
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<MeetingTaskService>();
final _taskService2 = Get.find<MeetingTaskService2>();
final GetStorage _storage = GetStorage();
List _taskList = [];
bool _isTask = false;
String summary = ''; //AI总结内容
String overview = ''; //AI概览内容
@override
void onInit() {
super.onInit();
_getTaskList();
}
void cancelTask() {
_taskList.clear();
_storage.remove("summary_task_list");
}
void _getTaskList() {
_taskList = _storage.read("summary_task_list") ?? [];
_executeTask();
}
Future<void> addTaskList(
int id, String text, String prompt, String outline) async {
Logger.debug("添加总结任务列表");
_taskList.add({
'id': id,
'text': text,
'prompt': prompt,
'outline': outline,
});
_storage.write("summary_task_list", _taskList);
_executeTask();
}
void _executeTask() async {
if (_taskList.isEmpty) return;
if (_isTask) return;
_isTask = true;
var task = _taskList.first;
_aiServiceMessage(
task['id'],
task['text'],
task['prompt'],
task['outline'],
);
Logger.debug("执行总结任务列表");
}
void removeTask(int id) {
_taskList.removeWhere((element) => element['id'] == id);
_storage.write("summary_task_list", _taskList);
}
void _aiServiceMessage(int id, String text, String prompt, String outline) {
final ai = DeapsoundAIService();
String summaryText = '';
String overviewText = '';
var summaryDone = false;
var overviewDone = false;
var summarySuccess = false;
var overviewSuccess = false;
void tryFinish() {
if (summaryDone && overviewDone) {
if (summarySuccess &&
overviewSuccess &&
summaryText.isNotEmpty &&
overviewText.isNotEmpty) {
_aiServiceValue(id, summaryText, overviewText);
} else {
_isTask = false;
_executeTask();
}
}
}
final summaryStream = ai.sendMessageStream(messages: [
{'role': 'system', 'content': prompt},
{'role': 'user', 'content': text},
]).timeout(const Duration(seconds: 120));
final overviewStream = ai.sendMessageStream(messages: [
{'role': 'system', 'content': outline},
{'role': 'user', 'content': text},
]).timeout(const Duration(seconds: 120));
summaryStream.listen((chunk) {
summaryText += chunk;
}, onError: (error) {
summaryDone = true;
summarySuccess = false;
tryFinish();
}, onDone: () {
summaryDone = true;
summarySuccess = true;
tryFinish();
}, cancelOnError: true);
overviewStream.listen((chunk) {
overviewText += chunk;
}, onError: (error) {
overviewDone = true;
overviewSuccess = false;
tryFinish();
}, onDone: () {
overviewDone = true;
overviewSuccess = true;
tryFinish();
}, cancelOnError: true);
}
Future<void> _aiServiceValue(
int id, String summaryText, String overviewText) async {
final exists = _taskList.any((item) => item['id'] == id);
if (!exists) {
_isTask = false;
_executeTask();
return;
}
await SqfliteApi.editMeeting(id, {
'tasktype': 3,
'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: () => {},
);
if (taskFinishData.isNotEmpty) {
taskFinishData['tasktype'] = 3;
_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;
_executeTask();
Logger.debug("总结任务完成");
}
}

59
lib/data/services/network/api.dart

@ -119,7 +119,16 @@ class Api {
} }
// 获取会议助手数据 // 获取会议助手数据
static getOperationRecord([params]) { static getOperationAllRecord([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_getallrecords',
params: params,
);
}
// 轮询多条会议助手转写结果
static getMultitermRecord([params]) {
return DioManager().request( return DioManager().request(
NWMethod.post, NWMethod.post,
'/api/home/echomeet_getrecords', '/api/home/echomeet_getrecords',
@ -291,4 +300,52 @@ class Api {
params: params, params: params,
); );
} }
static addRecordEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_addrecord',
params: params,
);
}
static upRecordEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_uprecord',
params: params,
);
}
static startTaskEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_starttask',
params: params,
);
}
static refreshTaskEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_summary',
params: params,
);
}
static readRecordEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_readrecord',
params: params,
);
}
static delRecordEchomeet([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_delrecords',
params: params,
);
}
} }

122
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -9,16 +9,12 @@ import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import '../../../core/utils/synchrodata.dart';
import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/meeting/meeting_task_service.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/meeting_upload_service.dart';
import '../../../data/services/meeting/summary_task_service.dart'; import '../../../data/services/network/api.dart';
import '../../../data/services/meeting/meeting_task_aggregator.dart';
import '../model/meeting_details_model.dart'; import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart'; import '../model/meeting_model.dart';
@ -31,12 +27,9 @@ class MeetingDetailsController extends GetxController
late AnimationController lottieController; late AnimationController lottieController;
late final Worker _tasktypeListener; late final Worker _tasktypeListener;
late final Worker _tasktypeListenerV2;
final _meetingUploadService = Get.find<MeetingUploadService>(); final _meetingUploadService = Get.find<MeetingUploadService>();
final _meetingTaskService = Get.find<MeetingTaskService>(); final _meetingTaskService = Get.find<MeetingTaskService>();
final _meetingTaskService2 = Get.find<MeetingTaskService2>();
final _summaryTaskService = Get.find<SummaryTaskService>();
final PlayerController playerController = PlayerController(); final PlayerController playerController = PlayerController();
@ -95,39 +88,19 @@ class MeetingDetailsController extends GetxController
..duration = const Duration(seconds: 2); ..duration = const Duration(seconds: 2);
final Map<String, dynamic> args = Get.arguments ?? {}; final Map<String, dynamic> args = Get.arguments ?? {};
id = args['id']; id = args['id'];
final storage = GetStorage();
final String svc =
(storage.read("meeting_service_type_$id") ?? '').toString();
final bool isFast = svc == 'fast';
_meetingTaskService.meetingId = id; _meetingTaskService.meetingId = id;
_meetingTaskService2.meetingId = id;
Get.find<MeetingTaskAggregator>().setMeeting(id);
_getDataDetails(); _getDataDetails();
_loadJS(); _loadJS();
_tasktypeListener = ever(_meetingTaskService.tasktype, (value) { _tasktypeListener = ever(_meetingTaskService.tasktype, (value) {
if (isFast && value != 3) return;
meetingDetails.value.tasktype = value; meetingDetails.value.tasktype = value;
if (_everType <= value) { if (_everType <= value) {
if (!isFast && value == 2) { if (value >= 5) {
textList.value = _meetingTaskService.textList; meetingDetails.value.personnel = _meetingTaskService.personnel;
} else if (value == 3) { meetingDetails.value.summary = _meetingTaskService.summary;
meetingDetails.value.summary = _summaryTaskService.summary; meetingDetails.value.overview = _meetingTaskService.overview;
meetingDetails.value.overview = _summaryTaskService.overview;
summaryNumberModifications.value++;
}
}
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 = _summaryTaskService.summary;
meetingDetails.value.overview = _summaryTaskService.overview;
summaryNumberModifications.value++; summaryNumberModifications.value++;
} else if (value >= 3) {
textList.value = _meetingTaskService.textList;
} }
} }
meetingDetails.refresh(); meetingDetails.refresh();
@ -137,22 +110,17 @@ class MeetingDetailsController extends GetxController
@override @override
void onClose() { void onClose() {
_tasktypeListener.dispose(); _tasktypeListener.dispose();
_tasktypeListenerV2.dispose();
_currentDurationSubscription?.cancel(); _currentDurationSubscription?.cancel();
_playerStateSubscription?.cancel(); _playerStateSubscription?.cancel();
tabController.dispose(); tabController.dispose();
playerController.dispose(); playerController.dispose();
lottieController.dispose(); lottieController.dispose();
Get.find<MeetingTaskAggregator>().clearMeeting();
super.onClose(); super.onClose();
} }
void popInvokedWithResult(didPop, result) async { void popInvokedWithResult(didPop, result) async {
_meetingUploadService.removeUpload(id); _meetingUploadService.removeUpload(id);
_meetingTaskService.removeTask(id);
_meetingTaskService2.removeTask(id);
_meetingTaskService.meetingId = 0; _meetingTaskService.meetingId = 0;
_meetingTaskService2.meetingId = 0;
} }
void _getDataDetails() async { void _getDataDetails() async {
@ -161,14 +129,17 @@ class MeetingDetailsController extends GetxController
meetingDetails.value = MeetingDetailsModel.fromJson(data['details']); meetingDetails.value = MeetingDetailsModel.fromJson(data['details']);
_everType = meetingDetails.value.tasktype + 1; _everType = meetingDetails.value.tasktype + 1;
_meetingTaskService.tasktype.value = meetingDetails.value.tasktype; _meetingTaskService.tasktype.value = meetingDetails.value.tasktype;
if (meetingDetails.value.tasktype >= 3) { if (meetingDetails.value.tasktype == 5) {
await _meetingTaskService.readTask(id);
}
if (meetingDetails.value.tasktype >= 5) {
dragOffset.value = 0; dragOffset.value = 0;
isTop.value = true; isTop.value = true;
} }
if (meetingData.value.filepath.isNotEmpty) { if (meetingData.value.filepath.isNotEmpty) {
await _loadPlayerController(); await _loadPlayerController();
} }
if (meetingDetails.value.tasktype >= 2) { if (meetingDetails.value.tasktype >= 3) {
List speakerList = await SqfliteApi.getMeetingSpeaker(id); List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList); textList.value = List.from(speakerList);
} }
@ -267,10 +238,6 @@ class MeetingDetailsController extends GetxController
textList[index] = dataMap; textList[index] = dataMap;
} }
} }
Synchrodata.saveOperationRecord('update', {
'original': jsonEncode(textList),
'id': id,
});
isSpeakerText.value = false; isSpeakerText.value = false;
editSpeakerTextId.value = 0; editSpeakerTextId.value = 0;
editSpeakerTextList = []; editSpeakerTextList = [];
@ -284,64 +251,33 @@ class MeetingDetailsController extends GetxController
} }
void summaryCreate( void summaryCreate(
bool isSpeaker, String language, String prompt, String outline) async { String formlanguage,
String tolanguage,
bool isdistinguishspeaker,
int templateid,
) async {
EasyLoading.show(); EasyLoading.show();
_everType = 2; _everType = 2;
final String audiourl = meetingData.value.audiourl; final String audiourl = meetingData.value.audiourl;
final int seconds = meetingData.value.seconds; await _meetingTaskService.addTask(
final storage = GetStorage(); id,
storage.write("meeting_service_type_$id", audiourl,
(seconds > 0 && seconds <= 7200) ? 'fast' : 'standard'); formlanguage,
if (seconds > 0 && seconds <= 7200) { tolanguage,
await _meetingTaskService2.addTask( isdistinguishspeaker,
id, templateid,
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.value.tasktype = 1;
meetingDetails.refresh(); meetingDetails.refresh();
EasyLoading.dismiss(); EasyLoading.dismiss();
} }
void refreshSummary(String prompt, String outline) async { void refreshSummary(int templateid) async {
String text = '';
for (var element in textList) {
if (element['speaker'].isNotEmpty) {
text += '[${element['speaker']}]: ';
}
text += '${element['content']}\n';
}
_everType = 3;
EasyLoading.show(); EasyLoading.show();
await SqfliteApi.editMeeting(id, {'tasktype': 2}); _everType = 4;
meetingDetails.value.tasktype = 2; await _meetingTaskService.refreshTask(id, templateid);
meetingDetails.value.tasktype = 3;
meetingDetails.refresh(); 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(); EasyLoading.dismiss();
} }
} }

78
lib/modules/meeting/controllers/meeting_home_controller.dart

@ -19,10 +19,8 @@ import '../../../core/utils/permission_util.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../data/services/meeting/meeting_task_service.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/meeting_upload_service.dart';
import '../../../data/services/meeting/summary_task_service.dart'; import '../../../data/services/network/api.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
import 'meeting_controller.dart'; import 'meeting_controller.dart';
@ -31,9 +29,6 @@ class MeetingHomeController extends GetxController {
final meetingController = Get.find<MeetingController>(); final meetingController = Get.find<MeetingController>();
final meetingUploadService = Get.find<MeetingUploadService>(); final meetingUploadService = Get.find<MeetingUploadService>();
final meetingTaskService = Get.find<MeetingTaskService>(); final meetingTaskService = Get.find<MeetingTaskService>();
final meetingTaskService2 = Get.find<MeetingTaskService2>();
final taskAggregator = Get.find<MeetingTaskAggregator>();
final summaryTaskService = Get.find<SummaryTaskService>();
final AudioPlayer _audioPlayer = AudioPlayer(); final AudioPlayer _audioPlayer = AudioPlayer();
@ -53,6 +48,8 @@ class MeetingHomeController extends GetxController {
// 蓝牙服务 // 蓝牙服务
final bleManager = Get.find<BleManager>(); final bleManager = Get.find<BleManager>();
late final Worker _updateListStateListener;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -68,6 +65,10 @@ class MeetingHomeController extends GetxController {
final String path = args['filePath']; final String path = args['filePath'];
addAudio(path); addAudio(path);
} }
_updateListStateListener =
ever(meetingTaskService.updateListState, (value) async {
_getDataList();
});
} }
@override @override
@ -75,11 +76,13 @@ class MeetingHomeController extends GetxController {
titleController.dispose(); titleController.dispose();
filtrateController.dispose(); filtrateController.dispose();
_audioPlayer.dispose(); _audioPlayer.dispose();
_updateListStateListener.dispose();
super.onClose(); super.onClose();
} }
void onPopInvokedWithResult(didPop, result) { void onPopInvokedWithResult(didPop, result) {
Synchrodata.uploadOperationRecords(isSynchro: true); // Synchrodata.uploadOperationRecords(isSynchro: true);
meetingTaskService.stopTask();
} }
void _initGetDataList() async { void _initGetDataList() async {
@ -246,8 +249,16 @@ class MeetingHomeController extends GetxController {
}; };
// 插入数据库 // 插入数据库
final meetingId = await SqfliteApi.insertMeeting(data); final response = await Api.addRecordEchomeet({
if (meetingId != null) { 'rtype': 'LOCAL',
...data,
});
final meetingId = response['record']['id'];
final meetingResult = await SqfliteApi.insertMeeting({
'id': meetingId,
...data,
});
if (meetingResult != null) {
// 安排后台上传任务 // 安排后台上传任务
meetingUploadService.addUpload(meetingId, newPath, 'LocalAudio'); meetingUploadService.addUpload(meetingId, newPath, 'LocalAudio');
@ -359,8 +370,16 @@ class MeetingHomeController extends GetxController {
'creationtime': creationTime, 'creationtime': creationTime,
}; };
final meetingId = await SqfliteApi.insertMeeting(data); final response = await Api.addRecordEchomeet({
if (meetingId != null) { 'rtype': recordType,
...data,
});
final meetingId = response['record']['id'];
final meetingResult = await SqfliteApi.insertMeeting({
'id': meetingId,
...data,
});
if (meetingResult != null) {
// 中文注释:上传分类按目录区分 // 中文注释:上传分类按目录区分
final uploadBucket = archiveToLocal ? 'LocalAudio' : 'TranslatAudio'; final uploadBucket = archiveToLocal ? 'LocalAudio' : 'TranslatAudio';
meetingUploadService.addUpload(meetingId, usePath, uploadBucket); meetingUploadService.addUpload(meetingId, usePath, uploadBucket);
@ -475,8 +494,13 @@ class MeetingHomeController extends GetxController {
'tasktype': 0, 'tasktype': 0,
'creationtime': creationTime, 'creationtime': creationTime,
}; };
final meetingId = await SqfliteApi.insertMeeting(data); final response = await Api.addRecordEchomeet({'rtype': type, ...data});
if (meetingId != null) { final meetingId = response['record']['id'];
final meetingResult = await SqfliteApi.insertMeeting({
'id': meetingId,
...data,
});
if (meetingResult != null) {
// 中文注释:根据类型选择上传分类;LOCAL 走 LocalAudio,其它走 ExternalAudio // 中文注释:根据类型选择上传分类;LOCAL 走 LocalAudio,其它走 ExternalAudio
final uploadBucket = type == 'LOCAL' ? 'LocalAudio' : 'ExternalAudio'; final uploadBucket = type == 'LOCAL' ? 'LocalAudio' : 'ExternalAudio';
meetingUploadService.addUpload( meetingUploadService.addUpload(
@ -544,19 +568,18 @@ class MeetingHomeController extends GetxController {
return targetPath; return targetPath;
} }
// 移除原来的_saveFile方法,替换为上面的_copyFileToLocal // 移除原来的_saveFile方法,替换为上面的_copyFileToLocal
// Future<String> _saveFile(PlatformFile file) async {
Future<String> _saveFile(PlatformFile file) async { // final directory = await getApplicationDocumentsDirectory();
final directory = await getApplicationDocumentsDirectory(); // final directoryDir = Directory('${directory.path}/ExternalAudio');
final directoryDir = Directory('${directory.path}/ExternalAudio'); // if (!await directoryDir.exists()) {
if (!await directoryDir.exists()) { // await directoryDir.create(recursive: true);
await directoryDir.create(recursive: true); // }
} // final String filePath = '${directory.path}/ExternalAudio/${file.name}';
final String filePath = '${directory.path}/ExternalAudio/${file.name}'; // final File localFile = File(filePath);
final File localFile = File(filePath); // await localFile.writeAsBytes(file.bytes!);
await localFile.writeAsBytes(file.bytes!); // return filePath;
return filePath; // }
}
// 修改标题 // 修改标题
void editMeetingTitle(int id) async { void editMeetingTitle(int id) async {
@ -589,8 +612,7 @@ class MeetingHomeController extends GetxController {
SqfliteApi.deleteMeeting(id); SqfliteApi.deleteMeeting(id);
_deleteFile(filepath); _deleteFile(filepath);
meetingUploadService.removeUpload(id, isDeleteTask: true); meetingUploadService.removeUpload(id, isDeleteTask: true);
meetingTaskService.removeTask(id, isDeleteTask: true); meetingTaskService.removeTask(id);
meetingTaskService2.removeTask(id, isDeleteTask: true);
} }
// 删除文件 // 删除文件

48
lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart

@ -26,8 +26,10 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
bool _isSpeaker = true; bool _isSpeaker = true;
String _languageText = 'chineseSimplified'.tr; // 对应中文:中文普通话(简体) String _formlanguageText = 'chineseSimplified'.tr; // 对应中文:中文普通话(简体)
String _language = 'zh-CN'; String _formlanguage = 'zh-CN';
String _tolanguageText = 'chineseSimplified'.tr; // 对应中文:中文普通话(简体)
String _tolanguage = 'zh-CN';
final Map _icons = { final Map _icons = {
'auto_stories': Icons.auto_stories, 'auto_stories': Icons.auto_stories,
@ -273,11 +275,34 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
), ),
_row( _row(
'recordingLanguage'.tr, // 对应中文:录音语言 'recordingLanguage'.tr, // 对应中文:录音语言
value: _languageText, value: _formlanguageText,
onTap: () {
Get.bottomSheet(
RecordLanguageBottomSheet(_formlanguage),
isScrollControlled: true,
backgroundColor: Colors.grey[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
).then((value) {
if (value != null) {
setState(() {
_formlanguageText = value['name'];
_formlanguage = value['value'];
});
}
});
},
),
_row(
'targetLanguage'.tr, // 对应中文:目标语言
value: _tolanguageText,
isBorder: false, isBorder: false,
onTap: () { onTap: () {
Get.bottomSheet( Get.bottomSheet(
RecordLanguageBottomSheet(_language), RecordLanguageBottomSheet(_tolanguage),
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -288,8 +313,8 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
setState(() { setState(() {
_languageText = value['name']; _tolanguageText = value['name'];
_language = value['value']; _tolanguage = value['value'];
}); });
} }
}); });
@ -318,16 +343,13 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
_storage.write("meeting_template_list", _templateList); _storage.write("meeting_template_list", _templateList);
Get.back(); Get.back();
if (widget.isSummarizeAgain) { if (widget.isSummarizeAgain) {
_controller.refreshSummary( _controller.refreshSummary(_templateData['id']);
_templateData['prompt'],
_templateData['outline'],
);
} else { } else {
_controller.summaryCreate( _controller.summaryCreate(
_formlanguage,
_tolanguage,
_isSpeaker, _isSpeaker,
_language, _templateData['id'],
_templateData['prompt'],
_templateData['outline'],
); );
} }
}, },

2
lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart

@ -26,7 +26,7 @@ class _MoreBottomSheetState extends State<MoreBottomSheet> {
children: [ children: [
SizedBox(height: 10.w), SizedBox(height: 10.w),
_shareItem(Icons.refresh, 'reSummarize'.tr, //重新总结 _shareItem(Icons.refresh, 'reSummarize'.tr, //重新总结
onTap: _detailsController.meetingDetails.value.tasktype >= 3 onTap: _detailsController.meetingDetails.value.tasktype >= 5
? () { ? () {
Get.back(); Get.back();
Get.bottomSheet( Get.bottomSheet(

23
lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart

@ -13,7 +13,6 @@ import 'package:share_plus/share_plus.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import '../../../../core/utils/synchrodata.dart';
import '../../../../data/services/version_update_service.dart'; import '../../../../data/services/version_update_service.dart';
import '../../../../data/services/network/dio_manager.dart'; import '../../../../data/services/network/dio_manager.dart';
import '../../controllers/meeting_details_controller.dart'; import '../../controllers/meeting_details_controller.dart';
@ -86,17 +85,9 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
isBorder: false, isBorder: false,
onTap: () async { onTap: () async {
Get.back(); Get.back();
Synchrodata.uploadOperationRecords();
final GetStorage storage = GetStorage(); final GetStorage storage = GetStorage();
String token = storage.read("logintoken") ?? ''; String token = storage.read("logintoken") ?? '';
String userId = ''; int id = _controller.meetingData.value.id;
Map? userInfo = storage.read("user_info");
if (userInfo != null) {
userId = userInfo['user']['uid'];
}
String id =
'${userId}_${_controller.meetingData.value.id}';
// final thumbnail = await getAssetThumbnailFile( // final thumbnail = await getAssetThumbnailFile(
// 'assets/images/mine-background.png', // 'assets/images/mine-background.png',
@ -126,7 +117,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
]), ]),
_box([ _box([
_shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写 _shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写
onTap: _controller.meetingDetails.value.tasktype >= 2 onTap: _controller.meetingDetails.value.tasktype >= 3
? () { ? () {
Get.back(); Get.back();
_copy(_getAllText()); _copy(_getAllText());
@ -134,7 +125,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
: null), : null),
_shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结 _shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结
isBorder: false, isBorder: false,
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 5
? () { ? () {
Get.back(); Get.back();
_copy(_markdownToPlainText( _copy(_markdownToPlainText(
@ -151,7 +142,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
'shareMeetingAudio'.tr); // 对应中文:分享会议音频 'shareMeetingAudio'.tr); // 对应中文:分享会议音频
}), }),
_shareItem(Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写 _shareItem(Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写
onTap: _controller.meetingDetails.value.tasktype >= 2 onTap: _controller.meetingDetails.value.tasktype >= 3
? () async { ? () async {
Get.back(); Get.back();
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
@ -164,7 +155,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
} }
: null), : null),
_shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结 _shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 5
? () async { ? () async {
Get.back(); Get.back();
Get.bottomSheet( Get.bottomSheet(
@ -214,7 +205,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
_shareItem( _shareItem(
Icons.save_alt, Icons.save_alt,
'保存思维导图', '保存思维导图',
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 5
? () { ? () {
Get.back(); Get.back();
final mindMapController = final mindMapController =
@ -242,7 +233,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
Icons.lan, Icons.lan,
'shareMindMap'.tr, // 对应中文:分享思维导图 'shareMindMap'.tr, // 对应中文:分享思维导图
isBorder: false, isBorder: false,
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 5
? () { ? () {
Get.back(); Get.back();
final mindMapController = final mindMapController =

5
lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart

@ -8,7 +8,6 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import '../../../../core/utils/synchrodata.dart';
import '../../../../data/services/db/sqflite_api.dart'; import '../../../../data/services/db/sqflite_api.dart';
import '../../controllers/meeting_details_controller.dart'; import '../../controllers/meeting_details_controller.dart';
@ -560,10 +559,6 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
bool isEditSummary = false; bool isEditSummary = false;
String summaryText = _controller.meetingDetails.value.summary; String summaryText = _controller.meetingDetails.value.summary;
_controller.textList.value = newTextList; _controller.textList.value = newTextList;
Synchrodata.saveOperationRecord('update', {
'original': jsonEncode(_controller.textList),
'id': _controller.meetingData.value.id,
});
for (var element in personnelList) { for (var element in personnelList) {
if (summaryText.contains('[${element['original']}]')) { if (summaryText.contains('[${element['original']}]')) {
summaryText = summaryText.replaceAll( summaryText = summaryText.replaceAll(

9
lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart

@ -6,7 +6,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import '../../../../core/utils/synchrodata.dart';
import '../../../../data/services/db/sqflite_api.dart'; import '../../../../data/services/db/sqflite_api.dart';
import '../../controllers/meeting_details_controller.dart'; import '../../controllers/meeting_details_controller.dart';
@ -337,10 +336,6 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
dataMap['speaker'] = _speakerController.text; dataMap['speaker'] = _speakerController.text;
_controller.textList[index] = dataMap; _controller.textList[index] = dataMap;
} }
Synchrodata.saveOperationRecord('update', {
'original': jsonEncode(_controller.textList),
'id': _controller.meetingData.value.id,
});
_addHistorySpeaker(); _addHistorySpeaker();
Get.back(); Get.back();
} }
@ -371,10 +366,6 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList); var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList);
if (result != null) { if (result != null) {
_controller.textList.value = newTextList; _controller.textList.value = newTextList;
Synchrodata.saveOperationRecord('update', {
'original': jsonEncode(_controller.textList),
'id': _controller.meetingData.value.id,
});
if (_controller.meetingDetails.value.summary if (_controller.meetingDetails.value.summary
.contains('[${widget.speakerName}]')) { .contains('[${widget.speakerName}]')) {
String summaryText = _controller.meetingDetails.value.summary String summaryText = _controller.meetingDetails.value.summary

44
lib/modules/meeting/views/meeting_details_view.dart

@ -331,35 +331,43 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
Obx( Obx(
() => controller.isLoading.value () => controller.isLoading.value
? const SizedBox() ? const SizedBox()
: controller.meetingDetails.value.tasktype == 1 || : controller.meetingDetails.value.tasktype > 0 &&
controller.meetingDetails.value.tasktype == 2 controller.meetingDetails.value.tasktype < 5
? _meetingWait('analyzing'.tr) // 对应中文:正在分析... ? _meetingWait(
controller.meetingDetails.value.tasktype > 0 &&
controller.meetingDetails.value.tasktype <
3
? 'transcribing'.tr // 对应中文:正在转写...
: 'analyzing'.tr) // 对应中文:正在分析...
: const OverviewTab(), : const OverviewTab(),
), ),
Obx( Obx(
() => controller.meetingDetails.value.tasktype == 1 () => controller.meetingDetails.value.tasktype > 0 &&
controller.meetingDetails.value.tasktype < 3
? _meetingWait('transcribing'.tr) // 对应中文:正在转写... ? _meetingWait('transcribing'.tr) // 对应中文:正在转写...
: const SpeechTab(), : const SpeechTab(),
), ),
Obx( Obx(
() => controller.meetingDetails.value.tasktype == 1 || () => controller.meetingDetails.value.tasktype > 0 &&
controller.meetingDetails.value.tasktype == 2 controller.meetingDetails.value.tasktype < 5
? _meetingWait( ? _meetingWait(
controller.meetingDetails.value.tasktype == 1 controller.meetingDetails.value.tasktype > 0 &&
controller.meetingDetails.value.tasktype < 3
? 'transcribing'.tr // 对应中文:正在转写... ? 'transcribing'.tr // 对应中文:正在转写...
: 'summarizing'.tr) // 对应中文:正在总结... : 'analyzing'.tr) // 对应中文:正在分析...
: const SummaryTab(), : const SummaryTab(),
), ),
Obx( Obx(() => controller.meetingDetails.value.tasktype >= 5
() => controller.meetingDetails.value.tasktype >= 3 ? const MindMapTab()
? const MindMapTab() : controller.meetingDetails.value.tasktype == 0
: controller.meetingDetails.value.tasktype == 0 ? const SizedBox()
? const SizedBox() : _meetingWait(controller
: _meetingWait( .meetingDetails.value.tasktype >
controller.meetingDetails.value.tasktype == 1 0 &&
? 'transcribing'.tr // 对应中文:正在转写... controller.meetingDetails.value.tasktype < 3
: 'summarizing'.tr), // 对应中文:正在总结... ? 'transcribing'.tr // 对应中文:正在转写...
), : 'analyzing'.tr) // 对应中文:正在分析...
),
], ],
), ),
), ),

57
lib/modules/meeting/views/meeting_home_view.dart

@ -247,6 +247,30 @@ class _MeetingHomeViewState extends State<MeetingHomeView>
} }
Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) { Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) {
String tasktypeText = '';
switch (data.tasktype) {
case 0:
tasktypeText = '';
break;
case 1:
tasktypeText = 'transcribing'.tr; // 对应中文:正在转写...
break;
case 2:
tasktypeText = 'transcribing'.tr; // 对应中文:正在转写...
break;
case 3:
tasktypeText = 'summarizing'.tr; // 对应中文:正在总结...
break;
case 4:
tasktypeText = 'summarizing'.tr; // 对应中文:正在总结...
break;
case 5:
tasktypeText = 'summaryComplete'.tr; // 对应中文:总结完成
break;
default:
tasktypeText = '';
}
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Get.toNamed(Routes.meetingDetails, arguments: { Get.toNamed(Routes.meetingDetails, arguments: {
@ -374,29 +398,16 @@ class _MeetingHomeViewState extends State<MeetingHomeView>
) )
: const SizedBox(); : const SizedBox();
}), }),
Obx(() { Padding(
Map taskData = padding: EdgeInsets.only(top: 5.w),
_controller.taskAggregator.taskFinishList.firstWhere( child: Text(
(element) => element['id'] == data.id, tasktypeText,
orElse: () => {}, style: TextStyle(
); color: Colors.blue,
return taskData.isNotEmpty fontSize: 10.sp,
? Padding( ),
padding: EdgeInsets.only(top: 5.w), ),
child: Text( ),
taskData['tasktype'] == 1
? 'transcribing'.tr // 对应中文:正在转写...
: taskData['tasktype'] == 2
? 'summarizing'.tr // 对应中文:正在总结...
: 'summaryComplete'.tr, // 对应中文:总结完成
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
),
)
: const SizedBox();
}),
], ],
), ),
], ],

8
lib/modules/splash/controllers/splash_controller.dart

@ -10,9 +10,6 @@ import 'package:http/http.dart' as http;
import '../../../data/models/appconfig.dart'; import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart'; import '../../../data/models/appconfig_model.dart';
import '../../../data/models/user_Info.dart'; import '../../../data/models/user_Info.dart';
import '../../../data/services/meeting/meeting_task_service.dart';
import '../../../data/services/meeting/meeting_upload_service.dart';
import '../../../data/services/meeting/summary_task_service.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
import '../../../data/services/network/api.dart'; import '../../../data/services/network/api.dart';
import '../../../data/services/version_update_service.dart'; import '../../../data/services/version_update_service.dart';
@ -269,13 +266,8 @@ class SplashController extends GetxController {
final isAppConfigInitialized = AppConfig.isInitialized(); final isAppConfigInitialized = AppConfig.isInitialized();
// 检查登录状态和配置初始化状态 // 检查登录状态和配置初始化状态
if (token != null && token.isNotEmpty && isAppConfigInitialized) { if (token != null && token.isNotEmpty && isAppConfigInitialized) {
Get.find<MeetingUploadService>();
Get.find<MeetingTaskService>();
Get.find<SummaryTaskService>();
Get.offAllNamed(Routes.pairing); Get.offAllNamed(Routes.pairing);
} else { } else {
_storage.remove('meeting_task_list');
_storage.remove('meeting_task_finish_list');
_storage.remove('meeting_upload_list'); _storage.remove('meeting_upload_list');
_storage.remove('operation_records'); _storage.remove('operation_records');
_storage.remove('operation_delids'); _storage.remove('operation_delids');

Loading…
Cancel
Save