diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 3f7e5fa45..0ca865540 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -1,6 +1,3 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; @@ -12,12 +9,9 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/settings/settings.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/chat_settings/pages/pangea_room_details.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/download/download_room_extension.dart'; import 'package:fluffychat/pangea/download/download_type_enum.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/join_rule_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/file_selector.dart'; @@ -269,31 +263,6 @@ class ChatDetailsController extends State { } } - Future setBotOptions(BotOptionsModel botOptions) async { - if (roomId == null) return; - final Room? room = Matrix.of(context).client.getRoomById(roomId!); - if (room == null) return; - - try { - await Matrix.of(context).client.setRoomStateWithKey( - room.id, - PangeaEventTypes.botOptions, - '', - botOptions.toJson(), - ); - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError( - e: err, - s: stack, - data: { - "botOptions": botOptions.toJson(), - "roomID": room.id, - }, - ); - } - } - Future setRoomCapacity() async { if (roomId == null) return; final Room? room = Matrix.of(context).client.getRoomById(roomId!); diff --git a/lib/pangea/bot/utils/bot_room_extension.dart b/lib/pangea/bot/utils/bot_room_extension.dart new file mode 100644 index 000000000..eef48a933 --- /dev/null +++ b/lib/pangea/bot/utils/bot_room_extension.dart @@ -0,0 +1,21 @@ +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; + +extension BotRoomExtension on Room { + BotOptionsModel? get botOptions { + if (isSpace) return null; + final stateEvent = getState(PangeaEventTypes.botOptions); + if (stateEvent == null) return null; + return BotOptionsModel.fromJson(stateEvent.content); + } + + Future setBotOptions(BotOptionsModel options) => + client.setRoomStateWithKey( + id, + PangeaEventTypes.botOptions, + '', + options.toJson(), + ); +} diff --git a/lib/pangea/chat_settings/models/bot_options_model.dart b/lib/pangea/chat_settings/models/bot_options_model.dart index b5f490957..0bc2779f9 100644 --- a/lib/pangea/chat_settings/models/bot_options_model.dart +++ b/lib/pangea/chat_settings/models/bot_options_model.dart @@ -2,12 +2,9 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; class BotOptionsModel { @@ -182,9 +179,4 @@ class BotOptionsModel { throw Exception('Invalid key for bot options - $key'); } } - - StateEvent get toStateEvent => StateEvent( - content: toJson(), - type: PangeaEventTypes.botOptions, - ); } diff --git a/lib/pangea/chat_settings/pages/chat_details_button_row.dart b/lib/pangea/chat_settings/pages/chat_details_button_row.dart index c3c9dad16..f2d2dd6f5 100644 --- a/lib/pangea/chat_settings/pages/chat_details_button_row.dart +++ b/lib/pangea/chat_settings/pages/chat_details_button_row.dart @@ -10,11 +10,8 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; -import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/chat_settings/pages/room_details_buttons.dart'; import 'package:fluffychat/pangea/chat_settings/utils/delete_room.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -116,23 +113,6 @@ class ChatDetailsButtonRowState extends State { enabled: room.ownPowerLevel >= 50, showInMainView: false, ), - ButtonDetails( - title: l10n.botSettings, - icon: const BotFace( - width: 30.0, - expression: BotExpression.idle, - ), - onPressed: () => showDialog( - context: context, - builder: (BuildContext context) => ConversationBotSettingsDialog( - room: room, - onSubmit: widget.controller.setBotOptions, - ), - ), - visible: (!room.isDirectChat || room.botOptions != null) && - !room.showActivityChatUI, - enabled: room.canInvite, - ), ButtonDetails( title: l10n.chatCapacity, icon: const Icon(Icons.reduce_capacity, size: 30.0), diff --git a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart index c25359442..601dfe16a 100644 --- a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart +++ b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart @@ -194,10 +194,6 @@ class PangeaInvitationSelectionController if (a.id == client.userID) return -1; if (b.id == client.userID) return 1; - // sort the bot to the bottom - if (a.id == BotName.byEnvironment) return 1; - if (b.id == BotName.byEnvironment) return -1; - if (participants != null) { final participantA = participants!.firstWhereOrNull((u) => u.id == a.id); final participantB = participants!.firstWhereOrNull((u) => u.id == b.id); @@ -268,10 +264,8 @@ class PangeaInvitationSelectionController ) .toList(); + contacts.removeWhere((u) => u.id == BotName.byEnvironment); contacts.sort(_sortUsers); - if (_room?.isSpace ?? false) { - contacts.removeWhere((u) => u.id == BotName.byEnvironment); - } return contacts; } @@ -347,9 +341,7 @@ class PangeaInvitationSelectionController } final results = response.results; - if (_room?.isSpace ?? false) { - results.removeWhere((profile) => profile.userId == BotName.byEnvironment); - } + results.removeWhere((profile) => profile.userId == BotName.byEnvironment); setState(() { foundProfiles = List.from(results); @@ -372,8 +364,8 @@ class PangeaInvitationSelectionController foundProfiles.removeWhere( (profile) => - participants?.indexWhere((u) => u.id == profile.userId) != -1 && - BotName.byEnvironment != profile.userId, + participants?.indexWhere((u) => u.id == profile.userId) != -1 || + BotName.byEnvironment == profile.userId, ); }); } diff --git a/lib/pangea/chat_settings/utils/bot_client_extension.dart b/lib/pangea/chat_settings/utils/bot_client_extension.dart index d1aae75f3..23f39b036 100644 --- a/lib/pangea/chat_settings/utils/bot_client_extension.dart +++ b/lib/pangea/chat_settings/utils/bot_client_extension.dart @@ -1,10 +1,13 @@ +import 'package:collection/collection.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; +import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; +import 'package:fluffychat/widgets/matrix.dart'; extension BotClientExtension on Client { bool get hasBotDM => rooms.any((room) { @@ -18,11 +21,33 @@ extension BotClientExtension on Client { return false; }); + Room? get botDM => rooms.firstWhereOrNull( + (room) { + if (room.isDirectChat && + room.directChatMatrixID == BotName.byEnvironment) { + return true; + } + if (room.botOptions?.mode == BotMode.directChat) { + return true; + } + return false; + }, + ); + Future startChatWithBot() => startDirectChat( BotName.byEnvironment, preset: CreateRoomPreset.trustedPrivateChat, initialState: [ - BotOptionsModel(mode: BotMode.directChat).toStateEvent, + StateEvent( + content: BotOptionsModel( + mode: BotMode.directChat, + targetLanguage: MatrixState + .pangeaController.languageController.userL2?.langCode, + languageLevel: MatrixState.pangeaController.userController.profile + .userSettings.cefrLevel, + ).toJson(), + type: PangeaEventTypes.botOptions, + ), RoomDefaults.defaultPowerLevels( userID!, ), diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_conversation_zone.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_conversation_zone.dart deleted file mode 100644 index 975cc1d0d..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_conversation_zone.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class ConversationBotConversationZone extends StatelessWidget { - const ConversationBotConversationZone({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return const Column( - children: [ - Text('Conversation Zone'), - ], - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart deleted file mode 100644 index 0735f7e98..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart'; - -class ConversationBotModeDynamicZone extends StatelessWidget { - final String? mode; - final TextEditingController discussionTopicController; - final TextEditingController discussionKeywordsController; - 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, - }); - - @override - Widget build(BuildContext context) { - final discussionChildren = [ - InkWell( - onTap: hasPermission ? null : () => showNoPermissionDialog(context), - child: TextFormField( - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - decoration: InputDecoration( - hintText: L10n.of(context) - .conversationBotDiscussionZone_discussionTopicPlaceholder, - ), - 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, - ), - ), - const SizedBox(height: 12), - InkWell( - onTap: hasPermission ? null : () => showNoPermissionDialog(context), - child: TextFormField( - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - decoration: InputDecoration( - hintText: L10n.of(context) - .conversationBotDiscussionZone_discussionKeywordsPlaceholder, - ), - controller: discussionKeywordsController, - enabled: hasPermission && enabled, - minLines: 1, // Minimum number of lines - maxLines: null, // Allow the field to expand based on content - keyboardType: TextInputType.multiline, - ), - ), - ]; - - final customChildren = [ - 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, - ), - ), - ]; - - return Column( - children: [ - if (mode == BotMode.discussion) ...discussionChildren, - if (mode == BotMode.custom) ...customChildren, - const SizedBox(height: 12), - ], - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_select.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_select.dart deleted file mode 100644 index ed693024f..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_mode_select.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:dropdown_button2/dropdown_button2.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; -import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart'; - -class ConversationBotModeSelect extends StatelessWidget { - final String? initialMode; - final void Function(String?)? onChanged; - final bool enabled; - final String? Function(String?)? validator; - - const ConversationBotModeSelect({ - super.key, - this.initialMode, - required this.onChanged, - this.enabled = true, - this.validator, - }); - - @override - Widget build(BuildContext context) { - final Map options = { - BotMode.discussion: - L10n.of(context).conversationBotModeSelectOption_discussion, - BotMode.custom: L10n.of(context).conversationBotModeSelectOption_custom, - // BotMode.textAdventure: - // L10n.of(context).conversationBotModeSelectOption_textAdventure, - // BotMode.storyGame: - // L10n.of(context).conversationBotModeSelectOption_storyGame, - }; - - return DropdownButtonFormField2( - customButton: initialMode != null && options.containsKey(initialMode) - ? CustomDropdownTextButton( - text: options[initialMode]!, - ) - : null, - menuItemStyleData: const MenuItemStyleData( - padding: EdgeInsets.zero, // Remove default padding - ), - isExpanded: true, - dropdownStyleData: DropdownStyleData( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - ), - ), - hint: Text(L10n.of(context).selectBotChatMode), - items: [ - for (final entry in options.entries) - DropdownMenuItem( - value: entry.key, - child: DropdownTextButton( - text: entry.value, - isSelected: initialMode == entry.key, - ), - ), - ], - onChanged: enabled ? onChanged : null, - validator: validator, - value: initialMode, - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart deleted file mode 100644 index 8a9a118ba..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/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/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart deleted file mode 100644 index ee112f998..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart +++ /dev/null @@ -1,306 +0,0 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; -import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart'; -import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_no_permission_dialog.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings_form.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class ConversationBotSettings extends StatefulWidget { - final Room room; - - const ConversationBotSettings({ - super.key, - required this.room, - }); - - @override - ConversationBotSettingsState createState() => ConversationBotSettingsState(); -} - -class ConversationBotSettingsState extends State { - Future setBotOptions(BotOptionsModel botOptions) async { - try { - await Matrix.of(context).client.setRoomStateWithKey( - widget.room.id, - PangeaEventTypes.botOptions, - '', - botOptions.toJson(), - ); - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError( - e: err, - s: stack, - data: { - "botOptions": botOptions.toJson(), - "roomID": widget.room.id, - }, - ); - } - } - - @override - Widget build(BuildContext context) { - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - title: Text( - L10n.of(context).botConfig, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Text(L10n.of(context).botSettingsSubtitle), - leading: CircleAvatar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - foregroundColor: Theme.of(context).textTheme.bodyLarge!.color, - child: const BotFace( - width: 30.0, - expression: BotExpression.idle, - ), - ), - onTap: () => showDialog( - context: context, - builder: (BuildContext context) => ConversationBotSettingsDialog( - room: widget.room, - onSubmit: setBotOptions, - ), - ), - ), - ], - ), - ); - } -} - -class ConversationBotSettingsDialog extends StatefulWidget { - final Room room; - final Function(BotOptionsModel) onSubmit; - - const ConversationBotSettingsDialog({ - super.key, - required this.room, - required this.onSubmit, - }); - - @override - ConversationBotSettingsDialogState createState() => - ConversationBotSettingsDialogState(); -} - -class ConversationBotSettingsDialogState - extends State { - late BotOptionsModel botOptions; - bool addBot = false; - - final TextEditingController discussionTopicController = - TextEditingController(); - final TextEditingController discussionKeywordsController = - TextEditingController(); - final TextEditingController customSystemPromptController = - TextEditingController(); - - bool hasUpdatedMode = false; - bool hasPermission = false; - - @override - void initState() { - super.initState(); - botOptions = widget.room.botOptions != null - ? BotOptionsModel.fromJson(widget.room.botOptions?.toJson()) - : BotOptionsModel(); - - botOptions.targetLanguage ??= - MatrixState.pangeaController.languageController.userL2?.langCode; - - 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 ?? ""; - customSystemPromptController.text = botOptions.customSystemPrompt ?? ""; - - hasUpdatedMode = widget.room.botOptions != null; - } - - final GlobalKey formKey = GlobalKey(); - - void updateFromTextControllers() { - botOptions.discussionTopic = discussionTopicController.text; - botOptions.discussionKeywords = discussionKeywordsController.text; - botOptions.customSystemPrompt = customSystemPromptController.text; - } - - void onUpdateChatMode(String? mode) { - hasUpdatedMode = true; - setState(() => botOptions.mode = mode ?? BotMode.discussion); - } - - void onUpdateBotLanguage(String? language) { - setState(() => botOptions.targetLanguage = language); - } - - void onUpdateBotVoice(String? voice) { - setState(() => botOptions.targetVoice = voice); - } - - void onUpdateBotLanguageLevel(LanguageLevelTypeEnum? level) { - setState(() => botOptions.languageLevel = level!); - } - - @override - Widget build(BuildContext context) { - final dialogContent = Form( - key: formKey, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12, - ), - child: Text( - L10n.of(context).botConfig, - style: Theme.of(context).textTheme.titleLarge, - ), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(null), - ), - ], - ), - 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), - ), - ), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - children: [ - const SizedBox(height: 20), - AnimatedOpacity( - duration: FluffyThemes.animationDuration, - opacity: addBot ? 1.0 : 0.5, - child: ConversationBotSettingsForm( - botOptions: botOptions, - discussionKeywordsController: - discussionKeywordsController, - discussionTopicController: discussionTopicController, - customSystemPromptController: - customSystemPromptController, - hasPermission: hasPermission, - enabled: addBot, - hasUpdatedMode: hasUpdatedMode, - onUpdateBotMode: onUpdateChatMode, - onUpdateBotLanguage: onUpdateBotLanguage, - onUpdateBotVoice: onUpdateBotVoice, - onUpdateBotLanguageLevel: onUpdateBotLanguageLevel, - ), - ), - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - 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; - - updateFromTextControllers(); - botOptions.targetLanguage ??= MatrixState - .pangeaController.languageController.userL2?.langCode; - - await showFutureLoadingDialog( - context: context, - future: () async => widget.onSubmit(botOptions), - ); - - final bool isBotRoomMember = await widget.room.botIsInRoom; - if (addBot && !isBotRoomMember) { - await widget.room.invite(BotName.byEnvironment); - } else if (!addBot && isBotRoomMember) { - await widget.room.kick(BotName.byEnvironment); - } - - Navigator.of(context).pop(botOptions); - }, - child: hasPermission - ? Text(L10n.of(context).confirm) - : Text(L10n.of(context).close), - ), - ], - ), - ], - ), - ), - ); - - return FullWidthDialog( - dialogContent: dialogContent, - maxWidth: 450, - maxHeight: 400, - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings_form.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings_form.dart deleted file mode 100644 index c130eb568..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings_form.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart'; -import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; -import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; -import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class ConversationBotSettingsForm extends StatelessWidget { - final BotOptionsModel botOptions; - - final TextEditingController discussionTopicController; - final TextEditingController discussionKeywordsController; - final TextEditingController customSystemPromptController; - - final bool enabled; - final bool hasUpdatedMode; - final bool hasPermission; - - final void Function(String?) onUpdateBotMode; - final void Function(String?) onUpdateBotLanguage; - final void Function(String?) onUpdateBotVoice; - final void Function(LanguageLevelTypeEnum?) onUpdateBotLanguageLevel; - - const ConversationBotSettingsForm({ - super.key, - required this.botOptions, - required this.discussionTopicController, - required this.discussionKeywordsController, - required this.customSystemPromptController, - required this.onUpdateBotMode, - required this.onUpdateBotLanguage, - required this.onUpdateBotVoice, - required this.onUpdateBotLanguageLevel, - required this.hasPermission, - this.enabled = true, - this.hasUpdatedMode = false, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - PLanguageDropdown( - decorationText: L10n.of(context).targetLanguage, - languages: MatrixState.pangeaController.pLanguageStore.targetOptions, - onChange: (lang) => hasPermission && enabled - ? onUpdateBotLanguage(lang.langCode) - : null, - initialLanguage: botOptions.targetLanguage != null - ? PLanguageStore.byLangCode(botOptions.targetLanguage!) - : null, - enabled: enabled && hasPermission, - ), - const SizedBox(height: 12), - LanguageLevelDropdown( - initialLevel: botOptions.languageLevel, - onChanged: hasPermission && enabled - ? (value) => - onUpdateBotLanguageLevel(value as LanguageLevelTypeEnum?) - : null, - validator: (value) => enabled && value == null - ? L10n.of(context).enterLanguageLevel - : null, - enabled: enabled && hasPermission, - ), - ], - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart deleted file mode 100644 index 92d8ac7d7..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; - -class ConversationBotGameMasterInstructionsInput extends StatelessWidget { - final BotOptionsModel initialBotOptions; - // call this to update propagate changes to parents - final void Function(BotOptionsModel) onChanged; - - const ConversationBotGameMasterInstructionsInput({ - super.key, - required this.initialBotOptions, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - String gameMasterInstructions = - initialBotOptions.textAdventureGameMasterInstructions ?? ""; - - final TextEditingController textFieldController = - TextEditingController(text: gameMasterInstructions); - - final GlobalKey gameMasterInstructionsFormKey = - GlobalKey(); - - void setBotTextAdventureGameMasterInstructionsAction() async { - showDialog( - context: context, - useRootNavigator: false, - builder: (BuildContext context) => AlertDialog( - title: Text( - L10n.of(context) - .conversationBotTextAdventureZone_instructionPlaceholder, - ), - content: Form( - key: gameMasterInstructionsFormKey, - child: TextFormField( - minLines: 1, - maxLines: 10, - maxLength: 1000, - controller: textFieldController, - onChanged: (value) { - if (value.isNotEmpty) { - gameMasterInstructions = value; - } - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'This field cannot be empty'; - } - return null; - }, - ), - ), - actions: [ - TextButton( - child: Text(L10n.of(context).cancel), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text(L10n.of(context).ok), - onPressed: () { - if (gameMasterInstructionsFormKey.currentState!.validate()) { - if (gameMasterInstructions != - initialBotOptions.textAdventureGameMasterInstructions) { - initialBotOptions.textAdventureGameMasterInstructions = - gameMasterInstructions; - onChanged.call(initialBotOptions); - } - Navigator.of(context).pop(); - } - }, - ), - ], - ), - ); - } - - return ListTile( - onTap: setBotTextAdventureGameMasterInstructionsAction, - title: Text( - initialBotOptions.textAdventureGameMasterInstructions ?? - L10n.of(context) - .conversationBotTextAdventureZone_instructionPlaceholder, - ), - ); - } -} diff --git a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart b/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart deleted file mode 100644 index 08fc81375..000000000 --- a/lib/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart'; - -// TODO check how this looks -class ConversationBotTextAdventureZone extends StatelessWidget { - final BotOptionsModel initialBotOptions; - // call this to update propagate changes to parents - - final void Function(BotOptionsModel) onChanged; - const ConversationBotTextAdventureZone({ - super.key, - required this.initialBotOptions, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: ConversationBotGameMasterInstructionsInput( - initialBotOptions: initialBotOptions, - onChanged: onChanged, - ), - ), - ], - ); - } -} diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index fa7b2f611..2f3194917 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -11,6 +11,9 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; +import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart'; +import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/chat_settings/utils/bot_client_extension.dart'; import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart'; @@ -48,7 +51,8 @@ class PangeaController { ///store Services final pLanguageStore = PLanguageStore(); - StreamSubscription? _languageStream; + StreamSubscription? _languageSubscription; + StreamSubscription? _settingsSubscription; ///Matrix Variables MatrixState matrixState; @@ -57,7 +61,7 @@ class PangeaController { int? randomint; PangeaController({required this.matrix, required this.matrixState}) { _setup(); - _setLanguageSubscription(); + _setSettingsSubscriptions(); randomint = Random().nextInt(2000); } @@ -175,14 +179,16 @@ class PangeaController { // Reset cached analytics data _disposeAnalyticsControllers(); userController.clear(); - _languageStream?.cancel(); - _languageStream = null; + _languageSubscription?.cancel(); + _settingsSubscription?.cancel(); + _languageSubscription = null; + _settingsSubscription = null; _logOutfromPangea(context); break; case LoginState.loggedIn: // Initialize analytics data initControllers(); - _setLanguageSubscription(); + _setSettingsSubscriptions(); userController.reinitialize().then((_) { final l1 = userController.profile.userSettings.sourceLanguage; @@ -218,11 +224,39 @@ class PangeaController { await _initAnalyticsControllers(); } - void _setLanguageSubscription() { - _languageStream?.cancel(); - _languageStream = userController.languageStream.stream.listen( - (_) => clearCache(exclude: ["analytics_storage"]), - ); + void _setSettingsSubscriptions() { + _languageSubscription?.cancel(); + _languageSubscription = + userController.languageStream.stream.listen(_onLanguageUpdate); + _settingsSubscription?.cancel(); + _settingsSubscription = userController.settingsUpdateStream.stream + .listen((_) => _updateBotOptions()); + } + + Future _onLanguageUpdate(LanguageUpdate update) async { + clearCache(exclude: ["analytics_storage"]); + _updateBotOptions(); + } + + Future _updateBotOptions() async { + if (!matrixState.client.isLogged()) return; + final botDM = matrixState.client.botDM; + if (botDM == null) { + return; + } + + final targetLanguage = languageController.userL2?.langCode; + final cefrLevel = userController.profile.userSettings.cefrLevel; + final updateBotOptions = botDM.botOptions ?? BotOptionsModel(); + + if (updateBotOptions.targetLanguage == targetLanguage && + updateBotOptions.languageLevel == cefrLevel) { + return; + } + + updateBotOptions.targetLanguage = targetLanguage; + updateBotOptions.languageLevel = cefrLevel; + await botDM.setBotOptions(updateBotOptions); } Future setPangeaPushRules() async { diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 0bd028786..cacdc7d52 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -18,10 +18,10 @@ import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; +import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; @@ -38,10 +38,10 @@ import '../events/constants/pangea_event_types.dart'; import '../events/models/representation_content_model.dart'; part "../analytics_misc/room_analytics_extension.dart"; +part "room_capacity_extension.dart"; part "room_children_and_parents_extension.dart"; part "room_events_extension.dart"; part "room_information_extension.dart"; -part "room_settings_extension.dart"; part "room_space_settings_extension.dart"; part "room_user_permissions_extension.dart"; diff --git a/lib/pangea/extensions/room_settings_extension.dart b/lib/pangea/extensions/room_capacity_extension.dart similarity index 56% rename from lib/pangea/extensions/room_settings_extension.dart rename to lib/pangea/extensions/room_capacity_extension.dart index e3778685f..5b38b4aab 100644 --- a/lib/pangea/extensions/room_settings_extension.dart +++ b/lib/pangea/extensions/room_capacity_extension.dart @@ -1,6 +1,6 @@ part of "pangea_room_extension.dart"; -extension RoomSettingsRoomExtension on Room { +extension RoomCapacityExtension on Room { Future updateRoomCapacity(int newCapacity) => client.setRoomStateWithKey( id, @@ -13,11 +13,4 @@ extension RoomSettingsRoomExtension on Room { final t = getState(PangeaEventTypes.capacity)?.content['capacity']; return t is int ? t : null; } - - BotOptionsModel? get botOptions { - if (isSpace) return null; - final stateEvent = getState(PangeaEventTypes.botOptions); - if (stateEvent == null) return null; - return BotOptionsModel.fromJson(stateEvent.content); - } }