diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 385009ff6..b8ad9121b 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3879,7 +3879,7 @@ "define": "Define", "listen": "Listen", "addConversationBot": "Enable Conversation Bot", - "addConversationBotDesc": "Add a bot to this group chat that will ask questions on a specific topic", + "addConversationBotDesc": "Add a bot to this group chat", "convoBotSettingsTitle": "Conversation Bot Settings", "convoBotSettingsDescription": "Edit conversation topic and difficulty", "enterAConversationTopic": "Enter a conversation topic", @@ -4003,13 +4003,21 @@ "conversationBotCustomZone_title": "Custom Settings", "conversationBotCustomZone_customSystemPromptLabel": "System prompt", "conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt", + "conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt", "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction", + "botConfig": "Conversation Bot Settings", "addConversationBotDialogTitleInvite": "Confirm inviting conversation bot", "addConversationBotButtonInvite": "Invite", "addConversationBotDialogInviteConfirmation": "Invite", "addConversationBotButtonTitleRemove": "Confirm removing conversation bot", "addConversationBotButtonRemove": "Remove", "addConversationBotDialogRemoveConfirmation": "Remove", + "conversationBotConfigConfirmChange": "Confirm", + "conversationBotStatus": "Bot Status", + "conversationBotTextAdventureZone_title": "Text Adventure", + "conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions", + "conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions", + "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", "studentAnalyticsNotAvailable": "Student data not currently available", "roomDataMissing": "Some data may be missing from rooms in which you are not a member.", "updatePhoneOS": "You may need to update your device's OS version.", diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index e0067703f..6ec7f18e6 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -331,7 +331,6 @@ class ChatController extends State ); } await Matrix.of(context).client.roomsLoading; - choreographer.setRoomId(roomId); }); // Pangea# _tryLoadTimeline(); diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 84a802e54..61ce641fc 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -463,7 +463,7 @@ class InputBar extends StatelessWidget { debounceDuration: const Duration(milliseconds: 50), // show suggestions after 50ms idle time (default is 300) // #Pangea - key: controller!.choreographer.inputLayerLinkAndKey.key, + key: controller?.choreographer.inputLayerLinkAndKey.key, // builder: (context, controller, focusNode) => TextField( builder: (context, _, focusNode) => TextField( // Pangea# @@ -504,11 +504,11 @@ class InputBar extends StatelessWidget { onSubmitted!(text); }, // #Pangea - style: controller?.isMaxLength ?? false + style: controller?.exceededMaxLength ?? false ? const TextStyle(color: Colors.red) : null, onTap: () { - controller!.onInputTap( + controller?.onInputTap( context, fNode: focusNode, ); diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 7b7bbfd1c..050a1b272 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/pages/settings/settings.dart'; import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_description_button.dart'; import 'package:fluffychat/pangea/utils/set_class_name.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; -import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -43,8 +42,9 @@ class ChatDetailsController extends State { // #Pangea final GlobalKey addToSpaceKey = GlobalKey(); - final GlobalKey addConversationBotKey = - GlobalKey(); + final GlobalKey + addConversationBotKey = + GlobalKey(); bool displayAddStudentOptions = false; void toggleAddStudentOptions() => diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 9c6e22f85..aa4a2504b 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -105,6 +105,34 @@ class NewGroupController extends State { if (!mounted) return; + // #Pangea + // validate init bot options + final addBot = addConversationBotKey.currentState?.addBot ?? false; + if (addBot) { + final botOptions = addConversationBotKey.currentState!.botOptions; + if (botOptions.mode == "custom") { + if (botOptions.customSystemPrompt == null || + botOptions.customSystemPrompt!.isEmpty) { + setState(() { + error = L10n.of(context)! + .conversationBotCustomZone_customSystemPromptEmptyError; + loading = false; + }); + return; + } + } else if (botOptions.mode == "text_adventure") { + if (botOptions.textAdventureGameMasterInstructions == null || + botOptions.textAdventureGameMasterInstructions!.isEmpty) { + setState(() { + error = L10n.of(context)! + .conversationBotCustomZone_instructionSystemPromptEmptyError; + loading = false; + }); + return; + } + } + } + final roomId = await client.createGroupChat( // #Pangea // visibility: diff --git a/lib/pangea/choreographer/controllers/alternative_translator.dart b/lib/pangea/choreographer/controllers/alternative_translator.dart index 5771d7f90..7ac564087 100644 --- a/lib/pangea/choreographer/controllers/alternative_translator.dart +++ b/lib/pangea/choreographer/controllers/alternative_translator.dart @@ -91,7 +91,7 @@ class AlternativeTranslator { final FullTextTranslationResponseModel results = await FullTextTranslationRepo.translate( - accessToken: await choreographer.accessToken, + accessToken: choreographer.accessToken, request: FullTextTranslationRequestModel( text: choreographer.itController.sourceText!, tgtLang: choreographer.l2LangCode!, @@ -117,7 +117,7 @@ class AlternativeTranslator { } similarityResponse = await SimilarityRepo.get( - accessToken: await choreographer.accessToken, + accessToken: choreographer.accessToken, request: SimilarityRequestModel( benchmark: results.bestTranslation, toCompare: [userTranslation!], diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index dd2078fa2..1a11de0e3 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -42,7 +42,6 @@ class Choreographer { bool isFetching = false; Timer? debounceTimer; - String? _roomId; ChoreoRecord choreoRecord = ChoreoRecord.newRecord; // last checked by IGC or translation String? _lastChecked; @@ -403,7 +402,7 @@ class Choreographer { PangeaTextController get textController => _textController; - Future get accessToken => pangeaController.userController.accessToken; + String get accessToken => pangeaController.userController.accessToken; clear() { choreoMode = ChoreoMode.igc; @@ -464,10 +463,7 @@ class Choreographer { setState(); } - get roomId => _roomId; - void setRoomId(String? roomId) { - _roomId = roomId ?? ''; - } + get roomId => chatController.roomId; bool get _useCustomInput => [ EditType.keyboard, diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 6295efa16..ab0e48669 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -46,7 +46,7 @@ class IgcController { ); final IGCTextData igcTextDataResponse = await IgcRepo.getIGC( - await choreographer.accessToken, + choreographer.accessToken, igcRequest: reqBody, ); diff --git a/lib/pangea/choreographer/controllers/span_data_controller.dart b/lib/pangea/choreographer/controllers/span_data_controller.dart index 5f83f1a53..24470454c 100644 --- a/lib/pangea/choreographer/controllers/span_data_controller.dart +++ b/lib/pangea/choreographer/controllers/span_data_controller.dart @@ -65,7 +65,7 @@ class SpanDataController { response = _cache[cacheKey]!.data; } else { response = SpanDataRepo.getSpanDetails( - await choreographer.accessToken, + choreographer.accessToken, request: SpanDetailsRepoReqAndRes( userL1: choreographer.l1LangCode!, userL2: choreographer.l2LangCode!, diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index 9cada0548..f959715ce 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -73,7 +73,7 @@ class ITFeedbackCardController extends State { isTranslating = true; }); FullTextTranslationRepo.translate( - accessToken: await controller.userController.accessToken, + accessToken: controller.userController.accessToken, request: FullTextTranslationRequestModel( text: res!.text, tgtLang: controller.languageController.userL1?.langCode ?? diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index 7c60b98d7..b42061446 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -114,6 +114,9 @@ class ModelKey { "custom_trigger_reaction_enabled"; static const String customTriggerReactionKey = "custom_trigger_reaction_key"; + static const String textAdventureGameMasterInstructions = + "text_adventure_game_master_instructions"; + static const String prevEventId = "prev_event_id"; static const String prevLastUpdated = "prev_last_updated"; } diff --git a/lib/pangea/controllers/contextual_definition_controller.dart b/lib/pangea/controllers/contextual_definition_controller.dart index d423c9b38..cfe13ebf0 100644 --- a/lib/pangea/controllers/contextual_definition_controller.dart +++ b/lib/pangea/controllers/contextual_definition_controller.dart @@ -51,7 +51,7 @@ class ContextualDefinitionController { try { final ContextualDefinitionResponseModel res = await _ContextualDefinitionRepo.define( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, request, ); return res; diff --git a/lib/pangea/controllers/it_feedback_controller.dart b/lib/pangea/controllers/it_feedback_controller.dart index ffef9123f..7a989bda6 100644 --- a/lib/pangea/controllers/it_feedback_controller.dart +++ b/lib/pangea/controllers/it_feedback_controller.dart @@ -52,7 +52,7 @@ class ITFeedbackController { ) async { try { final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, request, ); return res; diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index a3e07b0a3..bcc9b4140 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -145,7 +145,7 @@ class LanguageDetectionController { return _cache[params]!.data; } else { final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, params, ); _cache[params] = _LanguageDetectionCacheItem(data: response); diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart index 7b3a3e6d2..9903eada0 100644 --- a/lib/pangea/controllers/message_data_controller.dart +++ b/lib/pangea/controllers/message_data_controller.dart @@ -42,7 +42,7 @@ class MessageDataController extends BaseController { Future _getTokens( TokensRequestModel req, ) async { - final accessToken = await _pangeaController.userController.accessToken; + final accessToken = _pangeaController.userController.accessToken; final TokensResponseModel igcTextData = await TokensRepo.tokenize(accessToken, req); @@ -195,7 +195,7 @@ class MessageDataController extends BaseController { try { final FullTextTranslationResponseModel res = await FullTextTranslationRepo.translate( - accessToken: await _pangeaController.userController.accessToken, + accessToken: _pangeaController.userController.accessToken, request: req, ); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 4c592a6fc..199d640ab 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -277,7 +277,11 @@ class MyAnalyticsController { // get the timelines for each chat final List> timelineFutures = []; for (final chat in chats) { - timelineFutures.add(chat.getTimeline()); + timelineFutures.add( + chat.timeline == null + ? chat.getTimeline() + : Future.value(chat.timeline), + ); } final List timelines = await Future.wait(timelineFutures); final Map timelineMap = diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 67462bcef..4e32d6f9e 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -53,7 +53,7 @@ class SpeechToTextController { return _cache[cacheKey]!.data; } else { final Future response = _fetchResponse( - accessToken: await _pangeaController.userController.accessToken, + accessToken: _pangeaController.userController.accessToken, requestModel: requestModel, ); _cache[cacheKey] = _SpeechToTextCacheItem(data: response); diff --git a/lib/pangea/controllers/text_to_speech_controller.dart b/lib/pangea/controllers/text_to_speech_controller.dart index b34092618..069722590 100644 --- a/lib/pangea/controllers/text_to_speech_controller.dart +++ b/lib/pangea/controllers/text_to_speech_controller.dart @@ -100,7 +100,7 @@ class TextToSpeechController { return _cache[params]!.data; } else { final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, params, ); _cache[params] = _TextToSpeechCacheItem(data: response); diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index bf8ce3c11..035b122e4 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:matrix/matrix.dart' as matrix; -import '../constants/local.key.dart'; import '../models/user_model.dart'; import '../repo/user_repo.dart'; @@ -83,15 +82,6 @@ class UserController extends BaseController { createdAt: DateTime.now(), ); final newProfile = Profile(userSettings: userSettings); - - // we don't use the pangea profile anymore, but we still need - // it to get access token for the choreographer, so create one - await PUserRepo.repoCreatePangeaUser( - userID: userId!, - dob: dob.toIso8601String(), - fullName: fullname!, - matrixAccessToken: _matrixAccessToken!, - ); await newProfile.saveProfileData(waitForDataInSync: true); } @@ -157,41 +147,13 @@ class UserController extends BaseController { /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. bool needNewJWT(String token) => Jwt.isExpired(token); - /// Retrieves the access token for the user. Looks for it locally, - /// and if it's not found or expired, fetches it from the server. - Future get accessToken async { - final localAccessToken = - _pangeaController.pStoreService.read(PLocalKey.access); - - if (localAccessToken == null || needNewJWT(localAccessToken)) { - PangeaProfileResponse? userModel = await PUserRepo.fetchPangeaUserInfo( - userID: userId!, - matrixAccessToken: _matrixAccessToken!, - ); - // Oops, some accounts were made without creating pangea profiles, so they - // don't have access to an access token yet. In that case, create a pangea profile. - if (userModel?.access == null) { - final dob = profile.userSettings.dateOfBirth; - if (dob != null) { - userModel = await PUserRepo.repoCreatePangeaUser( - userID: userId!, - dob: dob.toIso8601String(), - fullName: fullname!, - matrixAccessToken: _matrixAccessToken!, - ); - if (userModel?.access == null) { - throw ("Trying to get accessToken with null userModel"); - } - } - } - _pangeaController.pStoreService.save( - PLocalKey.access, - userModel!.access, - ); - return userModel.access; + /// Retrieves matrix access token. + String get accessToken { + final token = _pangeaController.matrixState.client.accessToken; + if (token == null) { + throw ("Trying to get accessToken with null token. User is not logged in."); } - - return localAccessToken; + return token; } /// Returns the full name of the user. diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart index b7a8d287e..81affd726 100644 --- a/lib/pangea/controllers/word_net_controller.dart +++ b/lib/pangea/controllers/word_net_controller.dart @@ -54,7 +54,7 @@ class WordController extends BaseController { if (local != null) return local; final WordData remote = await WordRepo.getWordNetData( - accessToken: await _pangeaController.userController.accessToken, + accessToken: _pangeaController.userController.accessToken, fullText: fullText, word: word, userL1: userL1, diff --git a/lib/pangea/extensions/client_extension/general_info_extension.dart b/lib/pangea/extensions/client_extension/general_info_extension.dart index ca5df40cc..41f81442e 100644 --- a/lib/pangea/extensions/client_extension/general_info_extension.dart +++ b/lib/pangea/extensions/client_extension/general_info_extension.dart @@ -3,7 +3,7 @@ part of "client_extension.dart"; extension GeneralInfoClientExtension on Client { Future> get _teacherRoomIds async { final List adminRoomIds = []; - for (final Room adminSpace in (await _spacesImTeaching)) { + for (final Room adminSpace in (_spacesImTeaching)) { adminRoomIds.add(adminSpace.id); final List adminSpaceRooms = adminSpace.allSpaceChildRoomIds; adminRoomIds.addAll(adminSpaceRooms); @@ -59,7 +59,7 @@ extension GeneralInfoClientExtension on Client { final Event? originalEvent = await room!.getEventById(edittedEventId); if (originalEvent == null) return []; - final Timeline timeline = await room.getTimeline(); + final Timeline timeline = room.timeline ?? await room.getTimeline(); final List editEvents = originalEvent .aggregatedEvents( timeline, diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index bc820998c..182779853 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -320,7 +320,7 @@ extension EventsRoomExtension on Room { "In messageListForChat with room that is not a chat", ); } - final Timeline timeline = await getTimeline(); + final Timeline timeline = this.timeline ?? await getTimeline(); while (timeline.canRequestHistory && numberOfSearches < 50) { await timeline.requestHistory(historyCount: 100); @@ -433,7 +433,7 @@ extension EventsRoomExtension on Room { }) async { try { int numberOfSearches = 0; - final Timeline timeline = await getTimeline(); + final Timeline timeline = this.timeline ?? await getTimeline(); List relevantEvents() => timeline.events .where((event) => event.senderId == sender && event.type == type) diff --git a/lib/pangea/models/bot_options_model.dart b/lib/pangea/models/bot_options_model.dart index 179461a34..5c61eef5a 100644 --- a/lib/pangea/models/bot_options_model.dart +++ b/lib/pangea/models/bot_options_model.dart @@ -20,6 +20,7 @@ class BotOptionsModel { String? customSystemPrompt; bool? customTriggerReactionEnabled; String? customTriggerReactionKey; + String? textAdventureGameMasterInstructions; BotOptionsModel({ //////////////////////////////////////////////////////////////////////////// @@ -45,6 +46,11 @@ class BotOptionsModel { this.customSystemPrompt, this.customTriggerReactionEnabled = true, this.customTriggerReactionKey = "⏩", + + //////////////////////////////////////////////////////////////////////////// + // Text Adventure Mode Options + //////////////////////////////////////////////////////////////////////////// + this.textAdventureGameMasterInstructions, }); factory BotOptionsModel.fromJson(json) { @@ -73,6 +79,12 @@ class BotOptionsModel { customTriggerReactionEnabled: json[ModelKey.customTriggerReactionEnabled] ?? true, customTriggerReactionKey: json[ModelKey.customTriggerReactionKey] ?? "⏩", + + ////////////////////////////////////////////////////////////////////////// + // Text Adventure Mode Options + ////////////////////////////////////////////////////////////////////////// + textAdventureGameMasterInstructions: + json[ModelKey.textAdventureGameMasterInstructions], ); } @@ -93,6 +105,8 @@ class BotOptionsModel { data[ModelKey.customTriggerReactionEnabled] = customTriggerReactionEnabled ?? true; data[ModelKey.customTriggerReactionKey] = customTriggerReactionKey ?? "⏩"; + data[ModelKey.textAdventureGameMasterInstructions] = + textAdventureGameMasterInstructions; return data; } catch (e, s) { debugger(when: kDebugMode); @@ -134,6 +148,9 @@ class BotOptionsModel { case ModelKey.customTriggerReactionKey: customTriggerReactionKey = value; break; + case ModelKey.textAdventureGameMasterInstructions: + textAdventureGameMasterInstructions = value; + break; default: throw Exception('Invalid key for bot options - $key'); } diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index d46936c86..1848e7ada 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -166,7 +166,7 @@ class ConstructListViewState extends State { if (_timelinesCache.containsKey(use.chatId)) { timeline = _timelinesCache[use.chatId]; } else { - timeline = await msgRoom.getTimeline(); + timeline = msgRoom.timeline ?? await msgRoom.getTimeline(); _timelinesCache[use.chatId] = timeline; } diff --git a/lib/pangea/pages/find_partner/find_partner.dart b/lib/pangea/pages/find_partner/find_partner.dart index a953c7f19..0e50beab6 100644 --- a/lib/pangea/pages/find_partner/find_partner.dart +++ b/lib/pangea/pages/find_partner/find_partner.dart @@ -107,8 +107,7 @@ class FindPartnerController extends State { UserProfileSearchResponse response; try { - final String accessToken = - await pangeaController.userController.accessToken; + final String accessToken = pangeaController.userController.accessToken; response = await PUserRepo.searchUserProfiles( accessToken: accessToken, targetLanguage: targetLanguageSearch.langCode, diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 4182ab2d6..1a6576770 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,4 +1,3 @@ -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; @@ -32,15 +31,15 @@ class SettingsLearningView extends StatelessWidget { const SizedBox(height: 8), const Divider(height: 1), const SizedBox(height: 8), - if (controller.pangeaController.permissionsController.isUser18()) - SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context)!.publicProfileTitle), - subtitle: Text(L10n.of(context)!.publicProfileDesc), - value: controller.pangeaController.userController.isPublic, - onChanged: (bool isPublicProfile) => - controller.setPublicProfile(isPublicProfile), - ), + // if (controller.pangeaController.permissionsController.isUser18()) + // SwitchListTile.adaptive( + // activeColor: AppConfig.activeToggleColor, + // title: Text(L10n.of(context)!.publicProfileTitle), + // subtitle: Text(L10n.of(context)!.publicProfileDesc), + // value: controller.pangeaController.userController.isPublic, + // onChanged: (bool isPublicProfile) => + // controller.setPublicProfile(isPublicProfile), + // ), ListTile( subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), ), diff --git a/lib/pangea/repo/user_repo.dart b/lib/pangea/repo/user_repo.dart index 244e21c54..7f3efc48f 100644 --- a/lib/pangea/repo/user_repo.dart +++ b/lib/pangea/repo/user_repo.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:developer'; import 'package:fluffychat/pangea/constants/model_keys.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart'; import '../models/user_model.dart'; @@ -11,33 +10,33 @@ import '../network/requests.dart'; import '../network/urls.dart'; class PUserRepo { - static Future repoCreatePangeaUser({ - required String userID, - required String dob, - required fullName, - required String matrixAccessToken, - }) async { - try { - final Requests req = Requests( - baseUrl: PApiUrls.baseAPI, - matrixAccessToken: matrixAccessToken, - ); + // static Future repoCreatePangeaUser({ + // required String userID, + // required String dob, + // required fullName, + // required String matrixAccessToken, + // }) async { + // try { + // final Requests req = Requests( + // baseUrl: PApiUrls.baseAPI, + // matrixAccessToken: matrixAccessToken, + // ); - final Map body = { - ModelKey.userFullName: fullName, - ModelKey.userPangeaUserId: userID, - ModelKey.userDateOfBirth: dob, - }; - final resp = await req.post( - url: PApiUrls.createUser, - body: body, - ); - return PangeaProfileResponse.fromJson(jsonDecode(resp.body)); - } catch (err, s) { - ErrorHandler.logError(e: err, s: s); - return null; - } - } + // final Map body = { + // ModelKey.userFullName: fullName, + // ModelKey.userPangeaUserId: userID, + // ModelKey.userDateOfBirth: dob, + // }; + // final resp = await req.post( + // url: PApiUrls.createUser, + // body: body, + // ); + // return PangeaProfileResponse.fromJson(jsonDecode(resp.body)); + // } catch (err, s) { + // ErrorHandler.logError(e: err, s: s); + // return null; + // } + // } static Future fetchPangeaUserInfo({ required String userID, diff --git a/lib/pangea/utils/get_chat_list_item_subtitle.dart b/lib/pangea/utils/get_chat_list_item_subtitle.dart index 76bd453b6..d02ac7088 100644 --- a/lib/pangea/utils/get_chat_list_item_subtitle.dart +++ b/lib/pangea/utils/get_chat_list_item_subtitle.dart @@ -36,9 +36,10 @@ class GetChatListItemSubtitle { eventContextId = null; } - final Timeline timeline = await event.room.getTimeline( - eventContextId: eventContextId, - ); + final Timeline timeline = event.room.timeline != null && + event.room.timeline!.chunk.eventsMap.containsKey(eventContextId) + ? event.room.timeline! + : await event.room.getTimeline(eventContextId: eventContextId); if (moveBackInTimeline(event)) { event = timeline.events.firstWhereOrNull((e) => !moveBackInTimeline(e)); diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 68d3a6e95..10e75a47a 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -59,7 +59,7 @@ class MessageTranslationCardState extends State { oldSelectedText = widget.selection.selectedText; final String accessToken = - await MatrixState.pangeaController.userController.accessToken; + MatrixState.pangeaController.userController.accessToken; final resp = await FullTextTranslationRepo.translate( accessToken: accessToken, diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart index 55ec1493d..2e79e0677 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart @@ -20,6 +20,9 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget { final TextEditingController textFieldController = TextEditingController(text: customSystemPrompt); + final GlobalKey customSystemPromptFormKey = + GlobalKey(); + void setBotCustomSystemPromptAction() async { showDialog( context: context, @@ -28,14 +31,25 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget { title: Text( L10n.of(context)!.conversationBotCustomZone_customSystemPromptLabel, ), - content: TextField( - minLines: 1, - maxLines: 10, - maxLength: 1000, - controller: textFieldController, - onChanged: (value) { - customSystemPrompt = value; - }, + content: Form( + key: customSystemPromptFormKey, + child: TextFormField( + minLines: 1, + maxLines: 10, + maxLength: 1000, + controller: textFieldController, + onChanged: (value) { + if (value.isNotEmpty) { + customSystemPrompt = value; + } + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'This field cannot be empty'; + } + return null; + }, + ), ), actions: [ TextButton( @@ -47,11 +61,12 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget { TextButton( child: Text(L10n.of(context)!.ok), onPressed: () { - if (customSystemPrompt == "") return; - if (customSystemPrompt != - initialBotOptions.customSystemPrompt) { - initialBotOptions.customSystemPrompt = customSystemPrompt; - onChanged.call(initialBotOptions); + if (customSystemPromptFormKey.currentState!.validate()) { + if (customSystemPrompt != + initialBotOptions.customSystemPrompt) { + initialBotOptions.customSystemPrompt = customSystemPrompt; + onChanged.call(initialBotOptions); + } Navigator.of(context).pop(); } }, @@ -68,6 +83,13 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget { L10n.of(context)! .conversationBotCustomZone_customSystemPromptPlaceholder, ), + subtitle: customSystemPrompt.isEmpty + ? Text( + L10n.of(context)! + .conversationBotCustomZone_customSystemPromptEmptyError, + style: const TextStyle(color: Colors.red), + ) + : null, ); } } diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart index 553de7182..14b05dc90 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart @@ -1,5 +1,7 @@ import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -16,35 +18,14 @@ class ConversationBotCustomZone extends StatelessWidget { @override Widget build(BuildContext context) { - print(initialBotOptions.toJson()); return Column( children: [ - const SizedBox(height: 12), - Text( - L10n.of(context)!.conversationBotCustomZone_title, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - const Divider( - color: Colors.grey, - thickness: 1, + ConversationBotDynamicZoneTitle( + title: L10n.of(context)!.conversationBotCustomZone_title, ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.fromLTRB(12, 0, 0, 0), - child: Text( - L10n.of(context)! - .conversationBotCustomZone_customSystemPromptLabel, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), + ConversationBotDynamicZoneLabel( + label: L10n.of(context)! + .conversationBotCustomZone_customSystemPromptLabel, ), Padding( padding: const EdgeInsets.all(8), diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_discussion_zone.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_discussion_zone.dart index 2bb4d7a76..6035faf4d 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_discussion_zone.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_discussion_zone.dart @@ -1,6 +1,8 @@ import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_keywords_input.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_topic_input.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -19,32 +21,12 @@ class ConversationBotDiscussionZone extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - const SizedBox(height: 12), - Text( - L10n.of(context)!.conversationBotDiscussionZone_title, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - const Divider( - color: Colors.grey, - thickness: 1, + ConversationBotDynamicZoneTitle( + title: L10n.of(context)!.conversationBotDiscussionZone_title, ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.fromLTRB(12, 0, 0, 0), - child: Text( - L10n.of(context)! - .conversationBotDiscussionZone_discussionTopicLabel, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), + ConversationBotDynamicZoneLabel( + label: L10n.of(context)! + .conversationBotDiscussionZone_discussionTopicLabel, ), Padding( padding: const EdgeInsets.all(8), @@ -54,19 +36,9 @@ class ConversationBotDiscussionZone extends StatelessWidget { ), ), const SizedBox(height: 12), - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.fromLTRB(12, 0, 0, 0), - child: Text( - L10n.of(context)! - .conversationBotDiscussionZone_discussionKeywordsLabel, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), + ConversationBotDynamicZoneLabel( + label: L10n.of(context)! + .conversationBotDiscussionZone_discussionKeywordsLabel, ), Padding( padding: const EdgeInsets.all(8), diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart new file mode 100644 index 000000000..6c2043dcd --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class ConversationBotDynamicZoneLabel extends StatelessWidget { + final String label; + + const ConversationBotDynamicZoneLabel({ + super.key, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 0, 0), + child: Text( + label, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } +} diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart new file mode 100644 index 000000000..dbfbb00dc --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class ConversationBotDynamicZoneTitle extends StatelessWidget { + final String title; + + const ConversationBotDynamicZoneTitle({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const SizedBox(height: 12), + Text( + title, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + const Divider( + color: Colors.grey, + thickness: 1, + ), + const SizedBox(height: 12), + ], + ); + } +} 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 b15689357..e7d8f55a9 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,5 +1,6 @@ import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart'; import 'package:flutter/material.dart'; import 'conversation_bot_discussion_zone.dart'; @@ -26,7 +27,10 @@ class ConversationBotModeDynamicZone extends StatelessWidget { onChanged: onChanged, ), // "conversation": const ConversationBotConversationZone(), - // "text_adventure": const ConversationBotTextAdventureZone(), + "text_adventure": ConversationBotTextAdventureZone( + initialBotOptions: initialBotOptions, + onChanged: onChanged, + ), }; return Container( decoration: BoxDecoration( 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 ba60f038c..5ec435112 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart @@ -19,8 +19,8 @@ class ConversationBotModeSelect extends StatelessWidget { "custom": L10n.of(context)!.conversationBotModeSelectOption_custom, // "conversation": // L10n.of(context)!.conversationBotModeSelectOption_conversation, - // "text_adventure": - // L10n.of(context)!.conversationBotModeSelectOption_textAdventure, + "text_adventure": + L10n.of(context)!.conversationBotModeSelectOption_textAdventure, }; return Padding( diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index 1c78fd030..e4054f4e5 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -1,22 +1,19 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; 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_mode_dynamic_zone.dart'; -import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart'; -import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; -import '../../../widgets/matrix.dart'; -import '../../constants/pangea_event_types.dart'; -import '../../extensions/pangea_room_extension/pangea_room_extension.dart'; -import '../../utils/error_handler.dart'; - class ConversationBotSettings extends StatefulWidget { final Room? room; final bool startOpen; @@ -36,6 +33,7 @@ class ConversationBotSettings extends StatefulWidget { class ConversationBotSettingsState extends State { late BotOptionsModel botOptions; late bool isOpen; + late bool isCreating; bool addBot = false; Room? parentSpace; @@ -56,6 +54,22 @@ class ConversationBotSettingsState extends State { parentSpace = widget.activeSpaceId != null ? Matrix.of(context).client.getRoomById(widget.activeSpaceId!) : null; + isCreating = widget.room == null; + } + + Future setBotOption() async { + if (widget.room == null) return; + 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); + } } Future updateBotOption(void Function() makeLocalChange) async { @@ -74,196 +88,191 @@ class ConversationBotSettingsState extends State { ); } - Future setBotOption() async { - if (widget.room == null) return; - 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); - } - } - @override - Widget build(BuildContext context) => Column( + 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)!.convoBotSettingsTitle, + isCreating + ? L10n.of(context)!.addConversationBot + : L10n.of(context)!.botConfig, style: TextStyle( color: Theme.of(context).colorScheme.secondary, fontWeight: FontWeight.bold, ), ), - subtitle: Text(L10n.of(context)!.convoBotSettingsDescription), + subtitle: isCreating + ? Text(L10n.of(context)!.addConversationBotDesc) + : null, leading: CircleAvatar( backgroundColor: Theme.of(context).scaffoldBackgroundColor, foregroundColor: Theme.of(context).textTheme.bodyLarge!.color, - child: const Icon(Icons.psychology_outlined), - ), - trailing: Icon( - isOpen - ? Icons.keyboard_arrow_down_outlined - : Icons.keyboard_arrow_right_outlined, + child: const BotFace( + width: 30.0, + expression: BotExpression.idle, + ), ), - onTap: () => setState(() => isOpen = !isOpen), - ), - if (isOpen) - AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: isOpen ? null : 0, - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16), - child: ListTile( - title: Text( - L10n.of(context)!.addConversationBot, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Text(L10n.of(context)!.addConversationBotDesc), - leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - foregroundColor: - Theme.of(context).textTheme.bodyLarge!.color, - child: const BotFace( - width: 30.0, - expression: BotExpression.idle, - ), - ), - trailing: ElevatedButton( - onPressed: () async { - final bool? confirm = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: addBot + trailing: isCreating + ? ElevatedButton( + onPressed: () async { + final bool? confirm = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: addBot + ? Text( + L10n.of(context)! + .addConversationBotButtonTitleRemove, + ) + : Text( + L10n.of(context)! + .addConversationBotDialogTitleInvite, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(L10n.of(context)!.cancel), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(!addBot); + }, + child: addBot ? Text( L10n.of(context)! - .addConversationBotButtonTitleRemove, + .addConversationBotDialogRemoveConfirmation, ) : Text( L10n.of(context)! - .addConversationBotDialogTitleInvite, + .addConversationBotDialogInviteConfirmation, + ), + ), + ], + ); + }, + ); + + if (confirm == true) { + setState(() => addBot = true); + widget.room?.invite(BotName.byEnvironment); + } else { + setState(() => addBot = false); + widget.room?.kick(BotName.byEnvironment); + } + }, + child: addBot + ? Text( + L10n.of(context)!.addConversationBotButtonRemove, + ) + : Text( + L10n.of(context)!.addConversationBotButtonInvite, + ), + ) + : const Icon(Icons.settings), + onTap: isCreating + ? null + : () async { + final bool? confirm = await showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: Text( + L10n.of(context)!.botConfig, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.fromLTRB(0, 0, 0, 12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + L10n.of(context)!.conversationBotStatus, + ), + Switch( + value: addBot, + onChanged: (value) { + setState( + () => addBot = value, + ); + }, ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(L10n.of(context)!.cancel), + ], ), - TextButton( - onPressed: () { - Navigator.of(context).pop(!addBot); - }, - child: addBot - ? Text( - L10n.of(context)! - .addConversationBotDialogRemoveConfirmation, - ) - : Text( - L10n.of(context)! - .addConversationBotDialogInviteConfirmation, + ), + if (addBot) + Flexible( + child: SingleChildScrollView( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context) + .colorScheme + .secondary, + width: 0.5, ), + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), + ), + child: ConversationBotSettingsForm( + botOptions: botOptions, + ), + ), + ), ), - ], - ); - }, - ); - - if (confirm == true) { - setState(() => addBot = true); - widget.room?.invite(BotName.byEnvironment); - } else { - setState(() => addBot = false); - widget.room?.kick(BotName.byEnvironment); - } - }, - child: addBot - ? Text( - L10n.of(context)! - .addConversationBotButtonRemove, - ) - : Text( - L10n.of(context)! - .addConversationBotButtonInvite, + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(L10n.of(context)!.cancel), ), - ), - ), - ), - if (addBot) ...[ - Padding( - padding: const EdgeInsets.fromLTRB(32, 16, 0, 0), - child: Text( - L10n.of(context)!.conversationLanguageLevel, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16), - child: LanguageLevelDropdown( - initialLevel: botOptions.languageLevel, - onChanged: (int? newValue) => updateBotOption(() { - botOptions.languageLevel = newValue!; - }), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(32, 16, 0, 0), - child: Text( - L10n.of(context)!.conversationBotModeSelectDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16), - child: ConversationBotModeSelect( - initialMode: botOptions.mode, - onChanged: (String? mode) => updateBotOption( - () { - botOptions.mode = mode ?? "discussion"; - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(28, 0, 12, 0), - child: ConversationBotModeDynamicZone( - initialBotOptions: botOptions, - onChanged: (BotOptionsModel? newOptions) { - updateBotOption(() { - if (newOptions != null) { - botOptions = newOptions; - } - }); - }, - ), - ), - const SizedBox(height: 16), - ], - ], - ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + L10n.of(context)! + .conversationBotConfigConfirmChange, + ), + ), + ], + ), + ); + }, + ); + if (confirm == true) { + if (addBot) { + await widget.room?.invite(BotName.byEnvironment); + } else { + await widget.room?.kick(BotName.byEnvironment); + } + updateBotOption(() { + botOptions = botOptions; + }); + } + }, + ), + if (isCreating && addBot) + ConversationBotSettingsForm( + botOptions: botOptions, ), ], - ); + ), + ); + } } diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart new file mode 100644 index 000000000..519245303 --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart @@ -0,0 +1,88 @@ +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/space/language_level_dropdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class ConversationBotSettingsForm extends StatefulWidget { + final BotOptionsModel botOptions; + + const ConversationBotSettingsForm({ + super.key, + required this.botOptions, + }); + + @override + ConversationBotSettingsFormState createState() => + ConversationBotSettingsFormState(); +} + +class ConversationBotSettingsFormState + extends State { + final formKey = GlobalKey(); + + late BotOptionsModel botOptions; + + @override + void initState() { + super.initState(); + botOptions = widget.botOptions; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + L10n.of(context)!.conversationLanguageLevel, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: (int? newValue) => { + setState(() { + botOptions.languageLevel = newValue!; + }), + }, + ), + Text( + L10n.of(context)!.conversationBotModeSelectDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ConversationBotModeSelect( + initialMode: botOptions.mode, + onChanged: (String? mode) => { + setState(() { + botOptions.mode = mode ?? "discussion"; + }), + }, + ), + Padding( + padding: const EdgeInsets.all(12), + child: ConversationBotModeDynamicZone( + initialBotOptions: botOptions, + onChanged: (BotOptionsModel? newOptions) { + if (newOptions != null) { + setState(() { + botOptions = newOptions; + }); + } + }, + ), + ), + ], + ); + } +} diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart new file mode 100644 index 000000000..fc5aa0a08 --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart @@ -0,0 +1,91 @@ +import 'package:fluffychat/pangea/models/bot_options_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.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/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart index 2f65348cf..effbf70ee 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart @@ -1,15 +1,39 @@ +import 'package:fluffychat/pangea/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; 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 const Column( + return Column( children: [ - Text('Text Adventure Zone'), + ConversationBotDynamicZoneTitle( + title: L10n.of(context)!.conversationBotTextAdventureZone_title, + ), + ConversationBotDynamicZoneLabel( + label: L10n.of(context)! + .conversationBotTextAdventureZone_instructionLabel, + ), + Padding( + padding: const EdgeInsets.all(8), + child: ConversationBotGameMasterInstructionsInput( + initialBotOptions: initialBotOptions, + onChanged: onChanged, + ), + ), ], ); } diff --git a/lib/pangea/widgets/igc/pangea_text_controller.dart b/lib/pangea/widgets/igc/pangea_text_controller.dart index 8fc136edd..63c5b3f94 100644 --- a/lib/pangea/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/widgets/igc/pangea_text_controller.dart @@ -27,7 +27,7 @@ class PangeaTextController extends TextEditingController { } static const int maxLength = 1000; - bool get isMaxLength => text.length == 1000; + bool get exceededMaxLength => text.length >= maxLength; bool forceKeepOpen = false; diff --git a/lib/pangea/widgets/new_group/vocab_list.dart b/lib/pangea/widgets/new_group/vocab_list.dart index 67089145e..ac0d157a7 100644 --- a/lib/pangea/widgets/new_group/vocab_list.dart +++ b/lib/pangea/widgets/new_group/vocab_list.dart @@ -312,7 +312,7 @@ class GenerateVocabButtonState extends State { Future> _getWords() async { final ChatTopic topic = await TopicDataRepo.generate( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, request: TopicDataRequest( topicInfo: widget.topic, numWords: 10, @@ -514,7 +514,7 @@ class PromptsFieldState extends State { Future> _getPrompts() async { final ChatTopic res = await TopicDataRepo.generate( - await _pangeaController.userController.accessToken, + _pangeaController.userController.accessToken, request: TopicDataRequest( topicInfo: widget.topic, numPrompts: 10, diff --git a/pangea_packages/fcm_shared_isolate/android/.gradle/6.7.1/fileHashes/fileHashes.lock b/pangea_packages/fcm_shared_isolate/android/.gradle/6.7.1/fileHashes/fileHashes.lock deleted file mode 100644 index f1facd835..000000000 Binary files a/pangea_packages/fcm_shared_isolate/android/.gradle/6.7.1/fileHashes/fileHashes.lock and /dev/null differ diff --git a/pangea_packages/fcm_shared_isolate/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/pangea_packages/fcm_shared_isolate/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index ea7da199a..000000000 Binary files a/pangea_packages/fcm_shared_isolate/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/pangea_packages/fcm_shared_isolate/android/.gradle/checksums/checksums.lock b/pangea_packages/fcm_shared_isolate/android/.gradle/checksums/checksums.lock deleted file mode 100644 index a148f5b0e..000000000 Binary files a/pangea_packages/fcm_shared_isolate/android/.gradle/checksums/checksums.lock and /dev/null differ