Merge pull request #778 from pangeachat/bot-settings-design

initial work on updating bot settings UI
pull/1428/head
ggurdin 1 year ago committed by GitHub
commit f87128af77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3904,7 +3904,6 @@
"listen": "Listen", "listen": "Listen",
"addConversationBot": "Enable Conversation Bot", "addConversationBot": "Enable Conversation Bot",
"addConversationBotDesc": "Add a bot to this chat", "addConversationBotDesc": "Add a bot to this chat",
"convoBotSettingsTitle": "Conversation Bot Settings",
"convoBotSettingsDescription": "Edit conversation topic and difficulty", "convoBotSettingsDescription": "Edit conversation topic and difficulty",
"enterAConversationTopic": "Enter a conversation topic", "enterAConversationTopic": "Enter a conversation topic",
"conversationTopic": "Conversation topic", "conversationTopic": "Conversation topic",
@ -4009,7 +4008,7 @@
"accuracy": "Accuracy", "accuracy": "Accuracy",
"points": "Points", "points": "Points",
"noPaymentInfo": "No payment info necessary!", "noPaymentInfo": "No payment info necessary!",
"conversationBotModeSelectDescription": "Bot mode", "conversationBotModeSelectDescription": "Chat Activity",
"conversationBotModeSelectOption_discussion": "Discussion", "conversationBotModeSelectOption_discussion": "Discussion",
"conversationBotModeSelectOption_custom": "Custom", "conversationBotModeSelectOption_custom": "Custom",
"conversationBotModeSelectOption_conversation": "Conversation", "conversationBotModeSelectOption_conversation": "Conversation",
@ -4030,7 +4029,7 @@
"conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt", "conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt",
"conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt", "conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt",
"conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction", "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction",
"botConfig": "Conversation Bot Settings", "botConfig": "Chat Settings",
"addConversationBotDialogTitleInvite": "Confirm inviting conversation bot", "addConversationBotDialogTitleInvite": "Confirm inviting conversation bot",
"addConversationBotButtonInvite": "Invite", "addConversationBotButtonInvite": "Invite",
"addConversationBotDialogInviteConfirmation": "Invite", "addConversationBotDialogInviteConfirmation": "Invite",
@ -4038,7 +4037,7 @@
"addConversationBotButtonRemove": "Remove", "addConversationBotButtonRemove": "Remove",
"addConversationBotDialogRemoveConfirmation": "Remove", "addConversationBotDialogRemoveConfirmation": "Remove",
"conversationBotConfigConfirmChange": "Confirm", "conversationBotConfigConfirmChange": "Confirm",
"conversationBotStatus": "Bot Status", "conversationBotStatus": "Invite bot",
"conversationBotTextAdventureZone_title": "Text Adventure", "conversationBotTextAdventureZone_title": "Text Adventure",
"conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions", "conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions",
"conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions", "conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions",
@ -4360,5 +4359,10 @@
"grammarCopyCase": "Case", "grammarCopyCase": "Case",
"grammarCopyDefinite": "Definiteness", "grammarCopyDefinite": "Definiteness",
"grammarCopyNumForm": "Numeral Form", "grammarCopyNumForm": "Numeral Form",
"grammarCopyUnknown": "Unknown" "grammarCopyUnknown": "Unknown",
"enterPrompt": "Please enter a system prompt",
"selectBotLanguage": "Select bot language",
"chooseVoice": "Choose a voice",
"enterLanguageLevel": "Please enter a language level",
"enterDiscussionTopic": "Please enter a discussion topic"
} }

