implemented basic settings into the backend, renamed files, fixed bugs, and added back button functionality
This commit is contained in:
@@ -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
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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(
|
|
||||||
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(
|
// conversations.add(
|
||||||
// Conversation.completeConversation(
|
// Conversation.completeConversation(
|
||||||
// 1,
|
// 0,
|
||||||
// "Label test",
|
// "Test Conversation",
|
||||||
// "Sub-label Test",
|
// "Testing Functionality",
|
||||||
// [
|
// [
|
||||||
// (Chat("A different test. Only reply No.", 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("No.", false)),
|
// (Chat("Understood! How can I help?", false)),
|
||||||
// ]
|
// ]
|
||||||
// )
|
// )
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
maxConversationID = conversations.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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"));
|
loadedConversation = conversations[id];
|
||||||
maxConversationID++;
|
loadedChats = loadedConversation.chats;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedChats = conversations[conversationID].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 {
|
||||||
Reference in New Issue
Block a user