You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
10 KiB
362 lines
10 KiB
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:audio_waveforms/audio_waveforms.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_easyloading/flutter_easyloading.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/upload_oss.dart';
|
|
import '../../../data/models/appconfig.dart';
|
|
import '../../../data/services/db/sqflite_api.dart';
|
|
import '../../../data/services/deapsound_ai_service.dart';
|
|
import '../model/meeting_details_model.dart';
|
|
import '../model/meeting_model.dart';
|
|
|
|
class MeetingDetailsController extends GetxController
|
|
with GetSingleTickerProviderStateMixin {
|
|
late ScrollController scrollController;
|
|
late TabController tabController;
|
|
final RxInt currentIndex = 0.obs;
|
|
|
|
final PlayerController playerController = PlayerController();
|
|
RxBool isTop = false.obs;
|
|
RxInt durationMax = 0.obs;
|
|
|
|
int id = 0;
|
|
|
|
RxBool isLoading = true.obs;
|
|
Rx<MeetingModel> meetingData = MeetingModel().obs;
|
|
Rx<MeetingDetailsModel> meetingDetails = MeetingDetailsModel().obs;
|
|
RxList textList = [].obs;
|
|
String allText = '';
|
|
RxBool isSpeakerText = false.obs;
|
|
List editSpeakerTextList = [];
|
|
RxInt editSpeakerTextId = 0.obs;
|
|
String editSpeakerTextChange = '';
|
|
|
|
String _systemPrompt = '';
|
|
String _userId = '';
|
|
|
|
bool _isDisposed = false;
|
|
|
|
final String _appid = '2316081933';
|
|
final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En';
|
|
final String _cluster = 'volc_auc_meeting_flash';
|
|
final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc';
|
|
Map<String, String> _headers = {};
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
scrollController = ScrollController(initialScrollOffset: 0.0);
|
|
scrollController.addListener(_onScroll);
|
|
tabController = TabController(length: 4, vsync: this);
|
|
tabController.addListener(() {
|
|
currentIndex.value = tabController.index;
|
|
});
|
|
final Map<String, dynamic> args = Get.arguments ?? {};
|
|
id = args['id'];
|
|
_headers = {
|
|
'Authorization': 'Bearer; $_token',
|
|
'Content-Type': 'application/json',
|
|
};
|
|
_getAgent();
|
|
_getDataDetails();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
scrollController.dispose();
|
|
tabController.dispose();
|
|
playerController.dispose();
|
|
_isDisposed = true;
|
|
super.onClose();
|
|
}
|
|
|
|
void _onScroll() async {
|
|
double pixels = scrollController.position.pixels;
|
|
double maxScrollExtent = scrollController.position.maxScrollExtent;
|
|
if (!isTop.value && pixels > 5) {
|
|
await scrollController.animateTo(
|
|
maxScrollExtent,
|
|
duration: const Duration(milliseconds: 100),
|
|
curve: Curves.easeOut,
|
|
);
|
|
if (pixels > maxScrollExtent - 2) {
|
|
isTop.value = true;
|
|
}
|
|
}
|
|
if (isTop.value && pixels < maxScrollExtent - 5) {
|
|
await scrollController.animateTo(
|
|
0,
|
|
duration: const Duration(milliseconds: 100),
|
|
curve: Curves.easeOut,
|
|
);
|
|
if (pixels < 2) {
|
|
isTop.value = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _getAgent() async {
|
|
final GetStorage storage = GetStorage();
|
|
Map userInfo = storage.read("user_info");
|
|
_userId = userInfo['user']['uid'];
|
|
_systemPrompt = AppConfig.agentsystemPrompt("meet_assistant") ?? '';
|
|
}
|
|
|
|
void _getDataDetails() async {
|
|
final data = await SqfliteApi.getMeetingDetails(id);
|
|
meetingData.value = MeetingModel.fromJson(data);
|
|
meetingDetails.value = MeetingDetailsModel.fromJson(data['details']);
|
|
await playerController.preparePlayer(
|
|
path: meetingData.value.filepath,
|
|
noOfSamples: 1200,
|
|
);
|
|
durationMax.value = await playerController.getDuration(DurationType.max);
|
|
playerController.setFinishMode(finishMode: FinishMode.pause);
|
|
if (meetingDetails.value.tasktype == 1) {
|
|
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
|
|
textList.value = List.from(speakerList);
|
|
}
|
|
isLoading.value = false;
|
|
}
|
|
|
|
void editSpeakerText() async {
|
|
if (editSpeakerTextId.value != 0 && editSpeakerTextChange.isNotEmpty) {
|
|
Map? foundElement = editSpeakerTextList.firstWhere(
|
|
(element) => element['id'] == editSpeakerTextId.value,
|
|
orElse: () => null,
|
|
);
|
|
if (foundElement != null) {
|
|
foundElement['content'] = editSpeakerTextChange;
|
|
} else {
|
|
editSpeakerTextList.add({
|
|
'id': editSpeakerTextId.value,
|
|
'content': editSpeakerTextChange,
|
|
});
|
|
}
|
|
}
|
|
if (editSpeakerTextList.isNotEmpty) {
|
|
EasyLoading.show();
|
|
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerTextList);
|
|
if (result != null) {
|
|
for (var element in editSpeakerTextList) {
|
|
int index =
|
|
textList.indexWhere((item) => item['id'] == element['id']);
|
|
if (index != -1) {
|
|
final dataMap = Map<String, dynamic>.from(textList[index]);
|
|
dataMap['content'] = element['content'];
|
|
textList[index] = dataMap;
|
|
}
|
|
}
|
|
isSpeakerText.value = false;
|
|
editSpeakerTextId.value = 0;
|
|
editSpeakerTextList = [];
|
|
}
|
|
EasyLoading.dismiss();
|
|
} else {
|
|
isSpeakerText.value = false;
|
|
editSpeakerTextId.value = 0;
|
|
editSpeakerTextList = [];
|
|
}
|
|
}
|
|
|
|
void speakerCreate() async {
|
|
EasyLoading.show();
|
|
await _audioSpeaker();
|
|
EasyLoading.dismiss();
|
|
}
|
|
|
|
void summaryCreate() async {
|
|
EasyLoading.show();
|
|
if (meetingDetails.value.tasktype != 1) {
|
|
await _audioSpeaker();
|
|
}
|
|
if (allText.isEmpty) {
|
|
String text = '';
|
|
for (var element in textList) {
|
|
if (element['speaker'].isNotEmpty) {
|
|
text += '${element['speaker']}:';
|
|
}
|
|
text += '${element['content']}\n';
|
|
}
|
|
allText = text;
|
|
}
|
|
await _aiServiceSummary();
|
|
EasyLoading.dismiss();
|
|
}
|
|
|
|
Future<void> _aiServiceSummary() async {
|
|
String summaryText = await DeapsoundAIService().sendMessage(
|
|
messages: [
|
|
{'role': 'system', 'content': _systemPrompt},
|
|
{'role': 'user', 'content': allText},
|
|
],
|
|
);
|
|
SqfliteApi.editMeeting(id, {'summary': summaryText});
|
|
meetingDetails.value.summary = summaryText;
|
|
meetingDetails.refresh();
|
|
}
|
|
|
|
Future<void> _audioSpeaker() async {
|
|
String audiourl = meetingData.value.audiourl;
|
|
if (audiourl.isEmpty) {
|
|
audiourl = await UploadOss.upload(
|
|
filepath: meetingData.value.filepath,
|
|
rootDir: 'audio',
|
|
);
|
|
meetingData.value.audiourl = audiourl;
|
|
}
|
|
String taskId = meetingDetails.value.taskid;
|
|
if (taskId.isEmpty) {
|
|
taskId = await _submitTask(audiourl);
|
|
meetingDetails.value.taskid = taskId;
|
|
await SqfliteApi.editMeeting(id, {'taskid': taskId});
|
|
}
|
|
await _fileRecognize(taskId);
|
|
}
|
|
|
|
Future<String> _submitTask(String audiourl) async {
|
|
String format = _getFileType(audiourl);
|
|
|
|
final requestBody = jsonEncode({
|
|
"app": {
|
|
"appid": _appid,
|
|
"token": _token,
|
|
"cluster": _cluster,
|
|
},
|
|
"user": {
|
|
"uid": _userId,
|
|
},
|
|
"audio": {
|
|
"format": format,
|
|
"url": audiourl,
|
|
},
|
|
"additions": {
|
|
"with_speaker_info": "True",
|
|
}
|
|
});
|
|
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$_serviceUrl/submit'),
|
|
headers: _headers,
|
|
body: requestBody,
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Submit failed with status: ${response.statusCode}');
|
|
}
|
|
|
|
final respDic = jsonDecode(response.body);
|
|
final taskId = respDic['resp']['id'] as String;
|
|
Logger.debug('taskId: $taskId');
|
|
return taskId;
|
|
} catch (e) {
|
|
Logger.error('Submit error: ${e.toString()}');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
String _getFileType(String path) {
|
|
List<String> array = path.split('.');
|
|
return array[array.length - 1];
|
|
}
|
|
|
|
Future<Map<String, dynamic>> _queryTask(String taskId) async {
|
|
final queryBody = jsonEncode({
|
|
"appid": _appid,
|
|
"token": _token,
|
|
"id": taskId,
|
|
"cluster": _cluster,
|
|
});
|
|
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$_serviceUrl/query'),
|
|
headers: _headers,
|
|
body: queryBody,
|
|
);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
Future<void> _fileRecognize(String taskId) async {
|
|
try {
|
|
final startTime = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
while (!_isDisposed) {
|
|
await Future.delayed(const Duration(seconds: 2));
|
|
|
|
if (_isDisposed) return;
|
|
|
|
final respDic = await _queryTask(taskId);
|
|
final code = respDic['resp']['code'] as int;
|
|
|
|
if (code == 1000) {
|
|
await _addTextList(respDic['resp']['utterances']);
|
|
return;
|
|
} else if (code < 2000) {
|
|
Logger.debug("FileRecognize failed");
|
|
return;
|
|
}
|
|
|
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
|
if (currentTime - startTime > 300 * 1000) {
|
|
Logger.debug('wait time exceeds 300s');
|
|
return;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('Error occurred: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
Future<void> _addTextList(List utterances) async {
|
|
String text = '';
|
|
String personnel = '';
|
|
List<Map<String, Object?>> list = [];
|
|
for (var element in utterances) {
|
|
String speaker = '';
|
|
if (element['additions'].isNotEmpty) {
|
|
speaker = 'Speaker ${element['additions']['speaker']}';
|
|
text += '$speaker:';
|
|
if (!personnel.contains(speaker)) {
|
|
personnel += '[$speaker] ';
|
|
}
|
|
}
|
|
list.add({
|
|
'meetingid': id,
|
|
'content': element['text'],
|
|
'starttime': element['start_time'],
|
|
'endtime': element['end_time'],
|
|
'speaker': speaker,
|
|
});
|
|
text += '${element['text']}\n';
|
|
}
|
|
var result = await SqfliteApi.insertMeetingSpeaker(list);
|
|
if (result != null) {
|
|
await SqfliteApi.editMeeting(id, {'tasktype': 1, 'personnel': personnel});
|
|
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
|
|
textList.value = List.from(speakerList);
|
|
meetingDetails.value.tasktype = 1;
|
|
meetingDetails.value.personnel = personnel;
|
|
meetingDetails.refresh();
|
|
}
|
|
allText = text;
|
|
}
|
|
}
|
|
|