4307 bot in dms and activity chats only (#4319)

* remove bot settings files

* exclude bot in invite page

* listen to language / CERF level changes and update bot DM settings

* Update lib/pangea/common/controllers/pangea_controller.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
ggurdin 2025-10-10 10:29:57 -04:00 committed by GitHub
parent 71ea85876a
commit dc8bb8fe8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 99 additions and 797 deletions

View file

@ -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<ChatDetails> {
}
}
Future<void> 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<void> setRoomCapacity() async {
if (roomId == null) return;
final Room? room = Matrix.of(context).client.getRoomById(roomId!);

View file

@ -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<void> setBotOptions(BotOptionsModel options) =>
client.setRoomStateWithKey(
id,
PangeaEventTypes.botOptions,
'',
options.toJson(),
);
}

View file

@ -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,
);
}

View file

@ -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<ChatDetailsButtonRow> {
enabled: room.ownPowerLevel >= 50,
showInMainView: false,
),
ButtonDetails(
title: l10n.botSettings,
icon: const BotFace(
width: 30.0,
expression: BotExpression.idle,
),
onPressed: () => showDialog<BotOptionsModel?>(
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),

View file

@ -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<Profile>.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,
);
});
}

View file

@ -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<String> 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!,
),

View file

@ -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'),
],
);
}
}

View file

@ -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),
],
);
}
}

View file

@ -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<String, String> 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,
);
}
}

View file

@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
Future<void> showNoPermissionDialog(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(L10n.of(context).botConfigNoPermissionTitle),
content: Text(L10n.of(context).botConfigNoPermissionMessage),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(L10n.of(context).ok),
),
],
);
},
);
}

View file

@ -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<ConversationBotSettings> {
Future<void> 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<BotOptionsModel?>(
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<ConversationBotSettingsDialog> {
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<FormState> formKey = GlobalKey<FormState>();
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,
);
}
}

View file

@ -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,
),
],
);
}
}

View file

@ -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<FormState> gameMasterInstructionsFormKey =
GlobalKey<FormState>();
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,
),
);
}
}

View file

@ -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,
),
),
],
);
}
}

View file

@ -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<void> _onLanguageUpdate(LanguageUpdate update) async {
clearCache(exclude: ["analytics_storage"]);
_updateBotOptions();
}
Future<void> _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<void> setPangeaPushRules() async {

View file

@ -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";

View file

@ -1,6 +1,6 @@
part of "pangea_room_extension.dart";
extension RoomSettingsRoomExtension on Room {
extension RoomCapacityExtension on Room {
Future<void> 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);
}
}