if not add bot, show disabled, low opacity form. Moved state handling to top level widget

This commit is contained in:
ggurdin 2024-10-22 10:54:46 -04:00
parent 8e0a807d4e
commit e1062b3443
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
5 changed files with 215 additions and 224 deletions

View file

@ -4,20 +4,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotModeDynamicZone extends StatelessWidget {
final BotOptionsModel initialBotOptions;
final GlobalKey<FormState> formKey;
final BotOptionsModel botOptions;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
final bool enabled;
const ConversationBotModeDynamicZone({
super.key,
required this.initialBotOptions,
required this.formKey,
required this.botOptions,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
this.enabled = true,
});
@override
@ -29,9 +29,12 @@ class ConversationBotModeDynamicZone extends StatelessWidget {
.conversationBotDiscussionZone_discussionTopicPlaceholder,
),
controller: discussionTopicController,
validator: (value) => value == null || value.isEmpty
validator: (value) => enabled &&
botOptions.mode == BotMode.discussion &&
(value == null || value.isEmpty)
? L10n.of(context)!.enterDiscussionTopic
: null,
enabled: enabled,
),
const SizedBox(height: 12),
TextFormField(
@ -40,6 +43,7 @@ class ConversationBotModeDynamicZone extends StatelessWidget {
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
),
controller: discussionKeywordsController,
enabled: enabled,
),
];
@ -49,17 +53,20 @@ class ConversationBotModeDynamicZone extends StatelessWidget {
hintText: L10n.of(context)!
.conversationBotCustomZone_customSystemPromptPlaceholder,
),
validator: (value) => value == null || value.isEmpty
validator: (value) => enabled &&
botOptions.mode == BotMode.custom &&
(value == null || value.isEmpty)
? L10n.of(context)!.enterPrompt
: null,
controller: customSystemPromptController,
enabled: enabled,
),
];
return Column(
children: [
if (initialBotOptions.mode == BotMode.discussion) ...discussionChildren,
if (initialBotOptions.mode == BotMode.custom) ...customChildren,
if (botOptions.mode == BotMode.discussion) ...discussionChildren,
if (botOptions.mode == BotMode.custom) ...customChildren,
const SizedBox(height: 12),
CheckboxListTile(
title: Text(
@ -67,7 +74,7 @@ class ConversationBotModeDynamicZone extends StatelessWidget {
.conversationBotCustomZone_customTriggerReactionEnabledLabel,
),
enabled: false,
value: initialBotOptions.customTriggerReactionEnabled ?? true,
value: botOptions.customTriggerReactionEnabled ?? true,
onChanged: null,
),
const SizedBox(height: 12),

View file

@ -4,12 +4,14 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotModeSelect extends StatelessWidget {
final String? initialMode;
final void Function(String?)? onChanged;
final void Function(String?) onChanged;
final bool enabled;
const ConversationBotModeSelect({
super.key,
this.initialMode,
this.onChanged,
required this.onChanged,
this.enabled = true,
});
@override
@ -52,7 +54,7 @@ class ConversationBotModeSelect extends StatelessWidget {
),
),
],
onChanged: onChanged,
onChanged: enabled ? onChanged : null,
);
}
}

View file