@ -106,6 +106,20 @@ class NewGroupController extends State<NewGroup> {
// #Pangea // #Pangea
// validate init bot options // validate init bot options
if (addConversationBotKey.currentState?.formKey.currentState != null) {
final isValid = addConversationBotKey
.currentState!.formKey.currentState!
.validate();
if (isValid == false) {
setState(() {
error = L10n.of(context)!
.conversationBotCustomZone_customSystemPromptEmptyError;
loading = false;
});
return;
}
}
addConversationBotKey.currentState?.updateAllBotOptions();
final addBot = addConversationBotKey.currentState?.addBot ?? false; final addBot = addConversationBotKey.currentState?.addBot ?? false;
if (addBot) { if (addBot) {
final botOptions = addConversationBotKey.currentState!.botOptions; final botOptions = addConversationBotKey.currentState!.botOptions;

@ -121,6 +121,9 @@ class ModelKey {
static const String textAdventureGameMasterInstructions = static const String textAdventureGameMasterInstructions =
"text_adventure_game_master_instructions"; "text_adventure_game_master_instructions";
static const String targetLanguage = "target_language";
static const String targetVoice = "target_voice";
static const String prevEventId = "prev_event_id"; static const String prevEventId = "prev_event_id";
static const String prevLastUpdated = "prev_last_updated"; static const String prevLastUpdated = "prev_last_updated";

@ -21,6 +21,8 @@ class BotOptionsModel {
bool? customTriggerReactionEnabled; bool? customTriggerReactionEnabled;
String? customTriggerReactionKey; String? customTriggerReactionKey;
String? textAdventureGameMasterInstructions; String? textAdventureGameMasterInstructions;
String? targetLanguage;
String? targetVoice;
BotOptionsModel({ BotOptionsModel({
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -31,6 +33,8 @@ class BotOptionsModel {
this.keywords = const [], this.keywords = const [],
this.safetyModeration = true, this.safetyModeration = true,
this.mode = BotMode.discussion, this.mode = BotMode.discussion,
this.targetLanguage,
this.targetVoice,
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Discussion Mode Options // Discussion Mode Options
@ -63,6 +67,8 @@ class BotOptionsModel {
: null, : null,
safetyModeration: json[ModelKey.safetyModeration] ?? true, safetyModeration: json[ModelKey.safetyModeration] ?? true,
mode: json[ModelKey.mode] ?? BotMode.discussion, mode: json[ModelKey.mode] ?? BotMode.discussion,
targetLanguage: json[ModelKey.targetLanguage],
targetVoice: json[ModelKey.targetVoice],
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Discussion Mode Options // Discussion Mode Options
@ -97,6 +103,8 @@ class BotOptionsModel {
data[ModelKey.languageLevel] = languageLevel; data[ModelKey.languageLevel] = languageLevel;
data[ModelKey.safetyModeration] = safetyModeration; data[ModelKey.safetyModeration] = safetyModeration;
data[ModelKey.mode] = mode; data[ModelKey.mode] = mode;
data[ModelKey.targetLanguage] = targetLanguage;
data[ModelKey.targetVoice] = targetVoice;
data[ModelKey.discussionTopic] = discussionTopic; data[ModelKey.discussionTopic] = discussionTopic;
data[ModelKey.discussionKeywords] = discussionKeywords; data[ModelKey.discussionKeywords] = discussionKeywords;
data[ModelKey.discussionTriggerReactionEnabled] = data[ModelKey.discussionTriggerReactionEnabled] =
@ -153,6 +161,12 @@ class BotOptionsModel {
case ModelKey.textAdventureGameMasterInstructions: case ModelKey.textAdventureGameMasterInstructions:
textAdventureGameMasterInstructions = value; textAdventureGameMasterInstructions = value;
break; break;
case ModelKey.targetLanguage:
targetLanguage = value;
break;
case ModelKey.targetVoice:
targetVoice = value;
break;
default: default:
throw Exception('Invalid key for bot options - $key'); throw Exception('Invalid key for bot options - $key');
} }

@ -1,95 +0,0 @@
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotCustomSystemPromptInput extends StatelessWidget {
final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents
final void Function(BotOptionsModel) onChanged;
const ConversationBotCustomSystemPromptInput({
super.key,
required this.initialBotOptions,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
String customSystemPrompt = initialBotOptions.customSystemPrompt ?? "";
final TextEditingController textFieldController =
TextEditingController(text: customSystemPrompt);
final GlobalKey<FormState> customSystemPromptFormKey =
GlobalKey<FormState>();
void setBotCustomSystemPromptAction() async {
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
L10n.of(context)!.conversationBotCustomZone_customSystemPromptLabel,
),
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(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () {
if (customSystemPromptFormKey.currentState!.validate()) {
if (customSystemPrompt !=
initialBotOptions.customSystemPrompt) {
initialBotOptions.customSystemPrompt = customSystemPrompt;
onChanged.call(initialBotOptions);
}
Navigator.of(context).pop();
}
},
),
],
),
);
}
return ListTile(
onTap: setBotCustomSystemPromptAction,
title: Text(
initialBotOptions.customSystemPrompt ??
L10n.of(context)!
.conversationBotCustomZone_customSystemPromptPlaceholder,
),
subtitle: customSystemPrompt.isEmpty
? Text(
L10n.of(context)!
.conversationBotCustomZone_customSystemPromptEmptyError,
style: const TextStyle(color: Colors.red),
)
: null,
);
}
}

@ -1,57 +0,0 @@
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';
class ConversationBotCustomZone extends StatelessWidget {
final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents
final void Function(BotOptionsModel) onChanged;
const ConversationBotCustomZone({
super.key,
required this.initialBotOptions,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
ConversationBotDynamicZoneTitle(
title: L10n.of(context)!.conversationBotCustomZone_title,
),
ConversationBotDynamicZoneLabel(
label: L10n.of(context)!
.conversationBotCustomZone_customSystemPromptLabel,
),
Padding(
padding: const EdgeInsets.all(8),
child: ConversationBotCustomSystemPromptInput(
initialBotOptions: initialBotOptions,
onChanged: onChanged,
),
),
const SizedBox(height: 12),
CheckboxListTile(
title: Text(
L10n.of(context)!
.conversationBotCustomZone_customTriggerReactionEnabledLabel,
),
enabled: false,
value: initialBotOptions.customTriggerReactionEnabled ?? true,
onChanged: (value) {
initialBotOptions.customTriggerReactionEnabled = value ?? true;
initialBotOptions.customTriggerReactionKey =
""; // hard code this for now
onChanged.call(initialBotOptions);
},
// make this input disabled always
),
const SizedBox(height: 12),
],
);
}
}

@ -1,74 +0,0 @@
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotDiscussionKeywordsInput extends StatelessWidget {
final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents
final void Function(BotOptionsModel) onChanged;
const ConversationBotDiscussionKeywordsInput({
super.key,
required this.initialBotOptions,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
String discussionKeywords = initialBotOptions.discussionKeywords ?? "";
final TextEditingController textFieldController =
TextEditingController(text: discussionKeywords);
void setBotDiscussionKeywordsAction() async {
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
L10n.of(context)!
.conversationBotDiscussionZone_discussionKeywordsLabel,
),
content: TextField(
minLines: 1,
maxLines: 10,
maxLength: 1000,
controller: textFieldController,
onChanged: (value) {
discussionKeywords = value;
},
),
actions: [
TextButton(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () {
if (discussionKeywords == "") return;
if (discussionKeywords !=
initialBotOptions.discussionKeywords) {
initialBotOptions.discussionKeywords = discussionKeywords;
onChanged.call(initialBotOptions);
Navigator.of(context).pop();
}
},
),
],
),
);
}
return ListTile(
onTap: setBotDiscussionKeywordsAction,
title: Text(
initialBotOptions.discussionKeywords ??
L10n.of(context)!
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
),
);
}
}

@ -1,73 +0,0 @@
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotDiscussionTopicInput extends StatelessWidget {
final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents
final void Function(BotOptionsModel) onChanged;
const ConversationBotDiscussionTopicInput({
super.key,
required this.initialBotOptions,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
String discussionTopic = initialBotOptions.discussionTopic ?? "";
final TextEditingController textFieldController =
TextEditingController(text: discussionTopic);
void setBotDiscussionTopicAction() async {
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
L10n.of(context)!
.conversationBotDiscussionZone_discussionTopicLabel,
),
content: TextField(
minLines: 1,
maxLines: 10,
maxLength: 1000,
controller: textFieldController,
onChanged: (value) {
discussionTopic = value;
},
),
actions: [
TextButton(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () {
if (discussionTopic == "") return;
if (discussionTopic != initialBotOptions.discussionTopic) {
initialBotOptions.discussionTopic = discussionTopic;
onChanged.call(initialBotOptions);
Navigator.of(context).pop();
}
},
),
],
),
);
}
return ListTile(
onTap: setBotDiscussionTopicAction,
title: Text(
initialBotOptions.discussionTopic ??
L10n.of(context)!
.conversationBotDiscussionZone_discussionTopicPlaceholder,
),
);
}
}

@ -1,70 +0,0 @@
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';
class ConversationBotDiscussionZone extends StatelessWidget {
final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents
final void Function(BotOptionsModel) onChanged;
const ConversationBotDiscussionZone({
super.key,
required this.initialBotOptions,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
ConversationBotDynamicZoneTitle(
title: L10n.of(context)!.conversationBotDiscussionZone_title,
),
ConversationBotDynamicZoneLabel(
label: L10n.of(context)!
.conversationBotDiscussionZone_discussionTopicLabel,
),
Padding(
padding: const EdgeInsets.all(8),
child: ConversationBotDiscussionTopicInput(
initialBotOptions: initialBotOptions,
onChanged: onChanged,
),
),
const SizedBox(height: 12),
ConversationBotDynamicZoneLabel(
label: L10n.of(context)!
.conversationBotDiscussionZone_discussionKeywordsLabel,
),
Padding(
padding: const EdgeInsets.all(8),
child: ConversationBotDiscussionKeywordsInput(
initialBotOptions: initialBotOptions,
onChanged: onChanged,
),
),
const SizedBox(height: 12),
CheckboxListTile(
title: Text(
L10n.of(context)!
.conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel,
),
enabled: false,
value: initialBotOptions.discussionTriggerReactionEnabled ?? true,
onChanged: (value) {
initialBotOptions.discussionTriggerReactionEnabled = value ?? true;
initialBotOptions.discussionTriggerReactionKey =
""; // hard code this for now
onChanged.call(initialBotOptions);
},
// make this input disabled always
),
const SizedBox(height: 12),
],
);
}
}

@ -1,27 +0,0 @@
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,
),
),
),
);
}
}

