diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index aa2e1f4a3..272d02609 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -57,7 +57,7 @@ class MeetingDetailsController extends GetxController RxInt editSpeakerTextId = 0.obs; String editSpeakerTextChange = ''; - int _everType = 99; + int _everType = 99999; RxInt summaryNumberModifications = 0.obs; RxInt personnelNumberModifications = 0.obs; @@ -94,12 +94,12 @@ class MeetingDetailsController extends GetxController _tasktypeListener = ever(_meetingTaskService.tasktype, (value) { meetingDetails.value.tasktype = value; if (_everType <= value) { - if (value >= 5) { + if (value >= 5 && value < 10000) { meetingDetails.value.personnel = _meetingTaskService.personnel; meetingDetails.value.summary = _meetingTaskService.summary; meetingDetails.value.overview = _meetingTaskService.overview; summaryNumberModifications.value++; - } else if (value >= 3) { + } else if (value >= 3 && value < 10000) { textList.value = _meetingTaskService.textList; } } @@ -132,14 +132,16 @@ class MeetingDetailsController extends GetxController if (meetingDetails.value.tasktype == 5) { await _meetingTaskService.readTask(id); } - if (meetingDetails.value.tasktype >= 5) { + if (meetingDetails.value.tasktype >= 5 && + meetingDetails.value.tasktype < 10000) { dragOffset.value = 0; isTop.value = true; } if (meetingData.value.filepath.isNotEmpty) { await _loadPlayerController(); } - if (meetingDetails.value.tasktype >= 3) { + if (meetingDetails.value.tasktype >= 3 && + meetingDetails.value.tasktype < 10000) { List speakerList = await SqfliteApi.getMeetingSpeaker(id); textList.value = List.from(speakerList); } diff --git a/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart index 43335adfe..1701f6c76 100644 --- a/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart @@ -26,7 +26,8 @@ class _MoreBottomSheetState extends State { children: [ SizedBox(height: 10.w), _shareItem(Icons.refresh, 'reSummarize'.tr, //重新总结 - onTap: _detailsController.meetingDetails.value.tasktype >= 5 + onTap: _detailsController.meetingDetails.value.tasktype >= 5 && + _detailsController.meetingDetails.value.tasktype < 10002 ? () { Get.back(); Get.bottomSheet( diff --git a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart index 08a76eebf..f4d507a34 100644 --- a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart @@ -117,7 +117,8 @@ class _ShareBottomSheetState extends State { ]), _box([ _shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写 - onTap: _controller.meetingDetails.value.tasktype >= 3 + onTap: _controller.meetingDetails.value.tasktype >= 3 && + _controller.meetingDetails.value.tasktype < 10002 ? () { Get.back(); _copy(_getAllText()); @@ -125,7 +126,8 @@ class _ShareBottomSheetState extends State { : null), _shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结 isBorder: false, - onTap: _controller.meetingDetails.value.tasktype >= 5 + onTap: _controller.meetingDetails.value.tasktype >= 5 && + _controller.meetingDetails.value.tasktype < 10001 ? () { Get.back(); _copy(_markdownToPlainText( @@ -142,7 +144,8 @@ class _ShareBottomSheetState extends State { 'shareMeetingAudio'.tr); // 对应中文:分享会议音频 }), _shareItem(Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写 - onTap: _controller.meetingDetails.value.tasktype >= 3 + onTap: _controller.meetingDetails.value.tasktype >= 3 && + _controller.meetingDetails.value.tasktype < 10002 ? () async { Get.back(); final tempDir = await getTemporaryDirectory(); @@ -155,7 +158,8 @@ class _ShareBottomSheetState extends State { } : null), _shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结 - onTap: _controller.meetingDetails.value.tasktype >= 5 + onTap: _controller.meetingDetails.value.tasktype >= 5 && + _controller.meetingDetails.value.tasktype < 10001 ? () async { Get.back(); Get.bottomSheet( @@ -205,7 +209,8 @@ class _ShareBottomSheetState extends State { _shareItem( Icons.save_alt, '保存思维导图', - onTap: _controller.meetingDetails.value.tasktype >= 5 + onTap: _controller.meetingDetails.value.tasktype >= 5 && + _controller.meetingDetails.value.tasktype < 10001 ? () { Get.back(); final mindMapController = @@ -233,7 +238,8 @@ class _ShareBottomSheetState extends State { Icons.lan, 'shareMindMap'.tr, // 对应中文:分享思维导图 isBorder: false, - onTap: _controller.meetingDetails.value.tasktype >= 5 + onTap: _controller.meetingDetails.value.tasktype >= 5 && + _controller.meetingDetails.value.tasktype < 10001 ? () { Get.back(); final mindMapController = diff --git a/lib/modules/meeting/views/meeting_home_view.dart b/lib/modules/meeting/views/meeting_home_view.dart index c15a3f6bf..c754175cf 100644 --- a/lib/modules/meeting/views/meeting_home_view.dart +++ b/lib/modules/meeting/views/meeting_home_view.dart @@ -267,6 +267,12 @@ class _MeetingHomeViewState extends State case 5: tasktypeText = 'summaryComplete'.tr; // 对应中文:总结完成 break; + case 10001: + tasktypeText = '总结失败'; + break; + case 10002: + tasktypeText = '转写失败'; + break; default: tasktypeText = ''; } diff --git a/lib/modules/meeting/views/tabs/overview_tab.dart b/lib/modules/meeting/views/tabs/overview_tab.dart index c43201d9d..69057d32d 100644 --- a/lib/modules/meeting/views/tabs/overview_tab.dart +++ b/lib/modules/meeting/views/tabs/overview_tab.dart @@ -99,7 +99,8 @@ class _OverviewTabState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (_controller.meetingDetails.value.tasktype >= 3) + if (_controller.meetingDetails.value.tasktype >= 3 && + _controller.meetingDetails.value.tasktype < 10000) _button( Icons.auto_awesome, 'Ask AI', @@ -138,6 +139,50 @@ class _OverviewTabState extends State { }, ), ), + if (_controller.meetingDetails.value.tasktype == 10002) + SizedBox( + width: 150.w, + child: _button( + Icons.auto_awesome, + '重新生成', + () { + Get.bottomSheet( + const GenerateBottomSheet(), + isScrollControlled: true, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + ), + ), + if (_controller.meetingDetails.value.tasktype == 10001) + SizedBox( + width: 150.w, + child: _button( + Icons.auto_awesome, + '重新总结', + () { + Get.bottomSheet( + const GenerateBottomSheet( + isSummarizeAgain: true, + ), + isScrollControlled: true, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + ), + ), ], ), ), @@ -200,7 +245,8 @@ class _OverviewTabState extends State { Widget _empty() { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - return Padding( + return Container( + width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 30.w), child: Column( children: [ @@ -221,7 +267,9 @@ class _OverviewTabState extends State { ), ), Text( - 'aiAnalysisDesc'.tr, // 对应中文:AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。 + _controller.meetingDetails.value.tasktype < 10000 + ? 'aiAnalysisDesc'.tr // 对应中文:AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。 + : '分析失败,请稍后重试', style: TextStyle( color: MeetingUIUtils.getSecondaryTextColor(isDarkMode), fontSize: 14.sp, diff --git a/lib/modules/meeting/views/tabs/speech_tab.dart b/lib/modules/meeting/views/tabs/speech_tab.dart index b42d9af49..43acf27cc 100644 --- a/lib/modules/meeting/views/tabs/speech_tab.dart +++ b/lib/modules/meeting/views/tabs/speech_tab.dart @@ -20,229 +20,259 @@ class SpeechTab extends GetView { return Stack( children: [ Obx( - () => ListView.builder( - itemCount: controller.textList.length, - padding: EdgeInsets.only(bottom: 80.w), - itemBuilder: (BuildContext context, int index) { - Map item = controller.textList[index]; - TextStyle textStyle = TextStyle( - fontSize: 15.sp, - color: MeetingUIUtils.getTextColor(isDarkMode), - letterSpacing: 0, - height: 1.4, - ); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 10.w), - child: Row( + () => controller.meetingDetails.value.tasktype != 10002 + ? ListView.builder( + itemCount: controller.textList.length, + padding: EdgeInsets.only(bottom: 80.w), + itemBuilder: (BuildContext context, int index) { + Map item = controller.textList[index]; + TextStyle textStyle = TextStyle( + fontSize: 15.sp, + color: MeetingUIUtils.getTextColor(isDarkMode), + letterSpacing: 0, + height: 1.4, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (item['speaker'].isNotEmpty) - Container( - width: 11.w, - height: 11.w, - margin: EdgeInsets.only(right: 3.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(11.r), - border: Border.all( - color: const Color(0xFFFFC107), - width: 3.w, - ), - ), - ), - if (item['speaker'].isNotEmpty) - GestureDetector( - onTap: () { - if (controller.isSpeakerText.value) return; - Get.bottomSheet( - SpeakerBottomSheet(item['id'], item['speaker']), - isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(12.r), + Padding( + padding: EdgeInsets.only(top: 10.w), + child: Row( + children: [ + if (item['speaker'].isNotEmpty) + Container( + width: 11.w, + height: 11.w, + margin: EdgeInsets.only(right: 3.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(11.r), + border: Border.all( + color: const Color(0xFFFFC107), + width: 3.w, + ), ), ), - ); - }, - child: Padding( - padding: EdgeInsets.only(right: 5.w), - child: Text( - item['speaker'], + if (item['speaker'].isNotEmpty) + GestureDetector( + onTap: () { + if (controller.isSpeakerText.value) return; + Get.bottomSheet( + SpeakerBottomSheet( + item['id'], item['speaker']), + isScrollControlled: true, + backgroundColor: isDarkMode + ? Colors.grey[800] + : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + child: Padding( + padding: EdgeInsets.only(right: 5.w), + child: Text( + item['speaker'], + style: TextStyle( + color: const Color(0xFFFFC107), + fontSize: 13.sp, + ), + ), + ), + ), + Text( + DateUtil.formatDateMs( + item['starttime'], + format: 'HH:mm:ss', + isUtc: true, + ), style: TextStyle( - color: const Color(0xFFFFC107), + color: MeetingUIUtils.getSecondaryTextColor( + isDarkMode), fontSize: 13.sp, ), ), - ), - ), - Text( - DateUtil.formatDateMs( - item['starttime'], - format: 'HH:mm:ss', - isUtc: true, - ), - style: TextStyle( - color: MeetingUIUtils.getSecondaryTextColor(isDarkMode), - fontSize: 13.sp, + Obx(() { + bool isActive = false; + int starttime = item['starttime']; + int endtime = 0; + if (index == controller.textList.length - 1) { + endtime = controller.durationMax.value; + } else { + endtime = controller.textList[index + 1] + ['starttime']; + } + if (controller.currentDuration.value != 0) { + if (controller.currentDuration.value >= + starttime && + controller.currentDuration.value < + endtime) { + isActive = true; + } + } + return isActive + ? Container( + width: 16.w, + height: 16.w, + margin: EdgeInsets.only(left: 5.w), + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: + BorderRadius.circular(4.r), + ), + child: Transform.scale( + scale: 1.6, + child: Lottie.asset( + 'assets/lottie/sound_effect.json', + controller: + controller.lottieController, + ), + ), + ) + : const SizedBox(); + }), + ], ), ), Obx(() { - bool isActive = false; - int starttime = item['starttime']; - int endtime = 0; - if (index == controller.textList.length - 1) { - endtime = controller.durationMax.value; + if (controller.isSpeakerText.value) { + TextEditingController speakerTextController = + TextEditingController(text: item['content']); + return Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 14.w, + ), + decoration: BoxDecoration( + color: controller.editSpeakerTextId.value == + item['id'] + ? Colors.blue[100] + : null, + borderRadius: BorderRadius.circular(8.w), + ), + child: FocusScope( + onFocusChange: (bool hasFocus) { + if (hasFocus) { + controller.editSpeakerTextId.value = + item['id']; + controller.editSpeakerTextChange = ''; + } else { + if (speakerTextController.text != + item['content']) { + Map? foundElement = controller + .editSpeakerTextList + .firstWhere( + (element) => + element['id'] == item['id'], + orElse: () => null, + ); + if (foundElement != null) { + foundElement['content'] = + speakerTextController.text; + } else { + controller.editSpeakerTextList.add({ + 'id': item['id'], + 'content': + speakerTextController.text, + }); + } + } + } + }, + child: TextField( + controller: speakerTextController, + style: textStyle, + minLines: 1, + maxLines: 100, + decoration: InputDecoration( + isDense: true, + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(vertical: 5.w), + ), + onChanged: (String value) { + controller.editSpeakerTextChange = value; + }, + ), + ), + ), + ); } else { - endtime = - controller.textList[index + 1]['starttime']; - } - if (controller.currentDuration.value != 0) { - if (controller.currentDuration.value >= starttime && - controller.currentDuration.value < endtime) { - isActive = true; - } - } - return isActive - ? Container( - width: 16.w, - height: 16.w, - margin: EdgeInsets.only(left: 5.w), - alignment: Alignment.center, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Colors.red[100], - borderRadius: BorderRadius.circular(4.r), + return Obx(() { + bool isActive = false; + int starttime = item['starttime']; + int endtime = 0; + if (index == controller.textList.length - 1) { + endtime = controller.durationMax.value; + } else { + endtime = + controller.textList[index + 1]['starttime']; + } + if (controller.currentDuration.value != 0) { + if (controller.currentDuration.value >= + starttime && + controller.currentDuration.value < + endtime) { + isActive = true; + } + } + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + if (isActive) { + if (controller.isPlay.value) { + controller.playerController.pausePlayer(); + } else { + controller.playerController.startPlayer(); + } + } else { + controller.playerController + .seekTo(item['starttime']); + controller.playerController.startPlayer(); + } + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 14.w, + vertical: 5.w, ), - child: Transform.scale( - scale: 1.6, - child: Lottie.asset( - 'assets/lottie/sound_effect.json', - controller: controller.lottieController, + child: Text( + item['content'], + style: TextStyle( + fontSize: 15.sp, + color: isActive + ? Colors.red + : MeetingUIUtils.getTextColor( + isDarkMode), + letterSpacing: 0, + height: 1.4, ), ), - ) - : const SizedBox(); + ), + ); + }); + } }), ], + ); + }, + ) + : Container( + height: 200.w, + alignment: Alignment.center, + child: Text( + '转写失败,请稍后重试', + style: TextStyle( + color: MeetingUIUtils.getSecondaryTextColor(isDarkMode), + fontSize: 14.sp, ), ), - Obx(() { - if (controller.isSpeakerText.value) { - TextEditingController speakerTextController = - TextEditingController(text: item['content']); - return Obx( - () => Container( - padding: EdgeInsets.symmetric( - horizontal: 14.w, - ), - decoration: BoxDecoration( - color: - controller.editSpeakerTextId.value == item['id'] - ? Colors.blue[100] - : null, - borderRadius: BorderRadius.circular(8.w), - ), - child: FocusScope( - onFocusChange: (bool hasFocus) { - if (hasFocus) { - controller.editSpeakerTextId.value = item['id']; - controller.editSpeakerTextChange = ''; - } else { - if (speakerTextController.text != - item['content']) { - Map? foundElement = - controller.editSpeakerTextList.firstWhere( - (element) => element['id'] == item['id'], - orElse: () => null, - ); - if (foundElement != null) { - foundElement['content'] = - speakerTextController.text; - } else { - controller.editSpeakerTextList.add({ - 'id': item['id'], - 'content': speakerTextController.text, - }); - } - } - } - }, - child: TextField( - controller: speakerTextController, - style: textStyle, - minLines: 1, - maxLines: 100, - decoration: InputDecoration( - isDense: true, - fillColor: Colors.transparent, - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(vertical: 5.w), - ), - onChanged: (String value) { - controller.editSpeakerTextChange = value; - }, - ), - ), - ), - ); - } else { - return Obx(() { - bool isActive = false; - int starttime = item['starttime']; - int endtime = 0; - if (index == controller.textList.length - 1) { - endtime = controller.durationMax.value; - } else { - endtime = controller.textList[index + 1]['starttime']; - } - if (controller.currentDuration.value != 0) { - if (controller.currentDuration.value >= starttime && - controller.currentDuration.value < endtime) { - isActive = true; - } - } - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - if (isActive) { - if (controller.isPlay.value) { - controller.playerController.pausePlayer(); - } else { - controller.playerController.startPlayer(); - } - } else { - controller.playerController - .seekTo(item['starttime']); - controller.playerController.startPlayer(); - } - }, - child: Container( - width: double.infinity, - padding: EdgeInsets.symmetric( - horizontal: 14.w, - vertical: 5.w, - ), - child: Text( - item['content'], - style: TextStyle( - fontSize: 15.sp, - color: isActive ? Colors.red : MeetingUIUtils.getTextColor(isDarkMode), - letterSpacing: 0, - height: 1.4, - ), - ), - ), - ); - }); - } - }), - ], - ); - }, - ), + ), ), Obx( () => Positioned( @@ -253,7 +283,8 @@ class SpeechTab extends GetView { mainAxisAlignment: MainAxisAlignment.center, children: [ if (controller.meetingDetails.value.tasktype >= 2 && - !controller.isSpeakerText.value) + !controller.isSpeakerText.value && + controller.meetingDetails.value.tasktype != 10002) _button( Icons.edit, 'editSpeaker'.tr, // 对应中文:编辑 @@ -264,7 +295,8 @@ class SpeechTab extends GetView { }, ), if (controller.meetingDetails.value.tasktype >= 2 && - !controller.isSpeakerText.value) + !controller.isSpeakerText.value && + controller.meetingDetails.value.tasktype != 10002) _button( Icons.supervisor_account, 'nameSpeaker'.tr, // 对应中文:命名发言者 @@ -272,7 +304,8 @@ class SpeechTab extends GetView { Get.bottomSheet( const SpeakerAllBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -282,7 +315,8 @@ class SpeechTab extends GetView { }, ), if (controller.meetingDetails.value.tasktype >= 2 && - !controller.isSpeakerText.value) + !controller.isSpeakerText.value && + controller.meetingDetails.value.tasktype != 10002) _button( Icons.auto_awesome, 'Ask AI', @@ -290,7 +324,8 @@ class SpeechTab extends GetView { Get.bottomSheet( const AIBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.white, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -309,7 +344,29 @@ class SpeechTab extends GetView { Get.bottomSheet( const GenerateBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + ), + ), + if (controller.meetingDetails.value.tasktype == 10002) + SizedBox( + width: 150.w, + child: _button( + Icons.auto_awesome, + '重新生成', + () { + Get.bottomSheet( + const GenerateBottomSheet(), + isScrollControlled: true, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), diff --git a/lib/modules/meeting/views/tabs/summary_tab.dart b/lib/modules/meeting/views/tabs/summary_tab.dart index 995e18d42..a64103edc 100644 --- a/lib/modules/meeting/views/tabs/summary_tab.dart +++ b/lib/modules/meeting/views/tabs/summary_tab.dart @@ -8,6 +8,7 @@ import 'package:parchment/codecs.dart'; import '../../../../data/services/db/sqflite_api.dart'; import '../../../../data/services/network/api.dart'; import '../../controllers/meeting_details_controller.dart'; +import '../../utils/meeting_ui_utils.dart'; import '../bottomSheet/ai_bottom_sheet.dart'; import '../bottomSheet/generate_bottom_sheet.dart'; import '../bottomSheet/speaker_all_bottom_sheet.dart'; @@ -335,11 +336,22 @@ class _SummaryTabState extends State { ), Padding( padding: EdgeInsets.only(top: 10.w), - child: FleatherEditor( - controller: _fleatherController!, - focusNode: _focusNode, - editorKey: _editorKey, - ), + child: _controller.meetingDetails.value.tasktype < 10000 + ? FleatherEditor( + controller: _fleatherController!, + focusNode: _focusNode, + editorKey: _editorKey, + ) + : Center( + child: Text( + '总结失败,请稍后重试', + style: TextStyle( + color: MeetingUIUtils.getSecondaryTextColor( + isDarkMode), + fontSize: 14.sp, + ), + ), + ), ), ], ), @@ -354,7 +366,8 @@ class _SummaryTabState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (_controller.meetingDetails.value.tasktype >= 3) + if (_controller.meetingDetails.value.tasktype >= 3 && + _controller.meetingDetails.value.tasktype < 10000) _button( Icons.supervisor_account, 'editSpeaker'.tr, // 对应中文:编辑发言者 @@ -372,7 +385,8 @@ class _SummaryTabState extends State { ); }, ), - if (_controller.meetingDetails.value.tasktype >= 3) + if (_controller.meetingDetails.value.tasktype >= 3 && + _controller.meetingDetails.value.tasktype < 10000) _button( Icons.auto_awesome, 'Ask AI', @@ -412,6 +426,52 @@ class _SummaryTabState extends State { }, ), ), + if (_controller.meetingDetails.value.tasktype == 10002) + SizedBox( + width: 150.w, + child: _button( + Icons.auto_awesome, + '重新生成', + () { + Get.bottomSheet( + const GenerateBottomSheet(), + isScrollControlled: true, + backgroundColor: isDarkMode + ? Colors.grey[800] + : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + ), + ), + if (_controller.meetingDetails.value.tasktype == 10001) + SizedBox( + width: 150.w, + child: _button( + Icons.auto_awesome, + '重新总结', + () { + Get.bottomSheet( + const GenerateBottomSheet( + isSummarizeAgain: true, + ), + isScrollControlled: true, + backgroundColor: isDarkMode + ? Colors.grey[800] + : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + ), + ), ], ), ), diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt index 28712bf55..cbf330fe2 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt @@ -80,7 +80,16 @@ object BleAgent : BleService.Callback, AgentServiceListener { AgentService.interruptCurrentResponse() return AgentService.startRecognition(true, "ble_wakeup") } - + //开始AI单次对话 + fun startSingleDialog(): Boolean { + if (!isInitialized) { + Log.e(TAG, "语音代理未初始化") + return false + } + Log.d(TAG, "手动启动AI单次对话") + AgentService.interruptCurrentResponse() + return AgentService.startRecognition(true, "push_to_talk") + } /** * 停止语音识别 */ @@ -171,6 +180,19 @@ object BleAgent : BleService.Callback, AgentServiceListener { //stopListening() startListening() } + /** + * 处理开始 AI 单次对话 + */ + override fun onStartSingleDialogReceived() { + Log.d(TAG, "liwei-------------- 收到开始 AI 单次对话指令") + startSingleDialog() + } + + override fun onEndSingleDialogReceived() { + Log.d(TAG, "liwei-------------- 收到结束 AI 单次对话指令") + // 停止语音识别 + stopListening() + } override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { // 不处理设备信息 diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 98c3d4685..1050f1b19 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -2035,6 +2035,14 @@ extension AgentServiceImpl: BleService.Callback { } startRecognition(useBle: true, mode: "ble_wakeup") } + // 处理开始 AI 单次对话 + func onStartSingleDialogReceived() { + startRecognition(useBle: true, mode: "push_to_talk") + } + // 处理结束 AI 单次对话 + func onEndSingleDialogReceived() { + stopRecognition() + } func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) { } diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index ff536ae4a..a080f4562 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt @@ -1229,6 +1229,26 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { override fun onWakeupSignalReceived() { } + + /** + * 开始 AI 单次对话回调 + * @throws Exception 无特殊异常抛出 + * @return 无返回值 + * @param 无参数 + */ + override fun onStartSingleDialogReceived() { + // 最小实现:当前插件不处理开始单次对话事件 + } + + /** + * 结束 AI 单次对话回调 + * @throws Exception 无特殊异常抛出 + * @return 无返回值 + * @param 无参数 + */ + override fun onEndSingleDialogReceived() { + // 最小实现:当前插件不处理结束单次对话事件 + } override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { // 不处理设备信息 diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift index 0c065cedf..d6324d393 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift @@ -1150,6 +1150,14 @@ extension AzureSpeechPlugin: BleService.Callback { /// 唤醒信号接收回调 public func onWakeupSignalReceived() { + } + // 处理开始 AI 单次对话 + public func onStartSingleDialogReceived() { + + } + // 处理结束 AI 单次对话 + public func onEndSingleDialogReceived() { + } /// 设备信息接收回调 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt index 9fb6f1dcb..ed0f64736 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt @@ -84,7 +84,16 @@ class BleCommandSender { * 通知唤醒信号接收 */ fun notifyWakeupSignalReceived() + + /** + * 通知开始 AI 单次对话 + */ + fun notifyStartSingleDialogReceived() + /** + * 通知结束 AI 单次对话 + */ + fun notifyEndSingleDialogReceived() } private var callback: CommandSenderCallback? = null @@ -546,6 +555,16 @@ class BleCommandSender { callback?.notifyWakeupSignalReceived() } + BleConst.CMD_START_SINGLE_DIALOG -> { + Log.i(TAG, "收到开始 AI 单次对话指令") + // 接收到开始 AI 单次对话信号,打开编码器 (设备侧) + callback?.notifyStartSingleDialogReceived() + } + BleConst.CMD_END_SINGLE_DIALOG -> { + Log.i(TAG, "收到结束 AI 单次对话指令") + // 接收到结束 AI 单次对话信号,关闭编码器 (设备侧) + callback?.notifyEndSingleDialogReceived() + } // 可以在这里添加其他主动上报类型的处理 else -> { // 对于未知上报类型,返回原始数据 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt index 50e793c63..5b9ff99a3 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt @@ -88,6 +88,12 @@ object BleConst { const val CMD_VOLUME_DOWN = 0x07 /** 获取唤醒词版本 */ const val CMD_GET_WAKEUP_VERSION = 0x08 + /** 开始 AI 单次对话 0x09 */ + const val CMD_START_SINGLE_DIALOG = 0x09 + /**结束 Al 单次对话 0x10 */ + const val CMD_END_SINGLE_DIALOG = 0x10 + + /** 唤醒指令 */ const val CMD_WAKE_UP = 0x11 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 5e8e22b56..2daa1d6e1 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -53,6 +53,12 @@ object BleService { // 唤醒信号相关回调 fun onWakeupSignalReceived() + + // 开始 AI 单次对话相关回调 + fun onStartSingleDialogReceived() + + // 结束 AI 单次对话相关回调 + fun onEndSingleDialogReceived() // 设备信息相关回调 - 统一回调接口 fun onDeviceInfoReceived(infoType: Int, infoData: Map) @@ -338,7 +344,21 @@ object BleService { this@BleService.notifyWakeupSignalReceived() } - + /** + * 通知开始 AI 单次对话 + */ + override fun notifyStartSingleDialogReceived() { + // 使用外部类的方法来分发开始 AI 单次对话信号,避免无限递归 + this@BleService.notifyStartSingleDialogReceived() + } + + /** + * 通知结束 AI 单次对话 + */ + override fun notifyEndSingleDialogReceived() { + // 使用外部类的方法来分发结束 AI 单次对话信号,避免无限递归 + this@BleService.notifyEndSingleDialogReceived() + } }) // 初始化唤醒词升级指令发送器 @@ -1852,7 +1872,33 @@ object BleService { } } } - + + /** + * 向所有回调监听器分发开始 AI 单次对话 + */ + private fun notifyStartSingleDialogReceived() { + for (callback in callbacks) { + try { + callback.onStartSingleDialogReceived() + } catch (e: Exception) { + Log.e(TAG, "分发开始 AI 单次对话回调异常", e) + } + } + } + + /** + * 向所有回调监听器分发结束 AI 单次对话 + */ + private fun notifyEndSingleDialogReceived() { + for (callback in callbacks) { + try { + callback.onEndSingleDialogReceived() + } catch (e: Exception) { + Log.e(TAG, "分发结束 AI 单次对话回调异常", e) + } + } + } + /** * 向所有回调监听器分发设备信息 */ diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index 0ef8ddcbe..4983cf65e 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -389,6 +389,16 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, // 将唤醒事件发送到Flutter sendEvent(statusEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常") } + // 处理开始 AI 单次对话 + override fun onStartSingleDialogReceived() { + // 将开始对话事件发送到Flutter + sendEvent(statusEventSink, mapOf("type" to "startSingleDialog"), "发送开始对话异常") + } + // 处理结束 AI 单次对话 + override fun onEndSingleDialogReceived() { + // 将结束对话事件发送到Flutter + sendEvent(statusEventSink, mapOf("type" to "endSingleDialog"), "发送结束对话异常") + } override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { // 设备信息通过状态通道发送 diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift index 1ac452c00..c4936bb7e 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift @@ -85,6 +85,11 @@ class BleConst { /** A2DP播放 */ static let CMD_PLAY_A2DP: UInt8 = 0x08 + /*开始 AI 单次对话4 90x09*/ + static let CMD_START_SINGLE_DIALOG: UInt8 = 0x09 + /*结束 Al 单次对话 0x10*/ + static let CMD_END_SINGLE_DIALOG: UInt8 = 0x10 + /** 唤醒指令 */ static let CMD_WAKE_UP: UInt8 = 0x11 diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift index d757e18ca..ad0326ce2 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift @@ -33,6 +33,12 @@ public class BleService: NSObject { // 唤醒信号回调 func onWakeupSignalReceived() + // 开始 AI 单次对话回调 + func onStartSingleDialogReceived() + + // 结束 AI 单次对话回调 + func onEndSingleDialogReceived() + // 设备信息相关回调 func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) } @@ -592,6 +598,20 @@ private var cmdReplyType: UInt8 = 0 } } + /// 通知所有回调接收到开始 AI 单次对话信号 + private func notifyStartSingleDialogReceived() { + for callback in callbacks { + callback.onStartSingleDialogReceived() + } + } + + /// 通知所有回调接收到结束 AI 单次对话信号 + private func notifyEndSingleDialogReceived() { + for callback in callbacks { + callback.onEndSingleDialogReceived() + } + } + /// 通知所有回调接收到设备信息 private func notifyDeviceInfoReceived(infoType: Int, infoData: [String: Any]) { for callback in callbacks { @@ -1219,7 +1239,12 @@ private var cmdReplyType: UInt8 = 0 notifyWakeupSignalReceived() - + case BleConst.CMD_START_SINGLE_DIALOG: + os_log("📢 收到开始 AI 单次对话指令", log: logger, type: .info) + notifyStartSingleDialogReceived() + case BleConst.CMD_END_SINGLE_DIALOG: + os_log("📢 收到结束 AI 单次对话指令", log: logger, type: .info) + notifyEndSingleDialogReceived() default: // 对于未知上报类型,返回原始数据 let resultMap: [String: Any] = [ diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift index 8a706ff10..c75f8bf5d 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift @@ -251,6 +251,26 @@ extension SwiftBleServicePlugin: BleService.Callback { } } } + // 处理开始 AI 单次对话 + public func onStartSingleDialogReceived() { + if let eventSink = statusEventSink { + DispatchQueue.main.async { + eventSink([ + "type": "startSingleDialog" + ]) + } + } + } + // 处理结束 AI 单次对话 + public func onEndSingleDialogReceived() { + if let eventSink = statusEventSink { + DispatchQueue.main.async { + eventSink([ + "type": "endSingleDialog" + ]) + } + } + } public func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) { if let eventSink = statusEventSink { diff --git a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt b/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt index 58e791daa..d188fe30c 100644 --- a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt +++ b/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt @@ -390,7 +390,14 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler { override fun onWakeupSignalReceived() { } - + // 处理开始 AI 单次对话 + override fun onStartSingleDialogReceived() { + // 不处理开始对话 + } + // 处理结束 AI 单次对话 + override fun onEndSingleDialogReceived() { + // 不处理结束对话 + } override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { // 不处理设备信息 }