@ -1,5 +1,7 @@
import 'dart:developer';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/constants/bot_mode.dart';
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';
@ -11,17 +13,14 @@ 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';
class ConversationBotSettings extends StatefulWidget {
final Room room;
final String? activeSpaceId;
const ConversationBotSettings({
super.key,
required this.room,
this.activeSpaceId,
});
@override
@ -29,35 +28,7 @@ class ConversationBotSettings extends StatefulWidget {
}
class ConversationBotSettingsState extends State<ConversationBotSettings> {
late BotOptionsModel botOptions;
bool addBot = false;
ConversationBotSettingsState({Key? key});
final TextEditingController discussionTopicController =
TextEditingController();
final TextEditingController discussionKeywordsController =
TextEditingController();
final TextEditingController customSystemPromptController =
TextEditingController();
@override
void initState() {
super.initState();
botOptions = widget.room.botOptions != null
? BotOptionsModel.fromJson(widget.room.botOptions?.toJson())
: BotOptionsModel();
widget.room.botIsInRoom.then((bool isBotRoom) {
setState(() => addBot = isBotRoom);
});
discussionKeywordsController.text = botOptions.discussionKeywords ?? "";
discussionTopicController.text = botOptions.discussionTopic ?? "";
customSystemPromptController.text = botOptions.customSystemPrompt ?? "";
}
Future<void> setBotOption() async {
Future<void> setBotOptions(BotOptionsModel botOptions) async {
try {
await Matrix.of(context).client.setRoomStateWithKey(
widget.room.id,
@ -71,77 +42,18 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
}
}
Future<void> updateBotOption(void Function() makeLocalChange) async {
makeLocalChange();
await showFutureLoadingDialog(
context: context,
future: () async {
try {
await setBotOption();
} catch (err, stack) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: err, s: stack);
}
setState(() {});
},
);
}
void updateFromTextControllers() {
botOptions.discussionTopic = discussionTopicController.text;
botOptions.discussionKeywords = discussionKeywordsController.text;
botOptions.customSystemPrompt = customSystemPromptController.text;
}
Future<void> showBotOptionsDialog() async {
final bool? confirm = await showDialog<bool>(
final BotOptionsModel? newBotOptions = await showDialog<BotOptionsModel?>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) => Dialog(
child: Form(
key: formKey,
child: Container(
padding: const EdgeInsets.all(16),
constraints: const BoxConstraints(
maxWidth: 450,
maxHeight: 725,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: ConversationBotSettingsDialog(
addBot: addBot,
botOptions: botOptions,
formKey: formKey,
updateAddBot: (bool value) =>
setState(() => addBot = value),
discussionKeywordsController: discussionKeywordsController,
discussionTopicController: discussionTopicController,
customSystemPromptController: customSystemPromptController,
),
),
),
),
),
);
},
builder: (BuildContext context) =>
ConversationBotSettingsDialog(room: widget.room),
);
if (confirm == true) {
updateFromTextControllers();
updateBotOption(() => botOptions = 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);
}
if (newBotOptions != null) {
setBotOptions(newBotOptions);
}
}
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return AnimatedContainer(
@ -175,91 +87,175 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
}
}
class ConversationBotSettingsDialog extends StatelessWidget {
final bool addBot;
final BotOptionsModel botOptions;
final GlobalKey<FormState> formKey;
final void Function(bool) updateAddBot;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
class ConversationBotSettingsDialog extends StatefulWidget {
final Room room;
const ConversationBotSettingsDialog({
super.key,
required this.addBot,
required this.botOptions,
required this.formKey,
required this.updateAddBot,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
required this.room,
});
@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();
@override
void initState() {
super.initState();
botOptions = widget.room.botOptions != null
? BotOptionsModel.fromJson(widget.room.botOptions?.toJson())
: BotOptionsModel();
widget.room.botIsInRoom.then((bool isBotRoom) {
setState(() => addBot = isBotRoom);
});
discussionKeywordsController.text = botOptions.discussionKeywords ?? "";
discussionTopicController.text = botOptions.discussionTopic ?? "";
customSystemPromptController.text = botOptions.customSystemPrompt ?? "";
}
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
void updateFromTextControllers() {
botOptions.discussionTopic = discussionTopicController.text;
botOptions.discussionKeywords = discussionKeywordsController.text;
botOptions.customSystemPrompt = customSystemPromptController.text;
}
void onUpdateChatMode(String? mode) {
setState(() => botOptions.mode = mode ?? BotMode.discussion);
}
void onUpdateBotLanguage(String? language) {
setState(() => botOptions.targetLanguage = language);
}
void onUpdateBotVoice(String? voice) {
setState(() => botOptions.targetVoice = voice);
}
void onUpdateBotLanguageLevel(int? level) {
setState(() => botOptions.languageLevel = level);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
),
child: Text(
L10n.of(context)!.botConfig,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
SwitchListTile(
title: Text(
L10n.of(context)!.conversationBotStatus,
),
value: addBot,
onChanged: updateAddBot,
contentPadding: const EdgeInsets.all(4),
),
if (addBot)
Expanded(
child: SingleChildScrollView(
child: Column(
final dialogContent = Form(
key: formKey,
child: Container(
padding: const EdgeInsets.all(16),
constraints: kIsWeb
? const BoxConstraints(
maxWidth: 450,
maxHeight: 725,
)
: null,
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
),
child: Text(
L10n.of(context)!.botConfig,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
SwitchListTile(
title: Text(
L10n.of(context)!.conversationBotStatus,
),
value: addBot,
onChanged: (bool value) {
setState(() => addBot = value);
},
contentPadding: const EdgeInsets.all(4),
),
Expanded(
child: SingleChildScrollView(
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,
enabled: addBot,
onUpdateBotMode: onUpdateChatMode,
onUpdateBotLanguage: onUpdateBotLanguage,
onUpdateBotVoice: onUpdateBotVoice,
onUpdateBotLanguageLevel: onUpdateBotLanguageLevel,
),
),
],
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(height: 20),
ConversationBotSettingsForm(
botOptions: botOptions,
formKey: formKey,
discussionKeywordsController: discussionKeywordsController,
discussionTopicController: discussionTopicController,
customSystemPromptController: customSystemPromptController,
TextButton(
onPressed: () {
Navigator.of(context).pop(null);
},
child: Text(L10n.of(context)!.cancel),
),
const SizedBox(width: 20),
TextButton(
onPressed: () async {
final isValid = formKey.currentState!.validate();
if (!isValid) return;
updateFromTextControllers();
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: Text(L10n.of(context)!.confirm),
),
],
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(L10n.of(context)!.cancel),
),
const SizedBox(width: 20),
TextButton(
onPressed: () {
final isValid = formKey.currentState!.validate();
if (!isValid) return;
Navigator.of(context).pop(true);
},
child: Text(L10n.of(context)!.confirm),
),
],
),
],
),
);
return kIsWeb
? Dialog(child: dialogContent)
: Dialog.fullscreen(child: dialogContent);
}
}

