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.

425 lines
13 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:flutter_screenutil/flutter_screenutil.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';
import 'meeting_controller.dart';
class MeetingDetailsController extends GetxController
with GetTickerProviderStateMixin {
late TabController tabController;
final RxInt currentIndex = 0.obs;
late AnimationController lottieController;
final _meetingController = Get.find<MeetingController>();
final PlayerController playerController = PlayerController();
RxBool isTop = false.obs;
RxInt durationMax = 0.obs;
RxDouble dragOffset = 210.w.obs;
RxBool isPlay = false.obs;
RxInt currentDuration = 0.obs;
RxInt speakerAllStarttime = (-1).obs;
RxInt speakerAllEndtime = (-1).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();
tabController = TabController(length: 4, vsync: this);
tabController.addListener(() {
if (currentIndex.value == 2 && tabController.index != 2) {
FocusScope.of(Get.context!).unfocus();
}
currentIndex.value = tabController.index;
});
lottieController = AnimationController(vsync: this)
..duration = const Duration(seconds: 2);
final Map<String, dynamic> args = Get.arguments ?? {};
id = args['id'];
_headers = {
'Authorization': 'Bearer; $_token',
'Content-Type': 'application/json',
};
_getAgent();
_getDataDetails();
}
@override
void onClose() {
tabController.dispose();
playerController.dispose();
_isDisposed = true;
lottieController.dispose();
super.onClose();
}
void popInvokedWithResult(didPop, result) async {
if (meetingData.value.tasktype != meetingDetails.value.tasktype) {
await SqfliteApi.editMeetingTitle(
id, {'tasktype': meetingDetails.value.tasktype});
int index =
_meetingController.dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap =
Map<String, dynamic>.from(_meetingController.dataList[index]);
dataMap['tasktype'] = meetingDetails.value.tasktype;
if (dataMap['taskid'].isEmpty) {
dataMap['taskid'] = meetingDetails.value.taskid;
}
_meetingController.dataList[index] = dataMap;
int taskIndex = _meetingController.dataListTask
.indexWhere((item) => item['id'] == id);
if (taskIndex != -1) {
if (meetingDetails.value.tasktype == 1) {
_meetingController.dataListTask[taskIndex] = dataMap;
} else {
_meetingController.dataListTask.removeAt(taskIndex);
}
} else {
if (meetingDetails.value.tasktype == 1) {
_meetingController.dataListTask.add(dataMap);
}
}
}
}
_meetingController.isDisposed = false;
_meetingController.queryDataListTask(
_meetingController.dataListTask,
isAdd: 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']);
if (meetingDetails.value.tasktype >= 3) {
dragOffset.value = 0;
isTop.value = true;
}
await playerController.preparePlayer(
path: meetingData.value.filepath,
noOfSamples: 1200,
);
durationMax.value = await playerController.getDuration(DurationType.max);
playerController.setFinishMode(finishMode: FinishMode.pause);
playerController.onCurrentDurationChanged.listen((int position) {
currentDuration.value = position;
if (speakerAllEndtime.value > 0) {
if (position >= speakerAllEndtime.value) {
playerController.pausePlayer();
speakerAllStarttime.value = -1;
speakerAllEndtime.value = -1;
}
}
});
playerController.onPlayerStateChanged.listen((PlayerState playerState) {
isPlay.value = playerState.isPlaying;
if (_isDisposed) return;
if (playerState.isPlaying) {
lottieController.repeat();
} else {
lottieController.stop();
}
});
if (meetingDetails.value.tasktype >= 2) {
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
}
if (meetingDetails.value.tasktype == 1 ||
meetingDetails.value.tasktype == 2) {
summaryCreate();
}
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 summaryCreate() async {
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;
}
if (allText.isEmpty) return;
await _aiServiceSummary();
}
Future<void> _aiServiceSummary() async {
String summaryText = await DeapsoundAIService().sendMessage(
messages: [
{'role': 'system', 'content': _systemPrompt},
{'role': 'user', 'content': allText},
],
);
SqfliteApi.editMeeting(id, {'tasktype': 3, 'summary': summaryText});
meetingDetails.value.tasktype = 3;
meetingDetails.value.summary = summaryText;
meetingDetails.refresh();
}
Future<void> _audioSpeaker() async {
EasyLoading.show();
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);
await SqfliteApi.editMeeting(id, {'tasktype': 1, 'taskid': taskId});
meetingDetails.value.taskid = taskId;
meetingDetails.value.tasktype = 1;
meetingDetails.refresh();
}
EasyLoading.dismiss();
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;
if (_isDisposed) return;
var respDic = await _queryTask(taskId);
var code = respDic['resp']['code'] as int;
if (code == 1000) {
await _addTextList(respDic['resp']['utterances']);
return;
} else if (code < 2000) {
Logger.debug("FileRecognize failed");
return;
}
while (!_isDisposed) {
await Future.delayed(const Duration(seconds: 2));
if (_isDisposed) return;
respDic = await _queryTask(taskId);
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': 2, 'personnel': personnel});
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
meetingDetails.value.tasktype = 2;
meetingDetails.value.personnel = personnel;
meetingDetails.refresh();
}
allText = text;
}
}