@ -1,31 +0,0 @@
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),
],
);
}
}

@ -1,44 +1,77 @@
import 'package:fluffychat/pangea/constants/bot_mode.dart'; import 'package:fluffychat/pangea/constants/bot_mode.dart';
import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'conversation_bot_discussion_zone.dart';
class ConversationBotModeDynamicZone extends StatelessWidget { class ConversationBotModeDynamicZone extends StatelessWidget {
final BotOptionsModel initialBotOptions; final BotOptionsModel initialBotOptions;
final void Function(BotOptionsModel) onChanged; final GlobalKey<FormState> formKey;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
const ConversationBotModeDynamicZone({ const ConversationBotModeDynamicZone({
super.key, super.key,
required this.initialBotOptions, required this.initialBotOptions,
required this.onChanged, required this.formKey,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final zoneMap = { final discussionChildren = [
BotMode.discussion: ConversationBotDiscussionZone( TextFormField(
initialBotOptions: initialBotOptions, decoration: InputDecoration(
onChanged: onChanged, hintText: L10n.of(context)!
.conversationBotDiscussionZone_discussionTopicPlaceholder,
),
controller: discussionTopicController,
validator: (value) => value == null || value.isEmpty
? L10n.of(context)!.enterDiscussionTopic
: null,
), ),
BotMode.custom: ConversationBotCustomZone( const SizedBox(height: 12),
initialBotOptions: initialBotOptions, TextFormField(
onChanged: onChanged, decoration: InputDecoration(
hintText: L10n.of(context)!
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
),
controller: discussionKeywordsController,
), ),
}; ];
if (!zoneMap.containsKey(initialBotOptions.mode)) {
return Container(); final customChildren = [
} TextFormField(
return Container( decoration: InputDecoration(
decoration: BoxDecoration( hintText: L10n.of(context)!
border: Border.all( .conversationBotCustomZone_customSystemPromptPlaceholder,
color: Theme.of(context).colorScheme.secondary,
width: 0.5,
), ),
borderRadius: const BorderRadius.all(Radius.circular(10)), validator: (value) => value == null || value.isEmpty
? L10n.of(context)!.enterPrompt
: null,
controller: customSystemPromptController,
), ),
child: zoneMap[initialBotOptions.mode], ];
return Column(
children: [
if (initialBotOptions.mode == BotMode.discussion) ...discussionChildren,
if (initialBotOptions.mode == BotMode.custom) ...customChildren,
const SizedBox(height: 12),
CheckboxListTile(
title: Text(
L10n.of(context)!
.conversationBotCustomZone_customTriggerReactionEnabledLabel,
),
enabled: false,
value: initialBotOptions.customTriggerReactionEnabled ?? true,
onChanged: null,
),
const SizedBox(height: 12),
],
); );
} }
} }

@ -24,56 +24,35 @@ class ConversationBotModeSelect extends StatelessWidget {
// L10n.of(context)!.conversationBotModeSelectOption_storyGame, // L10n.of(context)!.conversationBotModeSelectOption_storyGame,
}; };
return Padding( String? mode = initialMode;
padding: const EdgeInsets.all(12.0), if (!options.containsKey(initialMode)) {
child: Container( mode = null;
decoration: BoxDecoration( }
border: Border.all(
color: Theme.of(context).colorScheme.secondary, return DropdownButtonFormField(
width: 0.5, // Initial Value
), hint: Text(
borderRadius: const BorderRadius.all(Radius.circular(10)), options[mode ?? BotMode.discussion]!,
), overflow: TextOverflow.clip,
child: DropdownButton( textAlign: TextAlign.center,
// Initial Value ),
hint: Padding( // ),
padding: const EdgeInsets.only(left: 15), isExpanded: true,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
// Array list of items
items: [
for (final entry in options.entries)
DropdownMenuItem(
value: entry.key,
child: Text( child: Text(
options[initialMode ?? BotMode.discussion]!, entry.value,
style: const TextStyle().copyWith(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 14,
),
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
isExpanded: true, ],
underline: Container(), onChanged: onChanged,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
// Array list of items
items: [
for (final entry in options.entries)
DropdownMenuItem(
value: entry.key,
child: Padding(
padding: const EdgeInsets.only(left: 15),
child: Text(
entry.value,
style: const TextStyle().copyWith(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 14,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
),
),
],
onChanged: onChanged,
),
),
); );
} }
} }

@ -39,6 +39,13 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
ConversationBotSettingsState({Key? key}); ConversationBotSettingsState({Key? key});
final TextEditingController discussionTopicController =
TextEditingController();
final TextEditingController discussionKeywordsController =
TextEditingController();
final TextEditingController customSystemPromptController =
TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -55,6 +62,10 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
? Matrix.of(context).client.getRoomById(widget.activeSpaceId!) ? Matrix.of(context).client.getRoomById(widget.activeSpaceId!)
: null; : null;
isCreating = widget.room == null; isCreating = widget.room == null;
discussionKeywordsController.text = botOptions.discussionKeywords ?? "";
discussionTopicController.text = botOptions.discussionTopic ?? "";
customSystemPromptController.text = botOptions.customSystemPrompt ?? "";
} }
Future<void> setBotOption() async { Future<void> setBotOption() async {
@ -88,6 +99,109 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
); );
} }
void updateAllBotOptions() {
botOptions.discussionTopic = discussionTopicController.text;
botOptions.discussionKeywords = discussionKeywordsController.text;
botOptions.customSystemPrompt = customSystemPromptController.text;
}
Future<void> showBotOptionsDialog() async {
if (isCreating) return;
final bool? confirm = await showDialog<bool>(
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,
),
),
),
),
),
);
},
);
if (confirm == true) {
updateAllBotOptions();
updateBotOption(() => botOptions = botOptions);
final bool isBotRoomMember = await widget.room?.botIsInRoom ?? false;
if (addBot && !isBotRoomMember) {
await widget.room?.invite(BotName.byEnvironment);
} else if (!addBot && isBotRoomMember) {
await widget.room?.kick(BotName.byEnvironment);
}
}
}
Future<void> showNewRoomBotOptionsDialog() async {
final bool? confirm = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: addBot
? Text(
L10n.of(context)!.addConversationBotButtonTitleRemove,
)
: Text(
L10n.of(context)!.addConversationBotDialogTitleInvite,
),
actions: <Widget>[
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 (confirm == true) {
setState(() => addBot = true);
widget.room?.invite(BotName.byEnvironment);
} else {
setState(() => addBot = false);
widget.room?.kick(BotName.byEnvironment);
}
}
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedContainer( return AnimatedContainer(
@ -119,162 +233,137 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
), ),
trailing: isCreating trailing: isCreating
? ElevatedButton( ? ElevatedButton(
onPressed: () async { onPressed: showNewRoomBotOptionsDialog,
final bool? confirm = await showDialog<bool>( child: Text(
context: context, addBot
builder: (BuildContext context) { ? L10n.of(context)!.addConversationBotButtonRemove
return AlertDialog( : L10n.of(context)!.addConversationBotButtonInvite,
title: addBot ),
? Text(
L10n.of(context)!
.addConversationBotButtonTitleRemove,
)
: Text(
L10n.of(context)!
.addConversationBotDialogTitleInvite,
),
actions: <Widget>[
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 (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), : const Icon(Icons.settings),
onTap: isCreating onTap: showBotOptionsDialog,
? null
: () async {
final bool? confirm = await showDialog<bool>(
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,
);
},
),
],
),
),
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,
),
),
),
),
],
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(L10n.of(context)!.cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(
L10n.of(context)!
.conversationBotConfigConfirmChange,
),
),
],
),
);
},
);
if (confirm == true) {
updateBotOption(() {
botOptions = botOptions;
});
final bool isBotRoomMember =
await widget.room?.botIsInRoom ?? false;
if (addBot && !isBotRoomMember) {
await widget.room?.invite(BotName.byEnvironment);
} else if (!addBot && isBotRoomMember) {
await widget.room?.kick(BotName.byEnvironment);
}
}
},
), ),
if (isCreating && addBot) if (isCreating && addBot)
ConversationBotSettingsForm( Padding(
botOptions: botOptions, padding: const EdgeInsets.all(16),
child: Column(
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,
),
),
),
Form(
key: formKey,
child: ConversationBotSettingsForm(
botOptions: botOptions,
formKey: formKey,
discussionKeywordsController:
discussionKeywordsController,
discussionTopicController: discussionTopicController,
customSystemPromptController:
customSystemPromptController,
),
),
],
),
), ),
], ],
), ),
); );
} }
} }
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;
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,
});
@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(
children: [
const SizedBox(height: 20),
ConversationBotSettingsForm(
botOptions: botOptions,
formKey: formKey,
discussionKeywordsController: discussionKeywordsController,
discussionTopicController: discussionTopicController,
customSystemPromptController: customSystemPromptController,
),
],
),
),
),
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),
),
],
),
],
);
}
}

