merge main, resolve conflicts
This commit is contained in:
commit
f5c81fe8ec
51 changed files with 930 additions and 628 deletions
|
|
@ -3879,7 +3879,7 @@
|
|||
"define": "Define",
|
||||
"listen": "Listen",
|
||||
"addConversationBot": "Enable Conversation Bot",
|
||||
"addConversationBotDesc": "Add a bot to this group chat that will ask questions on a specific topic",
|
||||
"addConversationBotDesc": "Add a bot to this group chat",
|
||||
"convoBotSettingsTitle": "Conversation Bot Settings",
|
||||
"convoBotSettingsDescription": "Edit conversation topic and difficulty",
|
||||
"enterAConversationTopic": "Enter a conversation topic",
|
||||
|
|
@ -4003,13 +4003,21 @@
|
|||
"conversationBotCustomZone_title": "Custom Settings",
|
||||
"conversationBotCustomZone_customSystemPromptLabel": "System prompt",
|
||||
"conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt",
|
||||
"conversationBotCustomZone_customSystemPromptEmptyError": "Missing custom system prompt",
|
||||
"conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction",
|
||||
"botConfig": "Conversation Bot Settings",
|
||||
"addConversationBotDialogTitleInvite": "Confirm inviting conversation bot",
|
||||
"addConversationBotButtonInvite": "Invite",
|
||||
"addConversationBotDialogInviteConfirmation": "Invite",
|
||||
"addConversationBotButtonTitleRemove": "Confirm removing conversation bot",
|
||||
"addConversationBotButtonRemove": "Remove",
|
||||
"addConversationBotDialogRemoveConfirmation": "Remove",
|
||||
"conversationBotConfigConfirmChange": "Confirm",
|
||||
"conversationBotStatus": "Bot Status",
|
||||
"conversationBotTextAdventureZone_title": "Text Adventure",
|
||||
"conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions",
|
||||
"conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions",
|
||||
"conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions",
|
||||
"studentAnalyticsNotAvailable": "Student data not currently available",
|
||||
"roomDataMissing": "Some data may be missing from rooms in which you are not a member.",
|
||||
"updatePhoneOS": "You may need to update your device's OS version.",
|
||||
|
|
|
|||
|
|
@ -328,7 +328,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
await Matrix.of(context).client.roomsLoading;
|
||||
choreographer.setRoomId(roomId);
|
||||
});
|
||||
// Pangea#
|
||||
_tryLoadTimeline();
|
||||
|
|
@ -1124,8 +1123,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
inputFocus.requestFocus();
|
||||
}
|
||||
|
||||
void scrollToEventId(String eventId) async {
|
||||
final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId);
|
||||
void scrollToEventId(
|
||||
String eventId, {
|
||||
bool highlightEvent = true,
|
||||
}) async {
|
||||
final eventIndex = timeline!.events
|
||||
.where((event) => event.isVisibleInGui)
|
||||
.toList()
|
||||
.indexWhere((e) => e.eventId == eventId);
|
||||
if (eventIndex == -1) {
|
||||
setState(() {
|
||||
timeline = null;
|
||||
|
|
@ -1141,11 +1146,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
scrollToEventIdMarker = eventId;
|
||||
});
|
||||
if (highlightEvent) {
|
||||
setState(() {
|
||||
scrollToEventIdMarker = eventId;
|
||||
});
|
||||
}
|
||||
await scrollController.scrollToIndex(
|
||||
eventIndex,
|
||||
eventIndex + 1,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
preferPosition: AutoScrollPosition.middle,
|
||||
);
|
||||
_updateScrollController();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
|||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -299,216 +298,232 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: controller.clearSingleSelectedEvent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
return ChatEventList(
|
||||
controller: controller,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
// ignore: deprecated_member_use
|
||||
.surfaceVariant,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: controller.room.isAbandonedDMRoom == true
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// #Pangea
|
||||
if (controller.room.isRoomAdmin)
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.archive_outlined,
|
||||
),
|
||||
onPressed: controller.archiveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.archive,
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
// #Pangea
|
||||
// Icons.archive_outlined,
|
||||
Icons.arrow_forward,
|
||||
// Pangea#
|
||||
),
|
||||
onPressed: controller.leaveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.leave,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.forum_outlined,
|
||||
),
|
||||
onPressed: controller.recreateChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.reopenChat,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
:
|
||||
// #Pangea
|
||||
null,
|
||||
// Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const ConnectionStatusHeader(),
|
||||
// ITBar(
|
||||
// choreographer:
|
||||
// controller.choreographer,
|
||||
// ),
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
// ChatEmojiPicker(controller),
|
||||
// ],
|
||||
// ),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
child:
|
||||
// #Pangea
|
||||
// Keep messages above minimum input bar height
|
||||
SizedBox(
|
||||
height: (PlatformInfos.isMobile ? 30 : 60),
|
||||
Stack(
|
||||
children: [
|
||||
// Pangea#
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: controller.clearSingleSelectedEvent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child:
|
||||
CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
return ChatEventList(
|
||||
controller: controller,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
// ignore: deprecated_member_use
|
||||
.surfaceVariant,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: controller.room.isAbandonedDMRoom ==
|
||||
true
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// #Pangea
|
||||
if (controller.room.isRoomAdmin)
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.archive_outlined,
|
||||
),
|
||||
onPressed:
|
||||
controller.archiveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.archive,
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
// #Pangea
|
||||
// Icons.archive_outlined,
|
||||
Icons.arrow_forward,
|
||||
// Pangea#
|
||||
),
|
||||
onPressed: controller.leaveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.leave,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.forum_outlined,
|
||||
),
|
||||
onPressed:
|
||||
controller.recreateChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.reopenChat,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
:
|
||||
// #Pangea
|
||||
null,
|
||||
// Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const ConnectionStatusHeader(),
|
||||
// ITBar(
|
||||
// choreographer:
|
||||
// controller.choreographer,
|
||||
// ),
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
// ChatEmojiPicker(controller),
|
||||
// ],
|
||||
// ),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// Keep messages above minimum input bar height
|
||||
const SizedBox(
|
||||
height: 60,
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
// #Pangea
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!controller.selectMode)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: 10,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StartIGCButton(
|
||||
controller: controller,
|
||||
),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
// #Pangea
|
||||
// if (controller.dragging)
|
||||
// Container(
|
||||
// color: Theme.of(context)
|
||||
// .scaffoldBackgroundColor
|
||||
// .withOpacity(0.9),
|
||||
// alignment: Alignment.center,
|
||||
// child: const Icon(
|
||||
// Icons.upload_outlined,
|
||||
// size: 100,
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
),
|
||||
// #Pangea
|
||||
// if (controller.dragging)
|
||||
// Container(
|
||||
// color: Theme.of(context)
|
||||
// .scaffoldBackgroundColor
|
||||
// .withOpacity(0.9),
|
||||
// alignment: Alignment.center,
|
||||
// child: const Icon(
|
||||
// Icons.upload_outlined,
|
||||
// size: 100,
|
||||
// ),
|
||||
// ),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!controller.selectMode)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: 10,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StartIGCButton(
|
||||
controller: controller,
|
||||
),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ class InputBar extends StatelessWidget {
|
|||
debounceDuration: const Duration(milliseconds: 50),
|
||||
// show suggestions after 50ms idle time (default is 300)
|
||||
// #Pangea
|
||||
key: controller!.choreographer.inputLayerLinkAndKey.key,
|
||||
key: controller?.choreographer.inputLayerLinkAndKey.key,
|
||||
// builder: (context, controller, focusNode) => TextField(
|
||||
builder: (context, _, focusNode) => TextField(
|
||||
// Pangea#
|
||||
|
|
@ -504,11 +504,11 @@ class InputBar extends StatelessWidget {
|
|||
onSubmitted!(text);
|
||||
},
|
||||
// #Pangea
|
||||
style: controller?.isMaxLength ?? false
|
||||
style: controller?.exceededMaxLength ?? false
|
||||
? const TextStyle(color: Colors.red)
|
||||
: null,
|
||||
onTap: () {
|
||||
controller!.onInputTap(
|
||||
controller?.onInputTap(
|
||||
context,
|
||||
fNode: focusNode,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:fluffychat/pages/settings/settings.dart';
|
|||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_description_button.dart';
|
||||
import 'package:fluffychat/pangea/utils/set_class_name.dart';
|
||||
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/app_lock.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -43,8 +42,9 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
|
||||
// #Pangea
|
||||
final GlobalKey<AddToSpaceState> addToSpaceKey = GlobalKey<AddToSpaceState>();
|
||||
final GlobalKey<ConversationBotSettingsState> addConversationBotKey =
|
||||
GlobalKey<ConversationBotSettingsState>();
|
||||
final GlobalKey<ChatDetailsController>
|
||||
addConversationBotKey =
|
||||
GlobalKey<ChatDetailsController>();
|
||||
|
||||
bool displayAddStudentOptions = false;
|
||||
void toggleAddStudentOptions() =>
|
||||
|
|
|
|||
|
|
@ -138,17 +138,20 @@ class ChatDetailsView extends StatelessWidget {
|
|||
Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
child: Hero(
|
||||
tag: controller.widget
|
||||
.embeddedCloseButton !=
|
||||
null
|
||||
? 'embedded_content_banner'
|
||||
: 'content_banner',
|
||||
child: Avatar(
|
||||
mxContent: room.avatar,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
// #Pangea
|
||||
// Hero animation is causing weird visual glitch
|
||||
// Probably not worth keeping
|
||||
// child: Hero(
|
||||
// tag: controller.widget
|
||||
// .embeddedCloseButton !=
|
||||
// null
|
||||
// ? 'embedded_content_banner'
|
||||
// : 'content_banner',
|
||||
// Pangea#
|
||||
child: Avatar(
|
||||
mxContent: room.avatar,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
if (!room.isDirectChat &&
|
||||
|
|
|
|||
|
|
@ -911,11 +911,12 @@ class ChatListController extends State<ChatList>
|
|||
await client.roomsLoading;
|
||||
await client.accountDataLoading;
|
||||
await client.userDeviceKeysLoading;
|
||||
if (client.prevBatch == null) {
|
||||
// #Pangea
|
||||
// See here for explanation of this change: https://github.com/pangeachat/client/pull/539
|
||||
// if (client.prevBatch == null) {
|
||||
if (client.onSync.value?.nextBatch == null) {
|
||||
// Pangea#
|
||||
await client.onSync.stream.first;
|
||||
// #Pangea
|
||||
pangeaController.startChatWithBotIfNotPresent();
|
||||
//Pangea#
|
||||
|
||||
// Display first login bootstrap if enabled
|
||||
// #Pangea
|
||||
|
|
@ -930,9 +931,19 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
|
||||
// #Pangea
|
||||
await _initPangeaControllers(client);
|
||||
// Pangea#
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
waitForFirstSync = true;
|
||||
});
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
Future<void> _initPangeaControllers(Client client) async {
|
||||
if (mounted) {
|
||||
// TODO try not to await so much
|
||||
GoogleAnalytics.analyticsUserUpdate(client.userID);
|
||||
pangeaController.startChatWithBotIfNotPresent();
|
||||
await pangeaController.subscriptionController.initialize();
|
||||
pangeaController.afterSyncAndFirstLoginInitialization(context);
|
||||
await pangeaController.inviteBotToExistingSpaces();
|
||||
|
|
@ -942,14 +953,9 @@ class ChatListController extends State<ChatList>
|
|||
ErrorHandler.logError(
|
||||
m: "didn't run afterSyncAndFirstLoginInitialization because not mounted",
|
||||
);
|
||||
// debugger(when: kDebugMode);
|
||||
}
|
||||
// Pangea#
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
waitForFirstSync = true;
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void cancelAction() {
|
||||
if (selectMode == SelectMode.share) {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,34 @@ class NewGroupController extends State<NewGroup> {
|
|||
|
||||
if (!mounted) return;
|
||||
|
||||
// #Pangea
|
||||
// validate init bot options
|
||||
final addBot = addConversationBotKey.currentState?.addBot ?? false;
|
||||
if (addBot) {
|
||||
final botOptions = addConversationBotKey.currentState!.botOptions;
|
||||
if (botOptions.mode == "custom") {
|
||||
if (botOptions.customSystemPrompt == null ||
|
||||
botOptions.customSystemPrompt!.isEmpty) {
|
||||
setState(() {
|
||||
error = L10n.of(context)!
|
||||
.conversationBotCustomZone_customSystemPromptEmptyError;
|
||||
loading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (botOptions.mode == "text_adventure") {
|
||||
if (botOptions.textAdventureGameMasterInstructions == null ||
|
||||
botOptions.textAdventureGameMasterInstructions!.isEmpty) {
|
||||
setState(() {
|
||||
error = L10n.of(context)!
|
||||
.conversationBotCustomZone_instructionSystemPromptEmptyError;
|
||||
loading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final roomId = await client.createGroupChat(
|
||||
// #Pangea
|
||||
// visibility:
|
||||
|
|
|
|||
|
|
@ -370,15 +370,17 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
onTap: () => controller
|
||||
.participantAction(UserBottomSheetAction.unban),
|
||||
),
|
||||
if (user != null && user.id != client.userID)
|
||||
ListTile(
|
||||
textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
title: Text(L10n.of(context)!.reportUser),
|
||||
leading: const Icon(Icons.report_outlined),
|
||||
onTap: () => controller
|
||||
.participantAction(UserBottomSheetAction.report),
|
||||
),
|
||||
// #Pangea
|
||||
// if (user != null && user.id != client.userID)
|
||||
// ListTile(
|
||||
// textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
// iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
// title: Text(L10n.of(context)!.reportUser),
|
||||
// leading: const Icon(Icons.report_outlined),
|
||||
// onTap: () => controller
|
||||
// .participantAction(UserBottomSheetAction.report),
|
||||
// ),
|
||||
// Pangea#
|
||||
if (profileSearchError != null)
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class AlternativeTranslator {
|
|||
|
||||
final FullTextTranslationResponseModel results =
|
||||
await FullTextTranslationRepo.translate(
|
||||
accessToken: await choreographer.accessToken,
|
||||
accessToken: choreographer.accessToken,
|
||||
request: FullTextTranslationRequestModel(
|
||||
text: choreographer.itController.sourceText!,
|
||||
tgtLang: choreographer.l2LangCode!,
|
||||
|
|
@ -117,7 +117,7 @@ class AlternativeTranslator {
|
|||
}
|
||||
|
||||
similarityResponse = await SimilarityRepo.get(
|
||||
accessToken: await choreographer.accessToken,
|
||||
accessToken: choreographer.accessToken,
|
||||
request: SimilarityRequestModel(
|
||||
benchmark: results.bestTranslation,
|
||||
toCompare: [userTranslation!],
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ class Choreographer {
|
|||
|
||||
bool isFetching = false;
|
||||
Timer? debounceTimer;
|
||||
String? _roomId;
|
||||
ChoreoRecord choreoRecord = ChoreoRecord.newRecord;
|
||||
// last checked by IGC or translation
|
||||
String? _lastChecked;
|
||||
|
|
@ -403,7 +402,7 @@ class Choreographer {
|
|||
|
||||
PangeaTextController get textController => _textController;
|
||||
|
||||
Future<String> get accessToken => pangeaController.userController.accessToken;
|
||||
String get accessToken => pangeaController.userController.accessToken;
|
||||
|
||||
clear() {
|
||||
choreoMode = ChoreoMode.igc;
|
||||
|
|
@ -464,10 +463,7 @@ class Choreographer {
|
|||
setState();
|
||||
}
|
||||
|
||||
get roomId => _roomId;
|
||||
void setRoomId(String? roomId) {
|
||||
_roomId = roomId ?? '';
|
||||
}
|
||||
get roomId => chatController.roomId;
|
||||
|
||||
bool get _useCustomInput => [
|
||||
EditType.keyboard,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class IgcController {
|
|||
);
|
||||
|
||||
final IGCTextData igcTextDataResponse = await IgcRepo.getIGC(
|
||||
await choreographer.accessToken,
|
||||
choreographer.accessToken,
|
||||
igcRequest: reqBody,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class SpanDataController {
|
|||
response = _cache[cacheKey]!.data;
|
||||
} else {
|
||||
response = SpanDataRepo.getSpanDetails(
|
||||
await choreographer.accessToken,
|
||||
choreographer.accessToken,
|
||||
request: SpanDetailsRepoReqAndRes(
|
||||
userL1: choreographer.l1LangCode!,
|
||||
userL2: choreographer.l2LangCode!,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class ITFeedbackCardController extends State<ITFeedbackCard> {
|
|||
isTranslating = true;
|
||||
});
|
||||
FullTextTranslationRepo.translate(
|
||||
accessToken: await controller.userController.accessToken,
|
||||
accessToken: controller.userController.accessToken,
|
||||
request: FullTextTranslationRequestModel(
|
||||
text: res!.text,
|
||||
tgtLang: controller.languageController.userL1?.langCode ??
|
||||
|
|
|
|||
|
|
@ -41,14 +41,20 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
choreoListener?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void updateSpinnerState(_) {
|
||||
if (prevState != AssistanceState.fetching &&
|
||||
assistanceState == AssistanceState.fetching) {
|
||||
_controller?.repeat();
|
||||
} else if (prevState == AssistanceState.fetching &&
|
||||
assistanceState != AssistanceState.fetching) {
|
||||
_controller?.stop();
|
||||
_controller?.reverse();
|
||||
_controller?.reset();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() => prevState = assistanceState);
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ class ModelKey {
|
|||
"custom_trigger_reaction_enabled";
|
||||
static const String customTriggerReactionKey = "custom_trigger_reaction_key";
|
||||
|
||||
static const String textAdventureGameMasterInstructions =
|
||||
"text_adventure_game_master_instructions";
|
||||
|
||||
static const String prevEventId = "prev_event_id";
|
||||
static const String prevLastUpdated = "prev_last_updated";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ContextualDefinitionController {
|
|||
try {
|
||||
final ContextualDefinitionResponseModel res =
|
||||
await _ContextualDefinitionRepo.define(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
return res;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class ITFeedbackController {
|
|||
) async {
|
||||
try {
|
||||
final ITFeedbackResponseModel res = await _ITFeedbackRepo.get(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
return res;
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class LanguageDetectionController {
|
|||
return _cache[params]!.data;
|
||||
} else {
|
||||
final Future<LanguageDetectionResponse> response = _fetchResponse(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
params,
|
||||
);
|
||||
_cache[params] = _LanguageDetectionCacheItem(data: response);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class MessageDataController extends BaseController {
|
|||
Future<PangeaMessageTokens?> _getTokens(
|
||||
TokensRequestModel req,
|
||||
) async {
|
||||
final accessToken = await _pangeaController.userController.accessToken;
|
||||
final accessToken = _pangeaController.userController.accessToken;
|
||||
|
||||
final TokensResponseModel igcTextData =
|
||||
await TokensRepo.tokenize(accessToken, req);
|
||||
|
|
@ -195,7 +195,7 @@ class MessageDataController extends BaseController {
|
|||
try {
|
||||
final FullTextTranslationResponseModel res =
|
||||
await FullTextTranslationRepo.translate(
|
||||
accessToken: await _pangeaController.userController.accessToken,
|
||||
accessToken: _pangeaController.userController.accessToken,
|
||||
request: req,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class SpeechToTextController {
|
|||
return _cache[cacheKey]!.data;
|
||||
} else {
|
||||
final Future<SpeechToTextModel> response = _fetchResponse(
|
||||
accessToken: await _pangeaController.userController.accessToken,
|
||||
accessToken: _pangeaController.userController.accessToken,
|
||||
requestModel: requestModel,
|
||||
);
|
||||
_cache[cacheKey] = _SpeechToTextCacheItem(data: response);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class TextToSpeechController {
|
|||
return _cache[params]!.data;
|
||||
} else {
|
||||
final Future<TextToSpeechResponse> response = _fetchResponse(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
params,
|
||||
);
|
||||
_cache[params] = _TextToSpeechCacheItem(data: response);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
|
|||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
|
||||
import '../constants/local.key.dart';
|
||||
import '../models/user_model.dart';
|
||||
import '../repo/user_repo.dart';
|
||||
|
||||
|
|
@ -86,15 +85,6 @@ class UserController extends BaseController {
|
|||
createdAt: DateTime.now(),
|
||||
);
|
||||
final newProfile = Profile(userSettings: userSettings);
|
||||
|
||||
// we don't use the pangea profile anymore, but we still need
|
||||
// it to get access token for the choreographer, so create one
|
||||
await PUserRepo.repoCreatePangeaUser(
|
||||
userID: userId!,
|
||||
dob: dob.toIso8601String(),
|
||||
fullName: fullname!,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
await newProfile.saveProfileData(waitForDataInSync: true);
|
||||
}
|
||||
|
||||
|
|
@ -160,41 +150,13 @@ class UserController extends BaseController {
|
|||
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
|
||||
bool needNewJWT(String token) => Jwt.isExpired(token);
|
||||
|
||||
/// Retrieves the access token for the user. Looks for it locally,
|
||||
/// and if it's not found or expired, fetches it from the server.
|
||||
Future<String> get accessToken async {
|
||||
final localAccessToken =
|
||||
_pangeaController.pStoreService.read(PLocalKey.access);
|
||||
|
||||
if (localAccessToken == null || needNewJWT(localAccessToken)) {
|
||||
PangeaProfileResponse? userModel = await PUserRepo.fetchPangeaUserInfo(
|
||||
userID: userId!,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
// Oops, some accounts were made without creating pangea profiles, so they
|
||||
// don't have access to an access token yet. In that case, create a pangea profile.
|
||||
if (userModel?.access == null) {
|
||||
final dob = profile.userSettings.dateOfBirth;
|
||||
if (dob != null) {
|
||||
userModel = await PUserRepo.repoCreatePangeaUser(
|
||||
userID: userId!,
|
||||
dob: dob.toIso8601String(),
|
||||
fullName: fullname!,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
if (userModel?.access == null) {
|
||||
throw ("Trying to get accessToken with null userModel");
|
||||
}
|
||||
}
|
||||
}
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.access,
|
||||
userModel!.access,
|
||||
);
|
||||
return userModel.access;
|
||||
/// Retrieves matrix access token.
|
||||
String get accessToken {
|
||||
final token = _pangeaController.matrixState.client.accessToken;
|
||||
if (token == null) {
|
||||
throw ("Trying to get accessToken with null token. User is not logged in.");
|
||||
}
|
||||
|
||||
return localAccessToken;
|
||||
return token;
|
||||
}
|
||||
|
||||
/// Returns the full name of the user.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class WordController extends BaseController {
|
|||
if (local != null) return local;
|
||||
|
||||
final WordData remote = await WordRepo.getWordNetData(
|
||||
accessToken: await _pangeaController.userController.accessToken,
|
||||
accessToken: _pangeaController.userController.accessToken,
|
||||
fullText: fullText,
|
||||
word: word,
|
||||
userL1: userL1,
|
||||
|
|
|
|||
|
|
@ -71,12 +71,11 @@ extension MessageModeExtension on MessageMode {
|
|||
switch (this) {
|
||||
case MessageMode.translation:
|
||||
case MessageMode.textToSpeech:
|
||||
case MessageMode.practiceActivity:
|
||||
case MessageMode.definition:
|
||||
return event.messageType == MessageTypes.Text;
|
||||
case MessageMode.speechToText:
|
||||
return event.messageType == MessageTypes.Audio;
|
||||
default:
|
||||
case MessageMode.practiceActivity:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ extension GeneralInfoClientExtension on Client {
|
|||
final Event? originalEvent = await room!.getEventById(edittedEventId);
|
||||
if (originalEvent == null) return [];
|
||||
|
||||
final Timeline timeline = await room.getTimeline();
|
||||
final Timeline timeline = room.timeline ?? await room.getTimeline();
|
||||
final List<Event> editEvents = originalEvent
|
||||
.aggregatedEvents(
|
||||
timeline,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class BotOptionsModel {
|
|||
String? customSystemPrompt;
|
||||
bool? customTriggerReactionEnabled;
|
||||
String? customTriggerReactionKey;
|
||||
String? textAdventureGameMasterInstructions;
|
||||
|
||||
BotOptionsModel({
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -45,6 +46,11 @@ class BotOptionsModel {
|
|||
this.customSystemPrompt,
|
||||
this.customTriggerReactionEnabled = true,
|
||||
this.customTriggerReactionKey = "⏩",
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Text Adventure Mode Options
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
this.textAdventureGameMasterInstructions,
|
||||
});
|
||||
|
||||
factory BotOptionsModel.fromJson(json) {
|
||||
|
|
@ -73,6 +79,12 @@ class BotOptionsModel {
|
|||
customTriggerReactionEnabled:
|
||||
json[ModelKey.customTriggerReactionEnabled] ?? true,
|
||||
customTriggerReactionKey: json[ModelKey.customTriggerReactionKey] ?? "⏩",
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Text Adventure Mode Options
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
textAdventureGameMasterInstructions:
|
||||
json[ModelKey.textAdventureGameMasterInstructions],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +105,8 @@ class BotOptionsModel {
|
|||
data[ModelKey.customTriggerReactionEnabled] =
|
||||
customTriggerReactionEnabled ?? true;
|
||||
data[ModelKey.customTriggerReactionKey] = customTriggerReactionKey ?? "⏩";
|
||||
data[ModelKey.textAdventureGameMasterInstructions] =
|
||||
textAdventureGameMasterInstructions;
|
||||
return data;
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
@ -134,6 +148,9 @@ class BotOptionsModel {
|
|||
case ModelKey.customTriggerReactionKey:
|
||||
customTriggerReactionKey = value;
|
||||
break;
|
||||
case ModelKey.textAdventureGameMasterInstructions:
|
||||
textAdventureGameMasterInstructions = value;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Invalid key for bot options - $key');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,8 +140,6 @@ class IGCTextData {
|
|||
matches.removeAt(matchIndex);
|
||||
|
||||
for (final match in matches) {
|
||||
final matchOffset = match.match.offset;
|
||||
final matchLength = match.match.length;
|
||||
match.match.fullText = originalInput;
|
||||
if (match.match.offset > pangeaMatch.match.offset) {
|
||||
match.match.offset += replacement.length - pangeaMatch.match.length;
|
||||
|
|
@ -305,7 +303,7 @@ class IGCTextData {
|
|||
// create a pointer to the current index in the original input
|
||||
// and iterate until the pointer has reached the end of the input
|
||||
int currentIndex = 0;
|
||||
while (currentIndex < originalInput.characters.length - 1) {
|
||||
while (currentIndex < originalInput.characters.length) {
|
||||
// check if the pointer is at a match, and if so, get the index of the match
|
||||
final int matchIndex = matchRanges.indexWhere(
|
||||
(range) => currentIndex >= range[0] && currentIndex < range[1],
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
if (_timelinesCache.containsKey(use.chatId)) {
|
||||
timeline = _timelinesCache[use.chatId];
|
||||
} else {
|
||||
timeline = await msgRoom.getTimeline();
|
||||
timeline = msgRoom.timeline ?? await msgRoom.getTimeline();
|
||||
_timelinesCache[use.chatId] = timeline;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,8 +107,7 @@ class FindPartnerController extends State<FindPartner> {
|
|||
|
||||
UserProfileSearchResponse response;
|
||||
try {
|
||||
final String accessToken =
|
||||
await pangeaController.userController.accessToken;
|
||||
final String accessToken = pangeaController.userController.accessToken;
|
||||
response = await PUserRepo.searchUserProfiles(
|
||||
accessToken: accessToken,
|
||||
targetLanguage: targetLanguageSearch.langCode,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart';
|
||||
|
|
@ -32,15 +31,15 @@ class SettingsLearningView extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 8),
|
||||
if (controller.pangeaController.permissionsController.isUser18())
|
||||
SwitchListTile.adaptive(
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
title: Text(L10n.of(context)!.publicProfileTitle),
|
||||
subtitle: Text(L10n.of(context)!.publicProfileDesc),
|
||||
value: controller.pangeaController.userController.isPublic,
|
||||
onChanged: (bool isPublicProfile) =>
|
||||
controller.setPublicProfile(isPublicProfile),
|
||||
),
|
||||
// if (controller.pangeaController.permissionsController.isUser18())
|
||||
// SwitchListTile.adaptive(
|
||||
// activeColor: AppConfig.activeToggleColor,
|
||||
// title: Text(L10n.of(context)!.publicProfileTitle),
|
||||
// subtitle: Text(L10n.of(context)!.publicProfileDesc),
|
||||
// value: controller.pangeaController.userController.isPublic,
|
||||
// onChanged: (bool isPublicProfile) =>
|
||||
// controller.setPublicProfile(isPublicProfile),
|
||||
// ),
|
||||
ListTile(
|
||||
subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'dart:convert';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../models/user_model.dart';
|
||||
|
|
@ -11,33 +10,33 @@ import '../network/requests.dart';
|
|||
import '../network/urls.dart';
|
||||
|
||||
class PUserRepo {
|
||||
static Future<PangeaProfileResponse?> repoCreatePangeaUser({
|
||||
required String userID,
|
||||
required String dob,
|
||||
required fullName,
|
||||
required String matrixAccessToken,
|
||||
}) async {
|
||||
try {
|
||||
final Requests req = Requests(
|
||||
baseUrl: PApiUrls.baseAPI,
|
||||
matrixAccessToken: matrixAccessToken,
|
||||
);
|
||||
// static Future<PangeaProfileResponse?> repoCreatePangeaUser({
|
||||
// required String userID,
|
||||
// required String dob,
|
||||
// required fullName,
|
||||
// required String matrixAccessToken,
|
||||
// }) async {
|
||||
// try {
|
||||
// final Requests req = Requests(
|
||||
// baseUrl: PApiUrls.baseAPI,
|
||||
// matrixAccessToken: matrixAccessToken,
|
||||
// );
|
||||
|
||||
final Map<String, dynamic> body = {
|
||||
ModelKey.userFullName: fullName,
|
||||
ModelKey.userPangeaUserId: userID,
|
||||
ModelKey.userDateOfBirth: dob,
|
||||
};
|
||||
final resp = await req.post(
|
||||
url: PApiUrls.createUser,
|
||||
body: body,
|
||||
);
|
||||
return PangeaProfileResponse.fromJson(jsonDecode(resp.body));
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// final Map<String, dynamic> body = {
|
||||
// ModelKey.userFullName: fullName,
|
||||
// ModelKey.userPangeaUserId: userID,
|
||||
// ModelKey.userDateOfBirth: dob,
|
||||
// };
|
||||
// final resp = await req.post(
|
||||
// url: PApiUrls.createUser,
|
||||
// body: body,
|
||||
// );
|
||||
// return PangeaProfileResponse.fromJson(jsonDecode(resp.body));
|
||||
// } catch (err, s) {
|
||||
// ErrorHandler.logError(e: err, s: s);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
static Future<PangeaProfileResponse?> fetchPangeaUserInfo({
|
||||
required String userID,
|
||||
|
|
|
|||
|
|
@ -36,9 +36,10 @@ class GetChatListItemSubtitle {
|
|||
eventContextId = null;
|
||||
}
|
||||
|
||||
final Timeline timeline = await event.room.getTimeline(
|
||||
eventContextId: eventContextId,
|
||||
);
|
||||
final Timeline timeline = event.room.timeline != null &&
|
||||
event.room.timeline!.chunk.eventsMap.containsKey(eventContextId)
|
||||
? event.room.timeline!
|
||||
: await event.room.getTimeline(eventContextId: eventContextId);
|
||||
|
||||
if (moveBackInTimeline(event)) {
|
||||
event = timeline.events.firstWhereOrNull((e) => !moveBackInTimeline(e));
|
||||
|
|
|
|||
|
|
@ -435,7 +435,11 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
Container(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 300,
|
||||
maxHeight: 228,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
|
|||
|
||||
oldSelectedText = widget.selection.selectedText;
|
||||
final String accessToken =
|
||||
await MatrixState.pangeaController.userController.accessToken;
|
||||
MatrixState.pangeaController.userController.accessToken;
|
||||
|
||||
final resp = await FullTextTranslationRepo.translate(
|
||||
accessToken: accessToken,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget {
|
|||
final TextEditingController textFieldController =
|
||||
TextEditingController(text: customSystemPrompt);
|
||||
|
||||
final GlobalKey<FormState> customSystemPromptFormKey =
|
||||
GlobalKey<FormState>();
|
||||
|
||||
void setBotCustomSystemPromptAction() async {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
@ -28,14 +31,25 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget {
|
|||
title: Text(
|
||||
L10n.of(context)!.conversationBotCustomZone_customSystemPromptLabel,
|
||||
),
|
||||
content: TextField(
|
||||
minLines: 1,
|
||||
maxLines: 10,
|
||||
maxLength: 1000,
|
||||
controller: textFieldController,
|
||||
onChanged: (value) {
|
||||
customSystemPrompt = value;
|
||||
},
|
||||
content: Form(
|
||||
key: customSystemPromptFormKey,
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 10,
|
||||
maxLength: 1000,
|
||||
controller: textFieldController,
|
||||
onChanged: (value) {
|
||||
if (value.isNotEmpty) {
|
||||
customSystemPrompt = value;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'This field cannot be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
|
|
@ -47,11 +61,12 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget {
|
|||
TextButton(
|
||||
child: Text(L10n.of(context)!.ok),
|
||||
onPressed: () {
|
||||
if (customSystemPrompt == "") return;
|
||||
if (customSystemPrompt !=
|
||||
initialBotOptions.customSystemPrompt) {
|
||||
initialBotOptions.customSystemPrompt = customSystemPrompt;
|
||||
onChanged.call(initialBotOptions);
|
||||
if (customSystemPromptFormKey.currentState!.validate()) {
|
||||
if (customSystemPrompt !=
|
||||
initialBotOptions.customSystemPrompt) {
|
||||
initialBotOptions.customSystemPrompt = customSystemPrompt;
|
||||
onChanged.call(initialBotOptions);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
|
@ -68,6 +83,13 @@ class ConversationBotCustomSystemPromptInput extends StatelessWidget {
|
|||
L10n.of(context)!
|
||||
.conversationBotCustomZone_customSystemPromptPlaceholder,
|
||||
),
|
||||
subtitle: customSystemPrompt.isEmpty
|
||||
? Text(
|
||||
L10n.of(context)!
|
||||
.conversationBotCustomZone_customSystemPromptEmptyError,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
|
|
@ -16,35 +18,14 @@ class ConversationBotCustomZone extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print(initialBotOptions.toJson());
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
L10n.of(context)!.conversationBotCustomZone_title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
ConversationBotDynamicZoneTitle(
|
||||
title: L10n.of(context)!.conversationBotCustomZone_title,
|
||||
),
|
||||
const Divider(
|
||||
color: Colors.grey,
|
||||
thickness: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 0, 0),
|
||||
child: Text(
|
||||
L10n.of(context)!
|
||||
.conversationBotCustomZone_customSystemPromptLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ConversationBotDynamicZoneLabel(
|
||||
label: L10n.of(context)!
|
||||
.conversationBotCustomZone_customSystemPromptLabel,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_keywords_input.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_topic_input.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
|
|
@ -19,32 +21,12 @@ class ConversationBotDiscussionZone extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
L10n.of(context)!.conversationBotDiscussionZone_title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
ConversationBotDynamicZoneTitle(
|
||||
title: L10n.of(context)!.conversationBotDiscussionZone_title,
|
||||
),
|
||||
const Divider(
|
||||
color: Colors.grey,
|
||||
thickness: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 0, 0),
|
||||
child: Text(
|
||||
L10n.of(context)!
|
||||
.conversationBotDiscussionZone_discussionTopicLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ConversationBotDynamicZoneLabel(
|
||||
label: L10n.of(context)!
|
||||
.conversationBotDiscussionZone_discussionTopicLabel,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
|
@ -54,19 +36,9 @@ class ConversationBotDiscussionZone extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 0, 0),
|
||||
child: Text(
|
||||
L10n.of(context)!
|
||||
.conversationBotDiscussionZone_discussionKeywordsLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ConversationBotDynamicZoneLabel(
|
||||
label: L10n.of(context)!
|
||||
.conversationBotDiscussionZone_discussionKeywordsLabel,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConversationBotDynamicZoneLabel extends StatelessWidget {
|
||||
final String label;
|
||||
|
||||
const ConversationBotDynamicZoneLabel({
|
||||
super.key,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 0, 0),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConversationBotDynamicZoneTitle extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const ConversationBotDynamicZoneTitle({
|
||||
super.key,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
color: Colors.grey,
|
||||
thickness: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_text_adventure_zone.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'conversation_bot_discussion_zone.dart';
|
||||
|
|
@ -26,7 +27,10 @@ class ConversationBotModeDynamicZone extends StatelessWidget {
|
|||
onChanged: onChanged,
|
||||
),
|
||||
// "conversation": const ConversationBotConversationZone(),
|
||||
// "text_adventure": const ConversationBotTextAdventureZone(),
|
||||
"text_adventure": ConversationBotTextAdventureZone(
|
||||
initialBotOptions: initialBotOptions,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
};
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ class ConversationBotModeSelect extends StatelessWidget {
|
|||
"custom": L10n.of(context)!.conversationBotModeSelectOption_custom,
|
||||
// "conversation":
|
||||
// L10n.of(context)!.conversationBotModeSelectOption_conversation,
|
||||
// "text_adventure":
|
||||
// L10n.of(context)!.conversationBotModeSelectOption_textAdventure,
|
||||
"text_adventure":
|
||||
L10n.of(context)!.conversationBotModeSelectOption_textAdventure,
|
||||
};
|
||||
|
||||
return Padding(
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart';
|
||||
import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../constants/pangea_event_types.dart';
|
||||
import '../../extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import '../../utils/error_handler.dart';
|
||||
|
||||
class ConversationBotSettings extends StatefulWidget {
|
||||
final Room? room;
|
||||
final bool startOpen;
|
||||
|
|
@ -36,6 +33,7 @@ class ConversationBotSettings extends StatefulWidget {
|
|||
class ConversationBotSettingsState extends State<ConversationBotSettings> {
|
||||
late BotOptionsModel botOptions;
|
||||
late bool isOpen;
|
||||
late bool isCreating;
|
||||
bool addBot = false;
|
||||
Room? parentSpace;
|
||||
|
||||
|
|
@ -56,6 +54,22 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
|
|||
parentSpace = widget.activeSpaceId != null
|
||||
? Matrix.of(context).client.getRoomById(widget.activeSpaceId!)
|
||||
: null;
|
||||
isCreating = widget.room == null;
|
||||
}
|
||||
|
||||
Future<void> setBotOption() async {
|
||||
if (widget.room == null) return;
|
||||
try {
|
||||
await Matrix.of(context).client.setRoomStateWithKey(
|
||||
widget.room!.id,
|
||||
PangeaEventTypes.botOptions,
|
||||
'',
|
||||
botOptions.toJson(),
|
||||
);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateBotOption(void Function() makeLocalChange) async {
|
||||
|
|
@ -74,196 +88,191 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> setBotOption() async {
|
||||
if (widget.room == null) return;
|
||||
try {
|
||||
await Matrix.of(context).client.setRoomStateWithKey(
|
||||
widget.room!.id,
|
||||
PangeaEventTypes.botOptions,
|
||||
'',
|
||||
botOptions.toJson(),
|
||||
);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.convoBotSettingsTitle,
|
||||
isCreating
|
||||
? L10n.of(context)!.addConversationBot
|
||||
: L10n.of(context)!.botConfig,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(L10n.of(context)!.convoBotSettingsDescription),
|
||||
subtitle: isCreating
|
||||
? Text(L10n.of(context)!.addConversationBotDesc)
|
||||
: null,
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: const Icon(Icons.psychology_outlined),
|
||||
child: const BotFace(
|
||||
width: 30.0,
|
||||
expression: BotExpression.idle,
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
isOpen
|
||||
? Icons.keyboard_arrow_down_outlined
|
||||
: Icons.keyboard_arrow_right_outlined,
|
||||
),
|
||||
onTap: () => setState(() => isOpen = !isOpen),
|
||||
),
|
||||
if (isOpen)
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: isOpen ? null : 0,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.addConversationBot,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(L10n.of(context)!.addConversationBotDesc),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: const BotFace(
|
||||
width: 30.0,
|
||||
expression: BotExpression.idle,
|
||||
),
|
||||
),
|
||||
trailing: ElevatedButton(
|
||||
onPressed: () async {
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: addBot
|
||||
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)!
|
||||
.addConversationBotButtonTitleRemove,
|
||||
.addConversationBotDialogRemoveConfirmation,
|
||||
)
|
||||
: Text(
|
||||
L10n.of(context)!
|
||||
.addConversationBotDialogTitleInvite,
|
||||
.addConversationBotDialogInviteConfirmation,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (addBot) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(32, 16, 0, 0),
|
||||
child: Text(
|
||||
L10n.of(context)!.conversationLanguageLevel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: LanguageLevelDropdown(
|
||||
initialLevel: botOptions.languageLevel,
|
||||
onChanged: (int? newValue) => updateBotOption(() {
|
||||
botOptions.languageLevel = newValue!;
|
||||
}),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(32, 16, 0, 0),
|
||||
child: Text(
|
||||
L10n.of(context)!.conversationBotModeSelectDescription,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: ConversationBotModeSelect(
|
||||
initialMode: botOptions.mode,
|
||||
onChanged: (String? mode) => updateBotOption(
|
||||
() {
|
||||
botOptions.mode = mode ?? "discussion";
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(28, 0, 12, 0),
|
||||
child: ConversationBotModeDynamicZone(
|
||||
initialBotOptions: botOptions,
|
||||
onChanged: (BotOptionsModel? newOptions) {
|
||||
updateBotOption(() {
|
||||
if (newOptions != null) {
|
||||
botOptions = newOptions;
|
||||
}
|
||||
});
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm == true) {
|
||||
setState(() => addBot = true);
|
||||
widget.room?.invite(BotName.byEnvironment);
|
||||
} else {
|
||||
setState(() => addBot = false);
|
||||
widget.room?.kick(BotName.byEnvironment);
|
||||
}
|
||||
},
|
||||
child: addBot
|
||||
? Text(
|
||||
L10n.of(context)!.addConversationBotButtonRemove,
|
||||
)
|
||||
: Text(
|
||||
L10n.of(context)!.addConversationBotButtonInvite,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.settings),
|
||||
onTap: isCreating
|
||||
? null
|
||||
: () async {
|
||||
final bool? confirm = await showDialog<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) {
|
||||
if (addBot) {
|
||||
await widget.room?.invite(BotName.byEnvironment);
|
||||
} else {
|
||||
await widget.room?.kick(BotName.byEnvironment);
|
||||
}
|
||||
updateBotOption(() {
|
||||
botOptions = botOptions;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
if (isCreating && addBot)
|
||||
ConversationBotSettingsForm(
|
||||
botOptions: botOptions,
|
||||
),
|
||||
],
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart';
|
||||
import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class ConversationBotSettingsForm extends StatefulWidget {
|
||||
final BotOptionsModel botOptions;
|
||||
|
||||
const ConversationBotSettingsForm({
|
||||
super.key,
|
||||
required this.botOptions,
|
||||
});
|
||||
|
||||
@override
|
||||
ConversationBotSettingsFormState createState() =>
|
||||
ConversationBotSettingsFormState();
|
||||
}
|
||||
|
||||
class ConversationBotSettingsFormState
|
||||
extends State<ConversationBotSettingsForm> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
late BotOptionsModel botOptions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
botOptions = widget.botOptions;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
L10n.of(context)!.conversationLanguageLevel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
LanguageLevelDropdown(
|
||||
initialLevel: botOptions.languageLevel,
|
||||
onChanged: (int? newValue) => {
|
||||
setState(() {
|
||||
botOptions.languageLevel = newValue!;
|
||||
}),
|
||||
},
|
||||
),
|
||||
Text(
|
||||
L10n.of(context)!.conversationBotModeSelectDescription,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
ConversationBotModeSelect(
|
||||
initialMode: botOptions.mode,
|
||||
onChanged: (String? mode) => {
|
||||
setState(() {
|
||||
botOptions.mode = mode ?? "discussion";
|
||||
}),
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ConversationBotModeDynamicZone(
|
||||
initialBotOptions: botOptions,
|
||||
onChanged: (BotOptionsModel? newOptions) {
|
||||
if (newOptions != null) {
|
||||
setState(() {
|
||||
botOptions = newOptions;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class ConversationBotGameMasterInstructionsInput extends StatelessWidget {
|
||||
final BotOptionsModel initialBotOptions;
|
||||
// call this to update propagate changes to parents
|
||||
final void Function(BotOptionsModel) onChanged;
|
||||
|
||||
const ConversationBotGameMasterInstructionsInput({
|
||||
super.key,
|
||||
required this.initialBotOptions,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String gameMasterInstructions =
|
||||
initialBotOptions.textAdventureGameMasterInstructions ?? "";
|
||||
|
||||
final TextEditingController textFieldController =
|
||||
TextEditingController(text: gameMasterInstructions);
|
||||
|
||||
final GlobalKey<FormState> gameMasterInstructionsFormKey =
|
||||
GlobalKey<FormState>();
|
||||
|
||||
void setBotTextAdventureGameMasterInstructionsAction() async {
|
||||
showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text(
|
||||
L10n.of(context)!
|
||||
.conversationBotTextAdventureZone_instructionPlaceholder,
|
||||
),
|
||||
content: Form(
|
||||
key: gameMasterInstructionsFormKey,
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 10,
|
||||
maxLength: 1000,
|
||||
controller: textFieldController,
|
||||
onChanged: (value) {
|
||||
if (value.isNotEmpty) {
|
||||
gameMasterInstructions = value;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'This field cannot be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(L10n.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(L10n.of(context)!.ok),
|
||||
onPressed: () {
|
||||
if (gameMasterInstructionsFormKey.currentState!.validate()) {
|
||||
if (gameMasterInstructions !=
|
||||
initialBotOptions.textAdventureGameMasterInstructions) {
|
||||
initialBotOptions.textAdventureGameMasterInstructions =
|
||||
gameMasterInstructions;
|
||||
onChanged.call(initialBotOptions);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
onTap: setBotTextAdventureGameMasterInstructionsAction,
|
||||
title: Text(
|
||||
initialBotOptions.textAdventureGameMasterInstructions ??
|
||||
L10n.of(context)!
|
||||
.conversationBotTextAdventureZone_instructionPlaceholder,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,39 @@
|
|||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_text_adventure_game_master_instruction_input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class ConversationBotTextAdventureZone extends StatelessWidget {
|
||||
final BotOptionsModel initialBotOptions;
|
||||
// call this to update propagate changes to parents
|
||||
|
||||
final void Function(BotOptionsModel) onChanged;
|
||||
const ConversationBotTextAdventureZone({
|
||||
super.key,
|
||||
required this.initialBotOptions,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
return Column(
|
||||
children: [
|
||||
Text('Text Adventure Zone'),
|
||||
ConversationBotDynamicZoneTitle(
|
||||
title: L10n.of(context)!.conversationBotTextAdventureZone_title,
|
||||
),
|
||||
ConversationBotDynamicZoneLabel(
|
||||
label: L10n.of(context)!
|
||||
.conversationBotTextAdventureZone_instructionLabel,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ConversationBotGameMasterInstructionsInput(
|
||||
initialBotOptions: initialBotOptions,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class PangeaTextController extends TextEditingController {
|
|||
}
|
||||
|
||||
static const int maxLength = 1000;
|
||||
bool get isMaxLength => text.length == 1000;
|
||||
bool get exceededMaxLength => text.length >= maxLength;
|
||||
|
||||
bool forceKeepOpen = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ class GenerateVocabButtonState extends State<GenerateVocabButton> {
|
|||
|
||||
Future<List<Lemma>> _getWords() async {
|
||||
final ChatTopic topic = await TopicDataRepo.generate(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
request: TopicDataRequest(
|
||||
topicInfo: widget.topic,
|
||||
numWords: 10,
|
||||
|
|
@ -514,7 +514,7 @@ class PromptsFieldState extends State<PromptsField> {
|
|||
|
||||
Future<List<DiscussionPrompt>> _getPrompts() async {
|
||||
final ChatTopic res = await TopicDataRepo.generate(
|
||||
await _pangeaController.userController.accessToken,
|
||||
_pangeaController.userController.accessToken,
|
||||
request: TopicDataRequest(
|
||||
topicInfo: widget.topic,
|
||||
numPrompts: 10,
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue