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 'chat.dart';
@@ -6,19 +6,30 @@ class Conversation with ChangeNotifier {
int conversationID;
String conversationLabel;
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 = [];
Conversation(this.conversationID, this.conversationLabel, this.conversationSubLabel);
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) {
chats.add(message);
notifyListeners();
}
void loadChats() {
chats = Backend.getChatsByConversationID(conversationID);
Backend.loadConversation;
chats = Backend.loadedChats;
}
void setLabel() {
@@ -1,7 +1,7 @@
import 'package:app/components/conversation_thread/chat_bubble_model.dart';
import 'package:app/components/conversation_thread/chat.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:flutterflow_ui/flutterflow_ui.dart';
@@ -1,5 +1,5 @@
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 '/components/conversation_thread/chat_bubble_widget.dart';
@@ -84,7 +84,7 @@ class _PopupWidgetState extends State<PopupWidget> {
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 12, 0),
child: Text(
'Refine the components modal.',
'A popup',
style: AppTheme.headlineMedium,
),
),
@@ -106,12 +106,12 @@ class _PopupWidgetState extends State<PopupWidget> {
],
),
const Divider(
height: 24,
height: 12,
thickness: 2,
color: Color(0xFFF1F4F8),
),
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,
),
Padding(
@@ -147,7 +147,7 @@ class _PopupWidgetState extends State<PopupWidget> {
onPressed: () {
print('Button pressed ...');
},
text: 'Create Task',
text: 'Action',
options: FFButtonOptions(
width: 130,
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:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
@@ -209,7 +209,8 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
validator: _model.textControllerValidator.asValidator(context),
textInputAction: TextInputAction.send, // "Send" on keyboard
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
},
),
+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 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:app/theme.dart';
@@ -64,17 +65,9 @@ class _ConversationWidgetState extends State<ConversationWidget> {
),
onPressed: () async {
print("something pressed in conversation_widget.dart");
// TODO
// context.goNamed(
// 'ConversationsList',
// extra: <String, dynamic>{
// kTransitionInfoKey: TransitionInfo(
// hasTransition: true,
// transitionType: PageTransitionType.leftToRight,
// duration: const Duration(milliseconds: 250),
// ),
// },
// );
// TODO: Smooth out the animation to prevent the conversations list from loading half-way up the screen.
SystemChannels.textInput.invokeMethod('TextInput.hide');
Navigator.pop(context);
},
),
title: wrapWithModel(
@@ -2,11 +2,12 @@ import 'package:app/components/conversation_thread/conversation.dart';
import 'package:app/theme.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:flutter/material.dart';
import 'package:app/components/options_dialog/options_dialog_widget.dart';
import 'conversations_list_model.dart';
export 'conversations_list_model.dart';
@@ -48,6 +49,20 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
@override
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(
onTap: () => _model.unfocusNode.canRequestFocus
? FocusScope.of(context).requestFocus(_model.unfocusNode)
@@ -181,6 +196,24 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
)
},
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.
print("opening options")
},
@@ -197,7 +230,7 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
right: 20.0, // Adjust positioning as needed
child: FloatingActionButton(
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())),
Navigator.push(
context,
@@ -1169,6 +1169,7 @@ class _SettingsWidgetState extends State<SettingsWidget>
focusColor: Colors.transparent,
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
// TODO: Should open in a browser rather than in-app?
onTap: () async {
await showModalBottomSheet(
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.
class Backend {
// SERVER VARIABLES
static final Backend _single_backend = Backend._internal();
static final Backend singleBackend = Backend._internal();
Backend._internal();
// 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 String endpoint = "";
static final Map<String, String> headers = {'Content-Type': 'application/json'};
@@ -27,16 +27,16 @@ class Backend {
// PRIVACY VARIABLES
static bool _historyEnabled = true;
static late int conversationID;
static late int maxConversationID;
// PRIVACY-FOCUSED VARIABLES
// TODO: Reorder based on most recent usage.
static List<Conversation> conversations = [];
static List<Chat> loadedChats = [];
static late Conversation loadedConversation;
// RUNTIME & STATUS VARIABLES
static late int conversationID;
static late int maxConversationID;
static late Model model;
static String prompt = "";
static String response = "";
@@ -50,7 +50,7 @@ class Backend {
// loadedChats = conversations[conversationID].chats;
return _single_backend;
return singleBackend;
}
void init() {
@@ -61,31 +61,19 @@ class Backend {
model = Model("llama3"); // Primarily used llama3, testing gemma2
// TODO: Dynamically obtain conversations.
conversations.add(
Conversation.completeConversation(
0,
"Test Conversation",
"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("Understood! How can I help?", false)),
]
)
);
// conversations.add(
// Conversation.completeConversation(
// 0,
// "Test Conversation",
// "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("Understood! How can I help?", false)),
// ]
// )
// );
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
@Deprecated("")
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.
static void loadConversation(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; }
@@ -155,7 +154,7 @@ class Backend {
// TODO: Allow for different models to be dynamically selected.
Map<String, dynamic> data;
if(_historyEnabled) {
if(conversations[conversationID].conversationContext) {
loadedChats = [...loadedChats, Chat(prompt, true)];
endpoint = "chat";
@@ -164,7 +163,7 @@ class Backend {
data = {
"model": model.name,
"messages": messageList,
"stream": false,
"stream": loadedConversation.stream,
};
}
else {
@@ -172,7 +171,7 @@ class Backend {
data = {
"model": model.name,
"prompt": prompt,
"stream": false,
"stream": loadedConversation.stream,
};
}
@@ -182,6 +181,8 @@ class Backend {
// Send the HTTP request to the server.
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();
// Send the HTTP POST request
@@ -193,7 +194,7 @@ class Backend {
if (serverResponse.statusCode == 200) {
Map<String, dynamic> re = jsonDecode(serverResponse.body);
if(_historyEnabled) {
if(conversations[conversationID].conversationContext) {
response = re["message"]["content"];
loadedChats = [...loadedChats, Chat(response, false)];
} else {