From 3435658dbbe9c263b4bec3d84505a8afd1f5f3bd Mon Sep 17 00:00:00 2001 From: Wilson Date: Thu, 12 Dec 2024 14:04:36 -0500 Subject: [PATCH] disable updating bot config if insufficient power level, remove respond on next reactions input (#1228) * disable updating bot config if insufficient power level, remove respond on next reactions input * comment .env file in pubspec.yaml --------- Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com> Co-authored-by: ggurdin --- assets/l10n/intl_en.arb | 3 +- assets/l10n/intl_es.arb | 1 - .../conversation_bot_mode_dynamic_zone.dart | 110 +++++++++--------- .../conversation_bot_mode_select.dart | 2 +- ...conversation_bot_no_permission_dialog.dart | 20 ++++ .../conversation_bot_settings.dart | 50 +++++--- .../conversation_bot_settings_form.dart | 107 ++++++++++------- 7 files changed, 176 insertions(+), 117 deletions(-) create mode 100644 lib/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index de95f0f28..818a5b526 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4040,8 +4040,9 @@ "conversationBotCustomZone_customSystemPromptLabel": "System prompt", "conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt", "conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt", - "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction", "botConfig": "Bot and activity settings", + "botConfigNoPermissionTitle": "No permission", + "botConfigNoPermissionMessage": "Contact room admin to change bot configuration", "addConversationBotDialogTitleInvite": "Confirm inviting conversation bot", "addConversationBotButtonInvite": "Invite", "addConversationBotDialogInviteConfirmation": "Invite", diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index fb76642f6..a0ea9ca67 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4816,7 +4816,6 @@ "conversationBotCustomZone_customSystemPromptLabel": "Mensaje del sistema", "conversationBotCustomZone_customSystemPromptPlaceholder": "Establecer mensaje del sistema personalizado", "conversationBotCustomZone_customSystemPromptEmptyError": "Falta mensaje del sistema personalizado", - "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responde a la reacción ⏩", "addConversationBotDialogTitleInvite": "Confirmar la invitación del bot de conversación", "addConversationBotButtonInvite": "Invitar", "addConversationBotDialogInviteConfirmation": "Invitar", diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart index 7b2a744b7..5f4e1bd12 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/pangea/constants/bot_mode.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -9,12 +10,14 @@ class ConversationBotModeDynamicZone extends StatelessWidget { final TextEditingController customSystemPromptController; final bool enabled; + final bool hasPermission; const ConversationBotModeDynamicZone({ super.key, required this.discussionTopicController, required this.discussionKeywordsController, required this.customSystemPromptController, + required this.hasPermission, this.enabled = true, this.mode, }); @@ -22,59 +25,68 @@ class ConversationBotModeDynamicZone extends StatelessWidget { @override Widget build(BuildContext context) { final discussionChildren = [ - TextFormField( - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - decoration: InputDecoration( - hintText: L10n.of(context) - .conversationBotDiscussionZone_discussionTopicPlaceholder, - contentPadding: - const EdgeInsets.symmetric(horizontal: 28.0, vertical: 12.0), + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: TextFormField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + decoration: InputDecoration( + hintText: L10n.of(context) + .conversationBotDiscussionZone_discussionTopicPlaceholder, + contentPadding: + const EdgeInsets.symmetric(horizontal: 28.0, vertical: 12.0), + ), + controller: discussionTopicController, + validator: (value) => enabled && + mode == BotMode.discussion && + (value == null || value.isEmpty) + ? L10n.of(context).enterDiscussionTopic + : null, + enabled: hasPermission && enabled, + minLines: 1, // Minimum number of lines + maxLines: null, // Allow the field to expand based on content + keyboardType: TextInputType.multiline, ), - controller: discussionTopicController, - validator: (value) => enabled && - mode == BotMode.discussion && - (value == null || value.isEmpty) - ? L10n.of(context).enterDiscussionTopic - : null, - enabled: enabled, - minLines: 1, // Minimum number of lines - maxLines: null, // Allow the field to expand based on content - keyboardType: TextInputType.multiline, ), const SizedBox(height: 12), - TextFormField( - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - decoration: InputDecoration( - hintText: L10n.of(context) - .conversationBotDiscussionZone_discussionKeywordsPlaceholder, - contentPadding: const EdgeInsets.symmetric(horizontal: 28.0), + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: TextFormField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + decoration: InputDecoration( + hintText: L10n.of(context) + .conversationBotDiscussionZone_discussionKeywordsPlaceholder, + contentPadding: const EdgeInsets.symmetric(horizontal: 28.0), + ), + controller: discussionKeywordsController, + enabled: hasPermission && enabled, + minLines: 1, // Minimum number of lines + maxLines: null, // Allow the field to expand based on content + keyboardType: TextInputType.multiline, ), - controller: discussionKeywordsController, - enabled: enabled, - minLines: 1, // Minimum number of lines - maxLines: null, // Allow the field to expand based on content - keyboardType: TextInputType.multiline, ), ]; final customChildren = [ - TextFormField( - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - decoration: InputDecoration( - hintText: L10n.of(context) - .conversationBotCustomZone_customSystemPromptPlaceholder, - contentPadding: const EdgeInsets.symmetric(horizontal: 28.0), + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: TextFormField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + decoration: InputDecoration( + hintText: L10n.of(context) + .conversationBotCustomZone_customSystemPromptPlaceholder, + contentPadding: const EdgeInsets.symmetric(horizontal: 28.0), + ), + validator: (value) => enabled && + mode == BotMode.custom && + (value == null || value.isEmpty) + ? L10n.of(context).enterPrompt + : null, + controller: customSystemPromptController, + enabled: hasPermission && enabled, + minLines: 1, // Minimum number of lines + maxLines: null, // Allow the field to expand based on content + keyboardType: TextInputType.multiline, ), - validator: (value) => enabled && - mode == BotMode.custom && - (value == null || value.isEmpty) - ? L10n.of(context).enterPrompt - : null, - controller: customSystemPromptController, - enabled: enabled, - minLines: 1, // Minimum number of lines - maxLines: null, // Allow the field to expand based on content - keyboardType: TextInputType.multiline, ), ]; @@ -83,16 +95,6 @@ class ConversationBotModeDynamicZone extends StatelessWidget { if (mode == BotMode.discussion) ...discussionChildren, if (mode == BotMode.custom) ...customChildren, const SizedBox(height: 12), - CheckboxListTile( - title: Text( - L10n.of(context) - .conversationBotCustomZone_customTriggerReactionEnabledLabel, - ), - enabled: false, - value: true, - onChanged: null, - ), - const SizedBox(height: 12), ], ); } diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart index fee13e627..dbc32b1eb 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart @@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; class ConversationBotModeSelect extends StatelessWidget { final String? initialMode; - final void Function(String?) onChanged; + final void Function(String?)? onChanged; final bool enabled; final String? Function(String?)? validator; diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart new file mode 100644 index 000000000..4ec90782b --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +Future showNoPermissionDialog(BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(L10n.of(context).botConfigNoPermissionTitle), + content: Text(L10n.of(context).botConfigNoPermissionMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(L10n.of(context).ok), + ), + ], + ); + }, + ); +} diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index d04a149c5..e9088c100 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; @@ -113,6 +114,7 @@ class ConversationBotSettingsDialogState TextEditingController(); bool hasUpdatedMode = false; + bool hasPermission = false; @override void initState() { @@ -124,6 +126,9 @@ class ConversationBotSettingsDialogState widget.room.botIsInRoom.then((bool isBotRoom) { setState(() => addBot = isBotRoom); }); + hasPermission = + widget.room.powerForChangingStateEvent(PangeaEventTypes.botOptions) <= + widget.room.ownPowerLevel; discussionKeywordsController.text = botOptions.discussionKeywords ?? ""; discussionTopicController.text = botOptions.discussionTopic ?? ""; @@ -193,15 +198,22 @@ class ConversationBotSettingsDialogState ), ], ), - SwitchListTile( - title: Text( - L10n.of(context).conversationBotStatus, + InkWell( + onTap: hasPermission + ? null + : () => showNoPermissionDialog(context), + child: SwitchListTile( + title: Text( + L10n.of(context).conversationBotStatus, + ), + value: addBot, + onChanged: hasPermission + ? (bool value) { + setState(() => addBot = value); + } + : null, // Keeps the switch disabled + contentPadding: const EdgeInsets.all(4), ), - value: addBot, - onChanged: (bool value) { - setState(() => addBot = value); - }, - contentPadding: const EdgeInsets.all(4), ), Expanded( child: SingleChildScrollView( @@ -221,6 +233,7 @@ class ConversationBotSettingsDialogState discussionTopicController, customSystemPromptController: customSystemPromptController, + hasPermission: hasPermission, enabled: addBot, hasUpdatedMode: hasUpdatedMode, onUpdateBotMode: onUpdateChatMode, @@ -237,15 +250,20 @@ class ConversationBotSettingsDialogState Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(null); - }, - child: Text(L10n.of(context).cancel), - ), + if (hasPermission) + TextButton( + onPressed: () { + Navigator.of(context).pop(null); + }, + child: Text(L10n.of(context).cancel), + ), const SizedBox(width: 20), TextButton( onPressed: () async { + if (!hasPermission) { + Navigator.of(context).pop(null); + return; + } final isValid = formKey.currentState!.validate(); if (!isValid) return; @@ -263,7 +281,9 @@ class ConversationBotSettingsDialogState await widget.room.kick(BotName.byEnvironment); } }, - child: Text(L10n.of(context).confirm), + child: hasPermission + ? Text(L10n.of(context).confirm) + : Text(L10n.of(context).close), ), ], ), diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart index dc64da050..93e9b890e 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart @@ -2,6 +2,7 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart'; import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -16,6 +17,7 @@ class ConversationBotSettingsForm extends StatelessWidget { final bool enabled; final bool hasUpdatedMode; + final bool hasPermission; final void Function(String?) onUpdateBotMode; final void Function(String?) onUpdateBotLanguage; @@ -32,6 +34,7 @@ class ConversationBotSettingsForm extends StatelessWidget { required this.onUpdateBotLanguage, required this.onUpdateBotVoice, required this.onUpdateBotLanguageLevel, + required this.hasPermission, this.enabled = true, this.hasUpdatedMode = false, }); @@ -40,50 +43,60 @@ class ConversationBotSettingsForm extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - DropdownButtonFormField2( - dropdownStyleData: const DropdownStyleData( - padding: EdgeInsets.zero, + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: DropdownButtonFormField2( + dropdownStyleData: const DropdownStyleData( + padding: EdgeInsets.zero, + ), + hint: Text( + L10n.of(context).selectBotLanguage, + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + ), + value: botOptions.targetLanguage, + isExpanded: true, + items: MatrixState.pangeaController.pLanguageStore.targetOptions + .map((language) { + return DropdownMenuItem( + value: language.langCode, + child: Text( + language.getDisplayName(context) ?? language.langCode, + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + ), + ); + }).toList(), + onChanged: hasPermission && enabled ? onUpdateBotLanguage : null, ), - hint: Text( - L10n.of(context).selectBotLanguage, - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - value: botOptions.targetLanguage, - isExpanded: true, - items: MatrixState.pangeaController.pLanguageStore.targetOptions - .map((language) { - return DropdownMenuItem( - value: language.langCode, - child: Text( - language.getDisplayName(context) ?? language.langCode, - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - ); - }).toList(), - onChanged: enabled ? onUpdateBotLanguage : null, ), const SizedBox(height: 12), - DropdownButtonFormField2( - hint: Text( - L10n.of(context).chooseVoice, - overflow: TextOverflow.clip, - textAlign: TextAlign.center, + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: DropdownButtonFormField2( + hint: Text( + L10n.of(context).chooseVoice, + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + ), + value: botOptions.targetVoice, + isExpanded: true, + items: const [], + onChanged: hasPermission && enabled ? onUpdateBotVoice : null, ), - value: botOptions.targetVoice, - isExpanded: true, - items: const [], - onChanged: enabled ? onUpdateBotVoice : null, ), const SizedBox(height: 12), - LanguageLevelDropdown( - initialLevel: botOptions.languageLevel, - onChanged: onUpdateBotLanguageLevel, - validator: (value) => enabled && value == null - ? L10n.of(context).enterLanguageLevel - : null, - enabled: enabled, + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: + hasPermission && enabled ? onUpdateBotLanguageLevel : null, + validator: (value) => enabled && value == null + ? L10n.of(context).enterLanguageLevel + : null, + enabled: enabled, + ), ), const SizedBox(height: 12), Align( @@ -96,13 +109,16 @@ class ConversationBotSettingsForm extends StatelessWidget { ), ), ), - ConversationBotModeSelect( - initialMode: hasUpdatedMode ? botOptions.mode : null, - onChanged: onUpdateBotMode, - enabled: enabled, - validator: (value) { - return value == null ? L10n.of(context).botModeValidation : null; - }, + InkWell( + onTap: hasPermission ? null : () => showNoPermissionDialog(context), + child: ConversationBotModeSelect( + initialMode: hasUpdatedMode ? botOptions.mode : null, + onChanged: hasPermission && enabled ? onUpdateBotMode : null, + enabled: enabled, + validator: (value) { + return value == null ? L10n.of(context).botModeValidation : null; + }, + ), ), const SizedBox(height: 12), ConversationBotModeDynamicZone( @@ -110,6 +126,7 @@ class ConversationBotSettingsForm extends StatelessWidget { discussionKeywordsController: discussionKeywordsController, customSystemPromptController: customSystemPromptController, enabled: enabled, + hasPermission: hasPermission, mode: hasUpdatedMode ? botOptions.mode : null, ), ],