@ -3,15 +3,25 @@ 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_dynamic_zone.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.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/space/language_level_dropdown.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotSettingsForm extends StatefulWidget { class ConversationBotSettingsForm extends StatefulWidget {
final BotOptionsModel botOptions; final BotOptionsModel botOptions;
final GlobalKey<FormState> formKey;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
const ConversationBotSettingsForm({ const ConversationBotSettingsForm({
super.key, super.key,
required this.botOptions, required this.botOptions,
required this.formKey,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
}); });
@override @override
@ -21,8 +31,6 @@ class ConversationBotSettingsForm extends StatefulWidget {
class ConversationBotSettingsFormState class ConversationBotSettingsFormState
extends State<ConversationBotSettingsForm> { extends State<ConversationBotSettingsForm> {
final formKey = GlobalKey<FormState>();
late BotOptionsModel botOptions; late BotOptionsModel botOptions;
@override @override
@ -35,17 +43,48 @@ class ConversationBotSettingsFormState
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
Padding( DropdownButtonFormField(
padding: const EdgeInsets.all(12.0), // Initial Value
child: Text( hint: Text(
L10n.of(context)!.conversationLanguageLevel, L10n.of(context)!.selectBotLanguage,
style: TextStyle( overflow: TextOverflow.clip,
color: Theme.of(context).colorScheme.secondary, textAlign: TextAlign.center,
fontWeight: FontWeight.bold,
fontSize: 16,
),
), ),
value: botOptions.targetLanguage,
isExpanded: true,
icon: const Icon(Icons.keyboard_arrow_down),
items: MatrixState.pangeaController.pLanguageStore.targetOptions
.map((language) {
return DropdownMenuItem(
value: language.langCode,
child: Text(
language.getDisplayName(context) ?? language.langCode,
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
);
}).toList(),
onChanged: (String? newValue) => {
setState(() => botOptions.targetLanguage = newValue!),
},
), ),
const SizedBox(height: 12),
DropdownButtonFormField<String>(
// Initial Value
hint: Text(
L10n.of(context)!.chooseVoice,
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
value: botOptions.targetVoice,
isExpanded: true,
icon: const Icon(Icons.keyboard_arrow_down),
items: const [],
onChanged: (String? newValue) => {
setState(() => botOptions.targetVoice = newValue!),
},
),
const SizedBox(height: 12),
LanguageLevelDropdown( LanguageLevelDropdown(
initialLevel: botOptions.languageLevel, initialLevel: botOptions.languageLevel,
onChanged: (int? newValue) => { onChanged: (int? newValue) => {
@ -53,13 +92,18 @@ class ConversationBotSettingsFormState
botOptions.languageLevel = newValue!; botOptions.languageLevel = newValue!;
}), }),
}, },
validator: (value) =>
value == null ? L10n.of(context)!.enterLanguageLevel : null,
), ),
Text( const SizedBox(height: 12),
L10n.of(context)!.conversationBotModeSelectDescription, Align(
style: TextStyle( alignment: Alignment.centerLeft,
color: Theme.of(context).colorScheme.secondary, child: Padding(
fontWeight: FontWeight.bold, padding: const EdgeInsets.symmetric(vertical: 12),
fontSize: 16, child: Text(
L10n.of(context)!.conversationBotModeSelectDescription,
style: Theme.of(context).textTheme.titleLarge,
),
), ),
), ),
ConversationBotModeSelect( ConversationBotModeSelect(
@ -70,18 +114,13 @@ class ConversationBotSettingsFormState
}), }),
}, },
), ),
Padding( const SizedBox(height: 12),
padding: const EdgeInsets.all(12), ConversationBotModeDynamicZone(
child: ConversationBotModeDynamicZone( initialBotOptions: botOptions,
initialBotOptions: botOptions, discussionTopicController: widget.discussionTopicController,
onChanged: (BotOptionsModel? newOptions) { discussionKeywordsController: widget.discussionKeywordsController,
if (newOptions != null) { customSystemPromptController: widget.customSystemPromptController,
setState(() { formKey: widget.formKey,
botOptions = newOptions;
});
}
},
),
), ),
], ],
); );

@ -1,10 +1,8 @@
import 'package:fluffychat/pangea/models/bot_options_model.dart'; 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:fluffychat/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
// TODO check how this looks
class ConversationBotTextAdventureZone extends StatelessWidget { class ConversationBotTextAdventureZone extends StatelessWidget {
final BotOptionsModel initialBotOptions; final BotOptionsModel initialBotOptions;
// call this to update propagate changes to parents // call this to update propagate changes to parents
@ -20,13 +18,6 @@ class ConversationBotTextAdventureZone extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
ConversationBotDynamicZoneTitle(
title: L10n.of(context)!.conversationBotTextAdventureZone_title,
),
ConversationBotDynamicZoneLabel(
label: L10n.of(context)!
.conversationBotTextAdventureZone_instructionLabel,
),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: ConversationBotGameMasterInstructionsInput( child: ConversationBotGameMasterInstructionsInput(

@ -6,71 +6,44 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
class LanguageLevelDropdown extends StatelessWidget { class LanguageLevelDropdown extends StatelessWidget {
final int? initialLevel; final int? initialLevel;
final void Function(int?)? onChanged; final void Function(int?)? onChanged;
final String? Function(int?)? validator;
const LanguageLevelDropdown({ const LanguageLevelDropdown({
super.key, super.key,
this.initialLevel, this.initialLevel,
this.onChanged, this.onChanged,
this.validator,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return DropdownButtonFormField(
padding: const EdgeInsets.all(12.0), // Initial Value
child: Container( hint: Text(
decoration: BoxDecoration( L10n.of(context)!.selectLanguageLevel,
border: Border.all( overflow: TextOverflow.clip,
color: Theme.of(context).colorScheme.secondary, textAlign: TextAlign.center,
width: 0.5, ),
), value: initialLevel,
borderRadius: const BorderRadius.all(Radius.circular(10)), isExpanded: true,
), // Down Arrow Icon
child: DropdownButton( icon: const Icon(Icons.keyboard_arrow_down),
// Initial Value // Array list of items
hint: Padding( items: LanguageLevelType.allInts.map((int levelOption) {
padding: const EdgeInsets.only(left: 15), return DropdownMenuItem(
child: Text( value: levelOption,
initialLevel == null child: Text(
? L10n.of(context)!.selectLanguageLevel LanguageLevelTextPicker.languageLevelText(
: LanguageLevelTextPicker.languageLevelText( context,
context, levelOption,
initialLevel!,
),
style: const TextStyle().copyWith(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 14,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
), ),
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
), ),
isExpanded: true, );
underline: Container(), }).toList(),
// Down Arrow Icon onChanged: onChanged,
icon: const Icon(Icons.keyboard_arrow_down), validator: validator,
// Array list of items
items: LanguageLevelType.allInts.map((int levelOption) {
return DropdownMenuItem(
value: levelOption,
child: Text(
LanguageLevelTextPicker.languageLevelText(
context,
levelOption,
),
style: const TextStyle().copyWith(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 14,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
);
}).toList(),
// After selecting the desired option,it will
// change button value to selected value
onChanged: onChanged,
),
),
); );
} }
} }

Loading…
Cancel
Save