implemented basic settings into the backend, renamed files, fixed bugs, and added back button functionality

This commit is contained in:
Joshua
2024-07-10 15:36:55 -06:00
parent 5d2e162da8
commit e5b69aa2b6
9 changed files with 104 additions and 64 deletions
@@ -1,4 +1,4 @@
import 'package:app/util/server.dart'; import 'package:app/util/backend.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'chat.dart'; import 'chat.dart';
@@ -6,19 +6,30 @@ class Conversation with ChangeNotifier {
int conversationID; int conversationID;
String conversationLabel; String conversationLabel;
String conversationSubLabel; String conversationSubLabel;
bool history = true;
// History is disabled by default. This means chats are auto-deleted once the user exits that conversation.
bool conversationContext = true; // The prior prompts and responses are used as additional context for the next prompt and response.
late bool stream = false; // Update the UI with a stream of characters as received from the server, or as one text.
List<Chat> chats = []; List<Chat> chats = [];
Conversation(this.conversationID, this.conversationLabel, this.conversationSubLabel); Conversation(this.conversationID, this.conversationLabel, this.conversationSubLabel);
Conversation.completeConversation(this.conversationID, this.conversationLabel, this.conversationSubLabel, this.chats); Conversation.completeConversation(this.conversationID, this.conversationLabel, this.conversationSubLabel, this.chats);
void toggleHistory() => history = !history;
void toggleConversationContext() => conversationContext = !conversationContext;
void toggleStream() => stream = !stream;
void add(Chat message) { void add(Chat message) {
chats.add(message); chats.add(message);
notifyListeners(); notifyListeners();
} }
void loadChats() { void loadChats() {
chats = Backend.getChatsByConversationID(conversationID); Backend.loadConversation;
chats = Backend.loadedChats;
} }
void setLabel() { void setLabel() {
@@ -1,7 +1,7 @@
import 'package:app/components/conversation_thread/chat_bubble_model.dart'; import 'package:app/components/conversation_thread/chat_bubble_model.dart';
import 'package:app/components/conversation_thread/chat.dart'; import 'package:app/components/conversation_thread/chat.dart';
import 'package:app/components/conversation_thread/conversation.dart'; import 'package:app/components/conversation_thread/conversation.dart';
import 'package:app/util/server.dart'; import 'package:app/util/backend.dart';
import 'package:app/components/prompt_box/prompt_box_widget.dart'; import 'package:app/components/prompt_box/prompt_box_widget.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart';
@@ -1,5 +1,5 @@
import 'package:app/components/conversation_thread/conversation.dart'; import 'package:app/components/conversation_thread/conversation.dart';
import 'package:app/util/server.dart'; import 'package:app/util/backend.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '/components/conversation_thread/chat_bubble_widget.dart'; import '/components/conversation_thread/chat_bubble_widget.dart';
@@ -84,7 +84,7 @@ class _PopupWidgetState extends State<PopupWidget> {
child: Padding( child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 12, 0), padding: EdgeInsetsDirectional.fromSTEB(0, 0, 12, 0),
child: Text( child: Text(
'Refine the components modal.', 'A popup',
style: AppTheme.headlineMedium, style: AppTheme.headlineMedium,
), ),
), ),
@@ -106,12 +106,12 @@ class _PopupWidgetState extends State<PopupWidget> {
], ],
), ),
const Divider( const Divider(
height: 24, height: 12,
thickness: 2, thickness: 2,
color: Color(0xFFF1F4F8), color: Color(0xFFF1F4F8),
), ),
const Text( const Text(
'FlutterFlow is a visual development platform that allows you to easily create beautiful and responsive user interfaces for your mobile and web applications. With its drag-and-drop interface and pre-built components, you can quickly prototype and build your app without writing any code. \nAdditionally, FlutterFlow\'s real-time preview feature allows you to see your changes in real-time and make adjustments on the fly.', 'Test',
style: AppTheme.labelMedium, style: AppTheme.labelMedium,
), ),
Padding( Padding(
@@ -147,7 +147,7 @@ class _PopupWidgetState extends State<PopupWidget> {
onPressed: () { onPressed: () {
print('Button pressed ...'); print('Button pressed ...');
}, },
text: 'Create Task', text: 'Action',
options: FFButtonOptions( options: FFButtonOptions(
width: 130, width: 130,
height: 40, height: 40,
@@ -1,4 +1,4 @@
import 'package:app/util/server.dart'; import 'package:app/util/backend.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:easy_debounce/easy_debounce.dart'; import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -209,7 +209,8 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
validator: _model.textControllerValidator.asValidator(context), validator: _model.textControllerValidator.asValidator(context),
textInputAction: TextInputAction.send, // "Send" on keyboard textInputAction: TextInputAction.send, // "Send" on keyboard
onFieldSubmitted: (text) { onFieldSubmitted: (text) {
Backend.respondWhenReady(text); // TODO: Connect with multimedia entry_text = _model.textController.text;
Backend.respondWhenReady(entry_text); // TODO: Connect with multimedia
_model.textController?.clear(); // Clear the text field _model.textController?.clear(); // Clear the text field
}, },
), ),
+4 -11
View File
@@ -5,6 +5,7 @@ import '/components/conversation_thread/conversation_thread_widget.dart';
import '/components/model_item/model_item_widget.dart'; import '/components/model_item/model_item_widget.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:app/theme.dart'; import 'package:app/theme.dart';
@@ -64,17 +65,9 @@ class _ConversationWidgetState extends State<ConversationWidget> {
), ),
onPressed: () async { onPressed: () async {
print("something pressed in conversation_widget.dart"); print("something pressed in conversation_widget.dart");
// TODO // TODO: Smooth out the animation to prevent the conversations list from loading half-way up the screen.
// context.goNamed( SystemChannels.textInput.invokeMethod('TextInput.hide');
// 'ConversationsList', Navigator.pop(context);
// extra: <String, dynamic>{
// kTransitionInfoKey: TransitionInfo(
// hasTransition: true,
// transitionType: PageTransitionType.leftToRight,
// duration: const Duration(milliseconds: 250),
// ),
// },
// );
}, },
), ),
title: wrapWithModel( title: wrapWithModel(
@@ -2,11 +2,12 @@ import 'package:app/components/conversation_thread/conversation.dart';
import 'package:app/theme.dart'; import 'package:app/theme.dart';
import 'package:app/pages/conversation_widget.dart'; import 'package:app/pages/conversation_widget.dart';
import 'package:app/util/server.dart'; import 'package:app/util/backend.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:app/components/options_dialog/options_dialog_widget.dart';
import 'conversations_list_model.dart'; import 'conversations_list_model.dart';
export 'conversations_list_model.dart'; export 'conversations_list_model.dart';
@@ -48,6 +49,20 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Clean conversation list prior to building widgets, to prevent bloating UI with empty conversations.
// Additionally, delete conversations that have history disabled.
for(int i = 0; i < conversations.length; i++) {
if(
conversations[i].conversation.getChats().isEmpty ||
!conversations[i].conversation.history
) {
Backend.conversations.removeAt(i);
conversations.removeAt(i);
Backend.maxConversationID--; // maxConversationID will eventually be deprecated with better ID system.
i--;
}
}
return GestureDetector( return GestureDetector(
onTap: () => _model.unfocusNode.canRequestFocus onTap: () => _model.unfocusNode.canRequestFocus
? FocusScope.of(context).requestFocus(_model.unfocusNode) ? FocusScope.of(context).requestFocus(_model.unfocusNode)
@@ -181,6 +196,24 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
) )
}, },
onLongPress: () => { onLongPress: () => {
showModalBottomSheet(
isScrollControlled: true,
backgroundColor:
Colors.transparent,
enableDrag: false,
useSafeArea: true,
context: context,
builder: (context) {
return Padding(
padding:
MediaQuery.viewInsetsOf(
context),
child: const PopupWidget(),
);
},
).then((value) =>
safeSetState(() {})),
// TODO: Need to implement. // TODO: Need to implement.
print("opening options") print("opening options")
}, },
@@ -197,7 +230,7 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
right: 20.0, // Adjust positioning as needed right: 20.0, // Adjust positioning as needed
child: FloatingActionButton( child: FloatingActionButton(
onPressed: () => { onPressed: () => {
Backend.loadConversation(Backend.maxConversationID), // TODO: Dynamically get the next ID Backend.loadConversation(Backend.createConversation()), // TODO: Dynamically get the next ID
conversations.add(ConversationWidget(conversation: Backend.getLoadedConversation())), conversations.add(ConversationWidget(conversation: Backend.getLoadedConversation())),
Navigator.push( Navigator.push(
context, context,
@@ -1169,6 +1169,7 @@ class _SettingsWidgetState extends State<SettingsWidget>
focusColor: Colors.transparent, focusColor: Colors.transparent,
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
// TODO: Should open in a browser rather than in-app?
onTap: () async { onTap: () async {
await showModalBottomSheet( await showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
@@ -11,10 +11,10 @@ import 'model.dart';
// Uses Singleton design pattern to ensure that any server references across the app are using the same instance. // Uses Singleton design pattern to ensure that any server references across the app are using the same instance.
class Backend { class Backend {
// SERVER VARIABLES // SERVER VARIABLES
static final Backend _single_backend = Backend._internal(); static final Backend singleBackend = Backend._internal();
Backend._internal(); Backend._internal();
// TODO: One-time process to establish server connection is required for easy setup/maintenance. // TODO: One-time process to establish server connection is required for easy setup/maintenance.
static final url = "http://10.0.2.2:11434/api/"; // For emulated device in testing static const url = "http://10.0.2.2:11434/api/"; // For emulated device in testing
//static final url = "http://192.168.1.68:11434/api"; // For local access to home server //static final url = "http://192.168.1.68:11434/api"; // For local access to home server
static String endpoint = ""; static String endpoint = "";
static final Map<String, String> headers = {'Content-Type': 'application/json'}; static final Map<String, String> headers = {'Content-Type': 'application/json'};
@@ -27,16 +27,16 @@ class Backend {
// PRIVACY VARIABLES // PRIVACY-FOCUSED VARIABLES
static bool _historyEnabled = true; // TODO: Reorder based on most recent usage.
static late int conversationID;
static late int maxConversationID;
static List<Conversation> conversations = []; static List<Conversation> conversations = [];
static List<Chat> loadedChats = []; static List<Chat> loadedChats = [];
static late Conversation loadedConversation;
// RUNTIME & STATUS VARIABLES // RUNTIME & STATUS VARIABLES
static late int conversationID;
static late int maxConversationID;
static late Model model; static late Model model;
static String prompt = ""; static String prompt = "";
static String response = ""; static String response = "";
@@ -50,7 +50,7 @@ class Backend {
// loadedChats = conversations[conversationID].chats; // loadedChats = conversations[conversationID].chats;
return _single_backend; return singleBackend;
} }
void init() { void init() {
@@ -61,31 +61,19 @@ class Backend {
model = Model("llama3"); // Primarily used llama3, testing gemma2 model = Model("llama3"); // Primarily used llama3, testing gemma2
// TODO: Dynamically obtain conversations. // TODO: Dynamically obtain conversations.
conversations.add( // conversations.add(
Conversation.completeConversation( // Conversation.completeConversation(
0, // 0,
"Test Conversation", // "Test Conversation",
"Testing Functionality", // "Testing Functionality",
[ // [
(Chat("Hello there! You are part of a prototype and are running locally on my Macbook Air. Please keep your responses brief as every prompt freezes my computer.", true)), // (Chat("Hello there! You are part of a prototype and are running locally on my Macbook Air. Please keep your responses brief as every prompt freezes my computer.", true)),
(Chat("Understood! How can I help?", false)), // (Chat("Understood! How can I help?", false)),
] // ]
) // )
); // );
maxConversationID = conversations.length; maxConversationID = conversations.length;
// conversations.add(
// Conversation.completeConversation(
// 1,
// "Label test",
// "Sub-label Test",
// [
// (Chat("A different test. Only reply No.", true)),
// (Chat("No.", false)),
// ]
// )
// );
} }
@@ -115,21 +103,32 @@ class Backend {
// CONVERSATION MANAGEMENT // CONVERSATION MANAGEMENT
@Deprecated("")
static List<Conversation> getConversationsList() { return conversations; } static List<Conversation> getConversationsList() { return conversations; }
static Conversation getLoadedConversation() { return conversations[conversationID]; } static Conversation getLoadedConversation() { return loadedConversation; }
// Load conversation should be called prior to any other server requests. // Load conversation should be called prior to any other server requests.
static void loadConversation(id) { static void loadConversation(id) {
conversationID = id; conversationID = id;
if(maxConversationID == id) {
conversations.add(Conversation(conversationID, "Conversation $id", "Another AI conversation"));
maxConversationID++;
}
loadedChats = conversations[conversationID].chats; loadedConversation = conversations[id];
loadedChats = loadedConversation.chats;
} }
// TODO: Better conversation ID management is needed. Preferably randomized IDs to not indicate the number of conversations.
static int createConversation() {
int id = conversations.length;
conversations.add(Conversation(id, "Conversation $id", "Another AI conversation"));
return id;
}
static void deleteConversation(id) {
loadedChats = conversations[conversationID].chats; // TODO: Temporarily saving the chats for "undo" popup.
conversations.removeAt(id);
}
@Deprecated('Just directly get loadedChats list. Unless multiple chats can be opened simultaneously?')
static List<Chat> getChatsByConversationID(id) { return conversations[id].chats; } static List<Chat> getChatsByConversationID(id) { return conversations[id].chats; }
@@ -155,7 +154,7 @@ class Backend {
// TODO: Allow for different models to be dynamically selected. // TODO: Allow for different models to be dynamically selected.
Map<String, dynamic> data; Map<String, dynamic> data;
if(_historyEnabled) { if(conversations[conversationID].conversationContext) {
loadedChats = [...loadedChats, Chat(prompt, true)]; loadedChats = [...loadedChats, Chat(prompt, true)];
endpoint = "chat"; endpoint = "chat";
@@ -164,7 +163,7 @@ class Backend {
data = { data = {
"model": model.name, "model": model.name,
"messages": messageList, "messages": messageList,
"stream": false, "stream": loadedConversation.stream,
}; };
} }
else { else {
@@ -172,7 +171,7 @@ class Backend {
data = { data = {
"model": model.name, "model": model.name,
"prompt": prompt, "prompt": prompt,
"stream": false, "stream": loadedConversation.stream,
}; };
} }
@@ -182,6 +181,8 @@ class Backend {
// Send the HTTP request to the server. // Send the HTTP request to the server.
static void httpSendRequest() async { static void httpSendRequest() async {
// TODO: Add AES256 encryption to the prompt/messages thread.
// TODO: Add SHA256 checksum to be sent and verified to prevent/notify in case of data loss.
String json = constructPrompt(); String json = constructPrompt();
// Send the HTTP POST request // Send the HTTP POST request
@@ -193,7 +194,7 @@ class Backend {
if (serverResponse.statusCode == 200) { if (serverResponse.statusCode == 200) {
Map<String, dynamic> re = jsonDecode(serverResponse.body); Map<String, dynamic> re = jsonDecode(serverResponse.body);
if(_historyEnabled) { if(conversations[conversationID].conversationContext) {
response = re["message"]["content"]; response = re["message"]["content"];
loadedChats = [...loadedChats, Chat(response, false)]; loadedChats = [...loadedChats, Chat(response, false)];
} else { } else {