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",
"addConversationBot": "Enable Conversation Bot",
"addConversationBotDesc": "Add a bot to this chat",
"convoBotSettingsTitle": "Conversation Bot Settings",
"convoBotSettingsDescription": "Edit conversation topic and difficulty",
"enterAConversationTopic": "Enter a conversation topic",
"conversationTopic": "Conversation topic",
@ -4009,7 +4008,7 @@
"accuracy": "Accuracy",
"points": "Points",
"noPaymentInfo": "No payment info necessary!",
"conversationBotModeSelectDescription": "Bot mode",
"conversationBotModeSelectDescription": "Chat Activity",
"conversationBotModeSelectOption_discussion": "Discussion",
"conversationBotModeSelectOption_custom": "Custom",
"conversationBotModeSelectOption_conversation": "Conversation",
@ -4030,7 +4029,7 @@
"conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt",
"conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt",
"conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction",
"botConfig": "Conversation Bot Settings",
"botConfig": "Chat Settings",
"addConversationBotDialogTitleInvite": "Confirm inviting conversation bot",
"addConversationBotButtonInvite": "Invite",
"addConversationBotDialogInviteConfirmation": "Invite",
@ -4038,7 +4037,7 @@
"addConversationBotButtonRemove": "Remove",
"addConversationBotDialogRemoveConfirmation": "Remove",
"conversationBotConfigConfirmChange": "Confirm",
"conversationBotStatus": "Bot Status",
"conversationBotStatus": "Invite bot",
"conversationBotTextAdventureZone_title": "Text Adventure",
"conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions",
"conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions",
@ -4360,5 +4359,10 @@
"grammarCopyCase": "Case",
"grammarCopyDefinite": "Definiteness",
"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
// 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;
if (addBot) {
final botOptions = addConversationBotKey.currentState!.botOptions;

@ -121,6 +121,9 @@ class ModelKey {
static const String textAdventureGameMasterInstructions =
"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 prevLastUpdated = "prev_last_updated";

@ -21,6 +21,8 @@ class BotOptionsModel {
bool? customTriggerReactionEnabled;
String? customTriggerReactionKey;
String? textAdventureGameMasterInstructions;
String? targetLanguage;
String? targetVoice;
BotOptionsModel({
////////////////////////////////////////////////////////////////////////////
@ -31,6 +33,8 @@ class BotOptionsModel {
this.keywords = const [],
this.safetyModeration = true,
this.mode = BotMode.discussion,
this.targetLanguage,
this.targetVoice,
////////////////////////////////////////////////////////////////////////////
// Discussion Mode Options
@ -63,6 +67,8 @@ class BotOptionsModel {
: null,
safetyModeration: json[ModelKey.safetyModeration] ?? true,
mode: json[ModelKey.mode] ?? BotMode.discussion,
targetLanguage: json[ModelKey.targetLanguage],
targetVoice: json[ModelKey.targetVoice],
//////////////////////////////////////////////////////////////////////////
// Discussion Mode Options
@ -97,6 +103,8 @@ class BotOptionsModel {
data[ModelKey.languageLevel] = languageLevel;
data[ModelKey.safetyModeration] = safetyModeration;
data[ModelKey.mode] = mode;
data[ModelKey.targetLanguage] = targetLanguage;
data[ModelKey.targetVoice] = targetVoice;
data[ModelKey.discussionTopic] = discussionTopic;
data[ModelKey.discussionKeywords] = discussionKeywords;
data[ModelKey.discussionTriggerReactionEnabled] =
@ -153,6 +161,12 @@ class BotOptionsModel {
case ModelKey.textAdventureGameMasterInstructions:
textAdventureGameMasterInstructions = value;
break;
case ModelKey.targetLanguage:
targetLanguage = value;
break;
case ModelKey.targetVoice:
targetVoice = value;
break;
default:
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/models/bot_options_model.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart';
import 'package:flutter/material.dart';
import 'conversation_bot_discussion_zone.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ConversationBotModeDynamicZone extends StatelessWidget {
final BotOptionsModel initialBotOptions;
final void Function(BotOptionsModel) onChanged;
final GlobalKey<FormState> formKey;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
const ConversationBotModeDynamicZone({
super.key,
required this.initialBotOptions,
required this.onChanged,
required this.formKey,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
});
@override
Widget build(BuildContext context) {
final zoneMap = {
BotMode.discussion: ConversationBotDiscussionZone(
initialBotOptions: initialBotOptions,
onChanged: onChanged,
final discussionChildren = [
TextFormField(
decoration: InputDecoration(
hintText: L10n.of(context)!
.conversationBotDiscussionZone_discussionTopicPlaceholder,
),
controller: discussionTopicController,
validator: (value) => value == null || value.isEmpty
? L10n.of(context)!.enterDiscussionTopic
: null,
),
BotMode.custom: ConversationBotCustomZone(
initialBotOptions: initialBotOptions,
onChanged: onChanged,
const SizedBox(height: 12),
TextFormField(
decoration: InputDecoration(
hintText: L10n.of(context)!
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
),
controller: discussionKeywordsController,
),
};
if (!zoneMap.containsKey(initialBotOptions.mode)) {
return Container();
}
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: 0.5,
];
final customChildren = [
TextFormField(
decoration: InputDecoration(
hintText: L10n.of(context)!
.conversationBotCustomZone_customSystemPromptPlaceholder,
),
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,
};
return Padding(
padding: const EdgeInsets.all(12.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: 0.5,
),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
child: DropdownButton(
// Initial Value
hint: Padding(
padding: const EdgeInsets.only(left: 15),
String? mode = initialMode;
if (!options.containsKey(initialMode)) {
mode = null;
}
return DropdownButtonFormField(
// Initial Value
hint: Text(
options[mode ?? BotMode.discussion]!,
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
// ),
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(
options[initialMode ?? BotMode.discussion]!,
style: const TextStyle().copyWith(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 14,
),
entry.value,
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
),
isExpanded: true,
underline: Container(),
// 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,
),
),
],
onChanged: onChanged,
);
}
}

@ -39,6 +39,13 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
ConversationBotSettingsState({Key? key});
final TextEditingController discussionTopicController =
TextEditingController();
final TextEditingController discussionKeywordsController =
TextEditingController();
final TextEditingController customSystemPromptController =
TextEditingController();
@override
void initState() {
super.initState();
@ -55,6 +62,10 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
? Matrix.of(context).client.getRoomById(widget.activeSpaceId!)
: null;
isCreating = widget.room == null;
discussionKeywordsController.text = botOptions.discussionKeywords ?? "";
discussionTopicController.text = botOptions.discussionTopic ?? "";
customSystemPromptController.text = botOptions.customSystemPrompt ?? "";
}
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
Widget build(BuildContext context) {
return AnimatedContainer(
@ -119,162 +233,137 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
),
trailing: isCreating
? ElevatedButton(
onPressed: () 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);
}
},
child: addBot
? Text(
L10n.of(context)!.addConversationBotButtonRemove,
)
: Text(
L10n.of(context)!.addConversationBotButtonInvite,
),
onPressed: showNewRoomBotOptionsDialog,
child: Text(
addBot
? L10n.of(context)!.addConversationBotButtonRemove
: L10n.of(context)!.addConversationBotButtonInvite,
),
)
: const Icon(Icons.settings),
onTap: isCreating
? 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);
}
}
},
onTap: showBotOptionsDialog,
),
if (isCreating && addBot)
ConversationBotSettingsForm(
botOptions: botOptions,
Padding(
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_select.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_gen/gen_l10n/l10n.dart';
class ConversationBotSettingsForm extends StatefulWidget {
final BotOptionsModel botOptions;
final GlobalKey<FormState> formKey;
final TextEditingController discussionTopicController;
final TextEditingController discussionKeywordsController;
final TextEditingController customSystemPromptController;
const ConversationBotSettingsForm({
super.key,
required this.botOptions,
required this.formKey,
required this.discussionTopicController,
required this.discussionKeywordsController,
required this.customSystemPromptController,
});
@override
@ -21,8 +31,6 @@ class ConversationBotSettingsForm extends StatefulWidget {
class ConversationBotSettingsFormState
extends State<ConversationBotSettingsForm> {
final formKey = GlobalKey<FormState>();
late BotOptionsModel botOptions;
@override
@ -35,17 +43,48 @@ class ConversationBotSettingsFormState
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,
),
DropdownButtonFormField(
// Initial Value
hint: Text(
L10n.of(context)!.selectBotLanguage,
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
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(
initialLevel: botOptions.languageLevel,
onChanged: (int? newValue) => {
@ -53,13 +92,18 @@ class ConversationBotSettingsFormState
botOptions.languageLevel = newValue!;
}),
},
validator: (value) =>
value == null ? L10n.of(context)!.enterLanguageLevel : null,
),
Text(
L10n.of(context)!.conversationBotModeSelectDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
fontSize: 16,
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
L10n.of(context)!.conversationBotModeSelectDescription,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
ConversationBotModeSelect(
@ -70,18 +114,13 @@ class ConversationBotSettingsFormState
}),
},
),
Padding(
padding: const EdgeInsets.all(12),
child: ConversationBotModeDynamicZone(
initialBotOptions: botOptions,
onChanged: (BotOptionsModel? newOptions) {
if (newOptions != null) {
setState(() {
botOptions = newOptions;
});
}
},
),
const SizedBox(height: 12),
ConversationBotModeDynamicZone(
initialBotOptions: botOptions,
discussionTopicController: widget.discussionTopicController,
discussionKeywordsController: widget.discussionKeywordsController,
customSystemPromptController: widget.customSystemPromptController,
formKey: widget.formKey,
),
],
);

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

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

Loading…
Cancel
Save