From c8bcc3d93f0c44dbe2bbeb6df0875fb215463375 Mon Sep 17 00:00:00 2001 From: Dane Madsen Date: Wed, 13 Mar 2024 15:32:13 +1000 Subject: [PATCH 1/3] add ability to add character --- .../character/character_browser_page.dart | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/lib/ui/mobile/pages/character/character_browser_page.dart b/lib/ui/mobile/pages/character/character_browser_page.dart index bacbd9c4..893171d1 100644 --- a/lib/ui/mobile/pages/character/character_browser_page.dart +++ b/lib/ui/mobile/pages/character/character_browser_page.dart @@ -54,30 +54,51 @@ class _CharacterBrowserPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const GenericAppBar(title: "Character Browser"), - body: Consumer( - builder: (context, character, child) { - _current = character.key; + return Consumer( + builder: (context, character, child) { + _current = character.key; - if (!_characters.contains(character)) { - _characters.insert(0, character); - } + if (!_characters.contains(character)) { + _characters.insert(0, character); + } - return ListView.builder( - itemCount: _characters.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.all( - 8.0), // Adjust the padding value as needed - child: CharacterBrowserTile( - character: _characters[index], + return Scaffold( + appBar: AppBar( + elevation: 0.0, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: const Text("Character Browser"), + actions: [ + const Expanded(child: SizedBox()), + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + setState(() { + final newCharacter = Character(); + _characters.add(newCharacter); + character.from(newCharacter); + }); + }, ), - ); - }, - ); - }, - ), + ], + ), + body: ListView.builder( + itemCount: _characters.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all( + 8.0), // Adjust the padding value as needed + child: CharacterBrowserTile( + character: _characters[index], + ), + ); + }, + )); + }, ); } } From 8a09e29b8f0e873b06f55a3880b5cd35bd7a7521 Mon Sep 17 00:00:00 2001 From: Dane Madsen Date: Wed, 13 Mar 2024 16:00:07 +1000 Subject: [PATCH 2/3] character browser tile --- .../character/character_browser_page.dart | 6 +- .../character_customization_page.dart | 12 +- .../dropdowns/ai_platform_dropdown.dart | 71 ++++----- .../widgets/tiles/character_browser_tile.dart | 150 ++++++++++++++---- 4 files changed, 169 insertions(+), 70 deletions(-) diff --git a/lib/ui/mobile/pages/character/character_browser_page.dart b/lib/ui/mobile/pages/character/character_browser_page.dart index 893171d1..1f02d355 100644 --- a/lib/ui/mobile/pages/character/character_browser_page.dart +++ b/lib/ui/mobile/pages/character/character_browser_page.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:maid/providers/character.dart'; -import 'package:maid/ui/mobile/widgets/appbars/generic_app_bar.dart'; import 'package:maid/ui/mobile/widgets/tiles/character_browser_tile.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -94,6 +93,11 @@ class _CharacterBrowserPageState extends State { 8.0), // Adjust the padding value as needed child: CharacterBrowserTile( character: _characters[index], + onDelete: () { + setState(() { + _characters.removeAt(index); + }); + }, ), ); }, diff --git a/lib/ui/mobile/pages/character/character_customization_page.dart b/lib/ui/mobile/pages/character/character_customization_page.dart index 95198d69..79f47fc9 100644 --- a/lib/ui/mobile/pages/character/character_customization_page.dart +++ b/lib/ui/mobile/pages/character/character_customization_page.dart @@ -14,10 +14,12 @@ class CharacterCustomizationPage extends StatefulWidget { const CharacterCustomizationPage({super.key}); @override - State createState() => _CharacterCustomizationPageState(); + State createState() => + _CharacterCustomizationPageState(); } -class _CharacterCustomizationPageState extends State { +class _CharacterCustomizationPageState + extends State { late TextEditingController _nameController; late TextEditingController _descriptionController; late TextEditingController _personalityController; @@ -51,7 +53,6 @@ class _CharacterCustomizationPageState extends State appBar: const GenericAppBar(title: "Character Customization"), body: Consumer( builder: (context, character, child) { - SharedPreferences.getInstance().then((prefs) { prefs.setString("last_character", json.encode(character.toMap())); }); @@ -82,7 +83,8 @@ class _CharacterCustomizationPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (context) => const CharacterBrowserPage())); + builder: (context) => + const CharacterBrowserPage())); }, child: Text( "Switch Character", @@ -167,7 +169,7 @@ class _CharacterCustomizationPageState extends State title: Row( children: [ const Expanded( - child: Text("Character Name"), + child: Text("Name"), ), Expanded( flex: 2, diff --git a/lib/ui/mobile/widgets/dropdowns/ai_platform_dropdown.dart b/lib/ui/mobile/widgets/dropdowns/ai_platform_dropdown.dart index 5e047c53..94894def 100644 --- a/lib/ui/mobile/widgets/dropdowns/ai_platform_dropdown.dart +++ b/lib/ui/mobile/widgets/dropdowns/ai_platform_dropdown.dart @@ -21,43 +21,42 @@ class AiPlatformDropdown extends StatelessWidget { blendMode: BlendMode .srcIn, // This blend mode applies the shader to the text color. child: DropdownMenu( - dropdownMenuEntries: const [ - DropdownMenuEntry( - value: AiPlatformType.llamacpp, - label: "LlamaCPP", + dropdownMenuEntries: const [ + DropdownMenuEntry( + value: AiPlatformType.llamacpp, + label: "LlamaCPP", + ), + DropdownMenuEntry( + value: AiPlatformType.ollama, + label: "Ollama", + ), + DropdownMenuEntry( + value: AiPlatformType.openAI, + label: "OpenAI", + ), + DropdownMenuEntry( + value: AiPlatformType.mistralAI, + label: "MistralAI", + ), + ], + onSelected: (AiPlatformType? value) { + if (value != null) { + ai.apiType = value; + } + }, + initialSelection: ai.apiType, + inputDecorationTheme: const InputDecorationTheme( + labelStyle: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.white, + fontSize: 15.0), + hintStyle: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.white, + fontSize: 15.0), + floatingLabelBehavior: FloatingLabelBehavior.never, ), - DropdownMenuEntry( - value: AiPlatformType.ollama, - label: "Ollama", - ), - DropdownMenuEntry( - value: AiPlatformType.openAI, - label: "OpenAI", - ), - DropdownMenuEntry( - value: AiPlatformType.mistralAI, - label: "MistralAI", - ), - ], - onSelected: (AiPlatformType? value) { - if (value != null) { - ai.apiType = value; - } - }, - initialSelection: ai.apiType, - inputDecorationTheme: const InputDecorationTheme( - labelStyle: TextStyle( - fontWeight: FontWeight.normal, - color: Colors.white, - fontSize: 15.0), - hintStyle: TextStyle( - fontWeight: FontWeight.normal, - color: Colors.white, - fontSize: 15.0), - floatingLabelBehavior: FloatingLabelBehavior.never, - ), -width: 175 - ), + width: 175), ); }); } diff --git a/lib/ui/mobile/widgets/tiles/character_browser_tile.dart b/lib/ui/mobile/widgets/tiles/character_browser_tile.dart index ec811f15..94ead74f 100644 --- a/lib/ui/mobile/widgets/tiles/character_browser_tile.dart +++ b/lib/ui/mobile/widgets/tiles/character_browser_tile.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; import 'package:maid/providers/character.dart'; import 'package:maid/providers/user.dart'; +import 'package:maid/static/logger.dart'; import 'package:maid/static/utilities.dart'; import 'package:provider/provider.dart'; class CharacterBrowserTile extends StatefulWidget { final Character character; + final void Function() onDelete; - const CharacterBrowserTile({super.key, required this.character}); + const CharacterBrowserTile( + {super.key, required this.character, required this.onDelete}); @override State createState() => _CharacterBrowserTileState(); @@ -22,37 +25,128 @@ class _CharacterBrowserTileState extends State { builder: (context, character, user, child) { selected = character.key == widget.character.key; - return ListTile( - tileColor: Theme.of(context).colorScheme.primary, - selectedTileColor: Theme.of(context).colorScheme.secondary.withOpacity(0.25), - textColor: Colors.white, - selectedColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), + return GestureDetector( + child: ListTile( + tileColor: Theme.of(context).colorScheme.primary, + selectedTileColor: + Theme.of(context).colorScheme.secondary.withOpacity(0.25), + textColor: Colors.white, + selectedColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + // Square image with rounded corners of the character + leading: ClipRRect( + borderRadius: BorderRadius.circular( + 10.0), // Adjust the corner radius here + child: Image( + image: Image.file(widget.character.profile).image, + width: 50, // Adjust the size as needed + height: 50, + fit: BoxFit + .cover, // This ensures the image covers the square area + ), + ), + minLeadingWidth: 60, + title: Column(children: [ + Text(widget.character.name), + const SizedBox(height: 10.0), + Text( + Utilities.formatPlaceholders(widget.character.description, + user.name, widget.character.name), + style: const TextStyle(fontSize: 12.0), + ), + ]), + selected: selected), + onTap: () { + character.from(widget.character); + }, + onSecondaryTapUp: (details) => _onSecondaryTapUp(details, context), + onLongPressStart: (details) => _onLongPressStart(details, context), + ); + }, + ); + } + + void _onSecondaryTapUp(TapUpDetails details, BuildContext context) => + _showContextMenu(details.globalPosition, context); + + void _onLongPressStart(LongPressStartDetails details, BuildContext context) => + _showContextMenu(details.globalPosition, context); + + void _showContextMenu(Offset position, BuildContext context) { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final TextEditingController controller = + TextEditingController(text: widget.character.name); + + showMenu( + context: context, + position: RelativeRect.fromRect( + position & const Size(40, 40), // smaller rect, the touch area + Offset.zero & overlay.size, // Bigger rect, the entire screen + ), + items: [ + PopupMenuItem( + child: const Text('Delete'), + onTap: () { + Navigator.of(context).pop(); // Close the menu first + widget.onDelete(); + }, + ), + PopupMenuItem( + child: const Text('Rename'), + onTap: () { + Navigator.of(context).pop(); // Close the menu first + // Delayed execution to allow the popup menu to close properly + WidgetsBinding.instance.addPostFrameCallback((_) { + _showRenameDialog(context, controller); + }); + }, + ), + ], + ); + } + + void _showRenameDialog( + BuildContext context, TextEditingController controller) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text( + "Rename Session", + textAlign: TextAlign.center, ), - // Square image with rounded corners of the character - leading: ClipRRect( - borderRadius: BorderRadius.circular(10.0), // Adjust the corner radius here - child: Image( - image: Image.file(widget.character.profile).image, - width: 50, // Adjust the size as needed - height: 50, - fit: BoxFit.cover, // This ensures the image covers the square area + content: TextField( + controller: controller, + decoration: const InputDecoration( + hintText: "Enter new name", ), ), - minLeadingWidth: 60, - title: Column(children: [ - Text(widget.character.name), - const SizedBox(height: 10.0), - Text( - Utilities.formatPlaceholders(widget.character.description, user.name, widget.character.name), - style: const TextStyle(fontSize: 12.0), + actions: [ + FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + "Cancel", + style: Theme.of(context).textTheme.labelLarge, + ), ), - ]), - selected: selected, - onTap: () { - character.from(widget.character); - } + FilledButton( + onPressed: () { + String oldName = widget.character.name; + Logger.log( + "Updating character $oldName ====> ${controller.text}"); + widget.character.name = controller.text; + Navigator.of(context).pop(); + setState(() {}); + }, + child: Text( + "Rename", + style: Theme.of(context).textTheme.labelLarge, + ), + ), + ], ); }, ); From cb565c9d13580b935bbe88e427aba9480891c0d1 Mon Sep 17 00:00:00 2001 From: Dane Madsen Date: Wed, 13 Mar 2024 16:10:43 +1000 Subject: [PATCH 3/3] session tile --- lib/providers/session.dart | 6 +++ lib/ui/mobile/widgets/tiles/session_tile.dart | 50 ++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/providers/session.dart b/lib/providers/session.dart index 8fa3cbad..8dac2a07 100644 --- a/lib/providers/session.dart +++ b/lib/providers/session.dart @@ -67,6 +67,12 @@ class Session extends ChangeNotifier { notifyListeners(); } + void from(Session session) { + _root = session._root; + tail = _root.findTail(); + notifyListeners(); + } + void fromMap(Map inputJson) { _root = ChatNode.fromMap(inputJson); tail = _root.findTail(); diff --git a/lib/ui/mobile/widgets/tiles/session_tile.dart b/lib/ui/mobile/widgets/tiles/session_tile.dart index c8c7f94d..8cdd459b 100644 --- a/lib/ui/mobile/widgets/tiles/session_tile.dart +++ b/lib/ui/mobile/widgets/tiles/session_tile.dart @@ -24,39 +24,31 @@ class _SessionTileState extends State { } return GestureDetector( + onSecondaryTapUp: _onSecondaryTapUp, + onLongPressStart: _onLongPressStart, + onTap: () { + if (session.isBusy) return; + session.from(widget.session); + }, child: ListTile( - title: Text( - displayMessage, - style: Theme.of(context).textTheme.labelLarge, - ), - onTap: () { - if (session.isBusy) return; - session.fromMap(widget.session.toMap()); - }, - ), - onSecondaryTapUp: (details) => - _onSecondaryTapUp(details, context, session), - onLongPressStart: (details) => - _onLongPressStart(details, context, session), + title: Text( + displayMessage, + style: Theme.of(context).textTheme.labelLarge, + )), ); }, ); } - void _onSecondaryTapUp( - TapUpDetails details, BuildContext context, Session session) => - _showContextMenu(details.globalPosition, context, session); + void _onSecondaryTapUp(TapUpDetails details) => + _showContextMenu(details.globalPosition); - void _onLongPressStart(LongPressStartDetails details, BuildContext context, - Session session) => - _showContextMenu(details.globalPosition, context, session); + void _onLongPressStart(LongPressStartDetails details) => + _showContextMenu(details.globalPosition); - void _showContextMenu( - Offset position, BuildContext context, Session session) { + void _showContextMenu(Offset position) { final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; - final TextEditingController controller = - TextEditingController(text: widget.session.rootMessage); showMenu( context: context, @@ -78,7 +70,7 @@ class _SessionTileState extends State { Navigator.of(context).pop(); // Close the menu first // Delayed execution to allow the popup menu to close properly WidgetsBinding.instance.addPostFrameCallback((_) { - _showRenameDialog(context, controller, session); + _showRenameDialog(); }); }, ), @@ -86,8 +78,10 @@ class _SessionTileState extends State { ); } - void _showRenameDialog( - BuildContext context, TextEditingController controller, Session session) { + void _showRenameDialog() { + final TextEditingController controller = + TextEditingController(text: widget.session.rootMessage); + showDialog( context: context, builder: (context) { @@ -112,10 +106,10 @@ class _SessionTileState extends State { ), FilledButton( onPressed: () { - String oldName = session.rootMessage; + String oldName = widget.session.rootMessage; Logger.log( "Updating session $oldName ====> ${controller.text}"); - session.setRootMessage(controller.text); + widget.session.setRootMessage(controller.text); Navigator.of(context).pop(); setState(() {}); },