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 'package:uuid/uuid.dart'; import '../../../core/utils/logger.dart'; import '../../../core/utils/upload_oss.dart'; import '../../../data/models/appconfig_model.dart'; import '../../../data/services/ai_service.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/open_ai_service_adapter.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 meetingData = MeetingModel().obs; Rx meetingDetails = MeetingDetailsModel().obs; RxList textList = [].obs; String allText = ''; RxBool isSpeakerText = false.obs; List editSpeakerTextList = []; RxInt editSpeakerTextId = 0.obs; String editSpeakerTextChange = ''; final AiService _aiService = Get.find(); // Agent 信息 late final DBAgent _agent; String _userId = ''; StreamSubscription? _aiResponseSubscription; 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 _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 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(); _aiResponseSubscription?.cancel(); _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(); final List agents = storage.read("AGENTS"); Map userInfo = storage.read("user_info"); _userId = userInfo['user']['uid']; _agent = agents.firstWhere((agent) => agent.id == '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.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 editSpeakerName() {} 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; } _aiServiceListen(); // const uuid = Uuid(); // _aiService.send( // sid: uuid.v4(), // messages: [ // {'role': 'user', 'content': allText}, // ], // systemPrompt: _agent.systemPrompt, // ); } void _aiServiceListen() { String summaryText = ''; // _aiResponseSubscription = _aiService.start().listen((AIStreamEvent event) { // summaryText += event.content ?? ''; // if (event.type == StreamEventType.complete) { // SqfliteApi.editMeeting(id, {'summary': summaryText}); // meetingDetails.value.summary = summaryText; // meetingDetails.refresh(); // EasyLoading.dismiss(); // } // }); } Future _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 _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'); rethrow; } } String _getFileType(String path) { List array = path.split('.'); return array[array.length - 1]; } Future> _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'); rethrow; } } Future _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'); } } Future _addTextList(List utterances) async { String text = ''; List> list = []; for (var element in utterances) { String speaker = ''; if (element['additions'].isNotEmpty) { speaker = 'Speaker ${element['additions']['speaker']}'; text += '$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}); List speakerList = await SqfliteApi.getMeetingSpeaker(id); textList.value = List.from(speakerList); meetingDetails.value.tasktype = 1; meetingDetails.refresh(); } allText = text; } }