View file

@ -1,4 +1,3 @@
import 'package:fluffychat/pangea/constants/bot_mode.dart';
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart';
@ -7,38 +6,32 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotSettingsForm extends StatefulWidget {
class ConversationBotSettingsForm extends StatelessWidget {
final BotOptionsModel botOptions;
final GlobalKey<FormState> formKey;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
final bool enabled;
final void Function(String?) onUpdateBotMode;
final void Function(String?) onUpdateBotLanguage;
final void Function(String?) onUpdateBotVoice;
final void Function(int?) onUpdateBotLanguageLevel;
const ConversationBotSettingsForm({
super.key,
required this.botOptions,
required this.formKey,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
required this.onUpdateBotMode,
required this.onUpdateBotLanguage,
required this.onUpdateBotVoice,
required this.onUpdateBotLanguageLevel,
this.enabled = true,
});
@override
ConversationBotSettingsFormState createState() =>
ConversationBotSettingsFormState();
}
class ConversationBotSettingsFormState
extends State<ConversationBotSettingsForm> {
late BotOptionsModel botOptions;
@override
void initState() {
super.initState();
botOptions = widget.botOptions;
}
@override
Widget build(BuildContext context) {
return Column(
@ -64,9 +57,7 @@ class ConversationBotSettingsFormState
),
);
}).toList(),
onChanged: (String? newValue) => {
setState(() => botOptions.targetLanguage = newValue!),
},
onChanged: enabled ? onUpdateBotLanguage : null,
),
const SizedBox(height: 12),
DropdownButtonFormField<String>(
@ -80,20 +71,16 @@ class ConversationBotSettingsFormState
isExpanded: true,
icon: const Icon(Icons.keyboard_arrow_down),
items: const [],
onChanged: (String? newValue) => {
setState(() => botOptions.targetVoice = newValue!),
},
onChanged: enabled ? onUpdateBotVoice : null,
),
const SizedBox(height: 12),
LanguageLevelDropdown(
initialLevel: botOptions.languageLevel,
onChanged: (int? newValue) => {
setState(() {
botOptions.languageLevel = newValue!;
}),
},
validator: (value) =>
value == null ? L10n.of(context)!.enterLanguageLevel : null,
onChanged: onUpdateBotLanguageLevel,
validator: (value) => enabled && value == null
? L10n.of(context)!.enterLanguageLevel
: null,
enabled: enabled,
),
const SizedBox(height: 12),
Align(
@ -108,19 +95,16 @@ class ConversationBotSettingsFormState
),
ConversationBotModeSelect(
initialMode: botOptions.mode,
onChanged: (String? mode) => {
setState(() {
botOptions.mode = mode ?? BotMode.discussion;
}),
},
onChanged: onUpdateBotMode,
enabled: enabled,
),
const SizedBox(height: 12),
ConversationBotModeDynamicZone(
initialBotOptions: botOptions,
discussionTopicController: widget.discussionTopicController,
discussionKeywordsController: widget.discussionKeywordsController,
customSystemPromptController: widget.customSystemPromptController,
formKey: widget.formKey,
botOptions: botOptions,
discussionTopicController: discussionTopicController,
discussionKeywordsController: discussionKeywordsController,
customSystemPromptController: customSystemPromptController,
enabled: enabled,
),
],
);

View file

@ -7,12 +7,14 @@ class LanguageLevelDropdown extends StatelessWidget {
final int? initialLevel;
final void Function(int?)? onChanged;
final String? Function(int?)? validator;
final bool enabled;
const LanguageLevelDropdown({
super.key,
this.initialLevel,
this.onChanged,
this.validator,
this.enabled = true,
});
@override
@ -42,7 +44,7 @@ class LanguageLevelDropdown extends StatelessWidget {
),
);
}).toList(),
onChanged: onChanged,
onChanged: enabled ? onChanged : null,
validator: validator,
);
}