import 'dart:async'; import 'dart:io'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import '../../../../common/api/index.dart'; import '../../../../common/models/index.dart'; import '../../../../common/routers/index.dart'; import '../../../../common/services/index.dart'; import '../../../../common/utils/index.dart'; import '../../../apps/meeting/index.dart'; import '../../../apps/process/process_picker/index.dart'; import '../../../common/create_form/index.dart'; import '../../../common/portal/index.dart'; import 'index.dart'; class SpeechAssistantChatController extends GetxController { SpeechAssistantChatController(); final state = SpeechAssistantChatState(); /// 录音 FlutterSoundRecorder? _myRecorder = FlutterSoundRecorder(); bool _mRecorderIsInited = false; String _mPath = ""; final _maxLength = 60; // 最长录音时间60秒 int _recordSecond = 0; StreamSubscription? _mRecordingDataSubscription; bool _ttsOpen = false; /// 在 onInit() 之后调用 1 帧。这是进入的理想场所 @override void onReady() { state.btnTitle = 'im_chat_tools_voice'.tr; _loadInitCommandList(); initTheRecorder(); _ttsOpen = ProgramCenterService.to.extendParam()['needTTS'] ?? false; super.onReady(); } @override void onClose() { _myRecorder?.closeRecorder(); _myRecorder = null; if (_ttsOpen) { FlutterTTSHelper.instance.stop(); } super.onClose(); } /// 初始化录音功能 Future initTheRecorder() async { // 检查录音权限 var isPermission = await O2Utils.microphonePermission(); if (!isPermission) { OLogger.e('没有权限,无法录音'); Loading.showError('im_chat_error_need_record_permission'.tr); return; } // 开启录音工具 await _myRecorder?.openRecorder(); //设置订阅计时器 await _myRecorder ?.setSubscriptionDuration(const Duration(milliseconds: 10)); // 初始化录音路径 var tempDir = await getTemporaryDirectory(); _mPath = '${tempDir.path}/o2_speech_assistant.pcm'; // pcm 格式录音 baidu 语音需要 _mRecorderIsInited = true; } /// 开始录音 Future startRecorder() async { if (!_mRecorderIsInited) { Loading.showError('im_chat_error_record_not_init'.tr); return; } if (state.status.value != SpeechStatus.idle) { OLogger.i('正在数据加载中!'); return; } if (_ttsOpen) { FlutterTTSHelper.instance.stop(); } state.status.value = SpeechStatus.speaking; // 初始化数字 _recordSecond = 0; // 初始化文件 var outputFile = File(_mPath); if (outputFile.existsSync()) { await outputFile.delete(); } _myRecorder?.startRecorder( toFile: _mPath, codec: Codec.pcm16, ); /// 监听录音 _mRecordingDataSubscription = _myRecorder?.onProgress?.listen((e) { final date = DateTime.fromMillisecondsSinceEpoch( e.duration.inMilliseconds, isUtc: true); // var txt = date.hms(); _recordSecond = date.second; // //设置了最大录音时长 if (date.second >= _maxLength) { stopRecorder(); return; } //更新录音时长 // state.btnTitle = txt; }); } Future stopRecorder() async { if (state.status.value != SpeechStatus.speaking) { OLogger.i('正在数据加载中!'); return; } await _myRecorder!.stopRecorder(); _cancelRecorderSubscriptions(); OLogger.d("record time $_recordSecond"); if (_recordSecond < 1) { Loading.showError('im_chat_error_record_time_too_short'.tr); state.status.value = SpeechStatus.idle; } else { _sendFileToServer(); } OLogger.d('stop end ......'); } /// 取消录音监听 void _cancelRecorderSubscriptions() { if (_mRecordingDataSubscription != null) { _mRecordingDataSubscription?.cancel(); _mRecordingDataSubscription = null; } } /// 发送到后台 Future _sendFileToServer() async { state.status.value = SpeechStatus.thinking; File file = File(_mPath); final ids = await FileAssembleService.to.uploadFileForSpeechAssistant(file); if (ids != null) { OLogger.d('文件 id ${ids.id}'); _thinking(fileId: ids.id); } else { state.status.value = SpeechStatus.idle; OLogger.e('上传文件失败!'); } } /// 随机命令列表,初始化展示用 Future _loadInitCommandList() async { Map body = {}; body['type'] = 'init'; final result = await ProgramCenterService.to.executeScript(ProgramCenterService.to.speechScript(), body); var list = result == null ? [] : result as List; state.initCommandList.addAll(list.map((e) => e as String).toList()); } Future _thinking({String? fileId, String? inputText}) async { Map body = {}; body['personId'] = O2ApiManager.instance.o2User?.id ?? ''; if (fileId != null && fileId.isNotEmpty) { body['speechFileId'] = fileId; } else if (inputText != null && inputText.isNotEmpty) { body['inputText'] = inputText; } final result = await ProgramCenterService.to.executeScript(ProgramCenterService.to.speechScript(), body); if (result != null) { ImSpeechAssistantResponse res = ImSpeechAssistantResponse.fromJson(result); if (res.msg == null || res.msg!.isEmpty == true) { _answerMsg(res.err!); } else { res.createTime = DateTime.now(); res.side = ImSpeechAssistantResponse.rightSide; state.chatList.add(res); final command = res.command; if (command == null) { _answerMsg('im_chat_speech_assistant_msg_unknown_command'.tr); } else { final answer = command.answer ?? ''; final processId = command.processId ?? ''; final portalId = command.portalId ?? ''; final commandName = command.name ?? ''; if (answer.isNotEmpty) { // 有答案 直接回复 _answerMsg(answer); } else if (processId.isNotEmpty) { // 有流程 就启动流程工作 _answerMsg( 'im_chat_speech_assistant_msg_open_start_level_process'.tr); _startWorkByProcess(processId); } else if (portalId.isNotEmpty) { _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([res.msg ?? ''])); _openPortal(portalId, command.pageId); } else if (commandName.isNotEmpty) { // 有命令执行对应的命令 _doCommand(commandName); } else if (res.err != null && res.err!.isNotEmpty == true) { // 有错误,比如不认识的命令 直接回复 _answerMsg(res.err!); } else { _answerMsg('im_chat_speech_assistant_msg_unknown_command'.tr); } } } } else { Loading.toast('request_error'.tr); } state.status.value = SpeechStatus.idle; OLogger.d('_thinking 完成!'); } // 执行特殊命令,一般是打开某个应用 _doCommand(String command) { switch (command) { case 'task': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.task.name])); Get.toNamed(O2OARoutes.appTask); break; case 'taskCompleted': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.taskcompleted.name])); Get.toNamed(O2OARoutes.appTaskcompleted); break; case 'read': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.read.name])); Get.toNamed(O2OARoutes.appRead); break; case 'readCompleted': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.readcompleted.name])); Get.toNamed(O2OARoutes.appReadcompleted); break; case 'startProcess': _answerMsg('im_chat_speech_assistant_msg_open_start_process'.tr); // 打开启动流程工具 _startProcess(); break; case 'meeting': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.meeting.name])); // 打开会议管理 MeetingPage.startMeetingApp(); break; case 'createMeeting': _answerMsg('im_chat_speech_assistant_msg_open_meeting_create'.tr); // 打开创建会议 MeetingPage.startMeetingApp(openCreate: true); break; case 'attendance': _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.attendance.name])); // 打开考勤打卡 FastCheckInService.instance.stop(); // 进入考勤 先关闭极速打卡 Get.toNamed(O2OARoutes.appAttendance); break; case 'bbs': // 论坛 _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.bbs.name])); Get.toNamed(O2OARoutes.appBBS); break; case 'yunpan': // 网盘 _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.yunpan.name])); Get.toNamed(FileAssembleService.to.isV3() ? O2OARoutes.appCloudDiskV3 : O2OARoutes.appYunpan); break; case 'cms': // 信息中心 _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.cms.name])); Get.toNamed(O2OARoutes.appCms); break; case 'calendar': // 信息中心 _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.calendar.name])); Get.toNamed(O2OARoutes.appCalendar); break; case 'mindMap': // 信息中心 _answerMsg('im_chat_speech_assistant_msg_open_app' .trArgs([O2NativeAppEnum.mindMap.name])); Get.toNamed(O2OARoutes.appMindMap); break; default: _answerMsg('im_chat_speech_assistant_msg_unknown_command'.tr); break; } } /// 回复信息 _answerMsg(String msg) { state.chatList.add(ImSpeechAssistantResponse( msg: msg, side: ImSpeechAssistantResponse.leftSide, createTime: DateTime.now())); if (_ttsOpen) { FlutterTTSHelper.instance.speak(msg); } } /// 新建工作 Future _startProcess() async { // 选择启动流程 var result = await ProcessPickerPage.startPicker(ProcessPickerMode.process); if (result != null && result is ProcessData) { CreateFormPage.startProcess(true, process: result); } } /// 启动流程,创建工作 /// 先查询请假流程对象,然后启动流程 Future _startWorkByProcess(String processId) async { if (processId.isEmpty) { _answerMsg('im_chat_speech_assistant_msg_not_found_level_process'.tr); return; } final processInfo = await ProcessSurfaceService.to.getProcess(processId); if (processInfo == null) { _answerMsg('im_chat_speech_assistant_msg_not_found_level_process'.tr); return; } CreateFormPage.startProcess(true, process: processInfo); } /// 打开门户 void _openPortal(String portalId, String? pageId) { PortalPage.open(portalId, pageId: pageId); } }