From ff4a54fbbcbedf1b234428a2867ace694e698727 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 8 Jul 2024 13:03:15 -0600 Subject: [PATCH] enabled togglable chat history (during runtime) that uses different api endpoints. --- .../components/conversation_thread/chat.dart | 19 ++- .../chat_bubble_widget.dart | 10 +- .../prompt_box/prompt_box_widget.dart | 27 ++-- mobile/app/lib/util/server.dart | 127 +++++++++--------- 4 files changed, 99 insertions(+), 84 deletions(-) diff --git a/mobile/app/lib/components/conversation_thread/chat.dart b/mobile/app/lib/components/conversation_thread/chat.dart index 0b9da7a..4d2fbf4 100644 --- a/mobile/app/lib/components/conversation_thread/chat.dart +++ b/mobile/app/lib/components/conversation_thread/chat.dart @@ -1,9 +1,22 @@ +import 'dart:convert'; +import 'package:json_annotation/json_annotation.dart'; + +@JsonSerializable() class Chat { - late String message; - late bool isMe; + final String content; + final bool role; // Most basic chat, text from server or from user. - Chat(this.message, this.isMe); + Chat(this.content, this.role); + + String toJson() { + Map c = { + "content": content, + "role": role, + }; + + return json.encode(c); + } // TODO: Overload constructors to allow for multimedia. } \ No newline at end of file diff --git a/mobile/app/lib/components/conversation_thread/chat_bubble_widget.dart b/mobile/app/lib/components/conversation_thread/chat_bubble_widget.dart index d4610a7..2477f96 100644 --- a/mobile/app/lib/components/conversation_thread/chat_bubble_widget.dart +++ b/mobile/app/lib/components/conversation_thread/chat_bubble_widget.dart @@ -9,7 +9,7 @@ class ChatBubbleWidget extends StatelessWidget{ @override Widget build(BuildContext context) { return Row( - mainAxisAlignment: c.isMe ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisAlignment: c.role ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(16.0), @@ -18,14 +18,14 @@ class ChatBubbleWidget extends StatelessWidget{ borderRadius: BorderRadius.only( topLeft: const Radius.circular(12.0), topRight: const Radius.circular(12.0), - bottomLeft: c.isMe ? const Radius.circular(12.0) : const Radius + bottomLeft: c.role ? const Radius.circular(12.0) : const Radius .circular(0.0), - bottomRight: c.isMe ? const Radius.circular(12.0) : const Radius + bottomRight: c.role ? const Radius.circular(12.0) : const Radius .circular(0.0), ), - color: c.isMe ? Colors.blue[200] : Colors.grey[200], + color: c.role ? Colors.blue[200] : Colors.grey[200], ), - child: Text(c.message), + child: Text(c.content), ), ], ); diff --git a/mobile/app/lib/components/prompt_box/prompt_box_widget.dart b/mobile/app/lib/components/prompt_box/prompt_box_widget.dart index 81688c0..5870e62 100644 --- a/mobile/app/lib/components/prompt_box/prompt_box_widget.dart +++ b/mobile/app/lib/components/prompt_box/prompt_box_widget.dart @@ -1,3 +1,4 @@ +import 'package:app/util/server.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/material.dart'; @@ -58,8 +59,9 @@ class _PromptBoxWidgetState extends State { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.end, + // TODO: Implement the context upload buttons children: [ - Expanded( + Expanded( // TODO: File context upload child: Padding( padding: const EdgeInsets.all(14), child: FlutterFlowIconButton( @@ -78,7 +80,7 @@ class _PromptBoxWidgetState extends State { ), ), ), - Expanded( + Expanded( // TODO: Photo context upload child: Padding( padding: const EdgeInsets.all(14), child: FlutterFlowIconButton( @@ -97,7 +99,7 @@ class _PromptBoxWidgetState extends State { ), ), ), - Expanded( + Expanded( // TODO: Microphone context upload -> or STT?? child: Padding( padding: const EdgeInsets.all(14), child: FlutterFlowIconButton( @@ -116,7 +118,7 @@ class _PromptBoxWidgetState extends State { ), ), ), - Expanded( + Expanded( // TODO: Video context upload child: Padding( padding: const EdgeInsets.all(14), child: FlutterFlowIconButton( @@ -135,7 +137,7 @@ class _PromptBoxWidgetState extends State { ), ), ), - Expanded( + Expanded( // TODO: Camera context upload child: Padding( padding: const EdgeInsets.all(14), child: FlutterFlowIconButton( @@ -173,8 +175,6 @@ class _PromptBoxWidgetState extends State { ), autofocus: true, textCapitalization: TextCapitalization.none, - textInputAction: TextInputAction.go, - obscureText: false, decoration: InputDecoration( isDense: false, counterStyle: AppTheme.bodyMedium, @@ -218,8 +218,11 @@ class _PromptBoxWidgetState extends State { textAlign: TextAlign.start, maxLines: 5, minLines: 1, - validator: - _model.textControllerValidator.asValidator(context), + validator: _model.textControllerValidator.asValidator(context), + textInputAction: TextInputAction.send, // "Send" on keyboard + onFieldSubmitted: (text) { + updateFromLatestInput(text); // TODO: Connect with multimedia + }, ), ), ), @@ -230,4 +233,10 @@ class _PromptBoxWidgetState extends State { ), ); } + + void updateFromLatestInput(String input) { + Server s = Server(); + s.setPrompt(input); + print(s.getResponse()); + } } diff --git a/mobile/app/lib/util/server.dart b/mobile/app/lib/util/server.dart index 8e7e365..dd8646e 100644 --- a/mobile/app/lib/util/server.dart +++ b/mobile/app/lib/util/server.dart @@ -16,6 +16,13 @@ class Server { // Status variables late bool _speechEnabled; + bool _historyEnabled = true; + + // Context variables + List chats = [ + (Chat("This is just a test. Only reply Yes.", true)), + (Chat("Yes.", false)), + ]; // Runtime variables String prompt = ""; @@ -31,40 +38,48 @@ class Server { } List getConversationByID(conversationID) { - this.conversationID = conversationID; - // TODO: Implement conversation IDs & retrieval from server - List chats = [ - (Chat("hello world!", true)), - (Chat("hello to you too!", false)), - ]; - + this.conversationID = conversationID; return chats; } + // TODO: Integrate with getConversationByID() + List> convertMessages() { + List> messageList = []; + for (Chat c in chats) { + messageList.add({ + "role": c.role ? "user" : "assistant", + "content": c.content, + }); + } + + return messageList; + } + // Using native on-device Speech-To-Text capability, get the server response when ready. Future sttGetResponseWhenReady() async { // TODO: In case on-device STT is unavailable, use server-side STT service. if (!_speechEnabled) "Unable to process Speech-To-Text on-device."; setPrompt(stt.getTextWhenReady() as String); - constructPrompt(); httpSendRequest(); return response; } + Future getResponseWhenReady(String p) async { + prompt = p; + httpSendRequest(); + return response; + } + // Arbitrary prompt, used for standard text interaction rather than STT. void setPrompt(String p) => prompt = p; // Get the last response or request a response from the server. String getResponse() { - if(response.isEmpty) { - constructPrompt(); httpSendRequest(); - } - - return response; + return response; } // Get the latest prompt. @@ -72,28 +87,46 @@ class Server { // Formats the prompt in JSON. String constructPrompt() { - // Construct the JSON payload - final Map data = { - "model": - "llama3", // TODO: Allow for different models to be dynamically selected. - "prompt": prompt, - "stream": false // TODO: Allow for toggleable states between stream - // TODO: Allow for additional flags, ie. continuous conversation, images, etc. - }; + // TODO: Allow for additional flags, ie. continuous conversation, images, etc. + // TODO: Allow for toggleable states between stream + // TODO: Allow for different models to be dynamically selected. + Map data; + + if(_historyEnabled) { + chats = [...chats, Chat(prompt, true)]; + + List> messageList = convertMessages(); + + data = { + "model": "llama3", + "messages": messageList, + "stream": false, + }; + } + else { + data = { + "model": "llama3", + "prompt": prompt, + "stream": false, + }; + } // Encode the JSON payload - final String body = json.encode(data); - - return body; + return json.encode(data); } // Send the HTTP request to the server. void httpSendRequest() async { String json = constructPrompt(); + print("\n\n$json\n\n"); + + String endpoint = "/api/"; + if(_historyEnabled) endpoint += "chat"; + else endpoint += "generate"; // Send the HTTP POST request final serverResponse = await http.post( - Uri.parse('$url/api/generate'), + Uri.parse('$url$endpoint'), headers: headers, body: json, ); @@ -104,46 +137,6 @@ class Server { } else { response = ('Error: ${serverResponse.statusCode}'); } + chats.add(Chat(response, false)); } -} -// -// -// // GUI components -// child: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Container( -// padding: const EdgeInsets.all(16), -// child: const Text( -// 'Recognized words:', -// style: TextStyle(fontSize: 20.0), -// ), -// ), -// Expanded( -// child: Container( -// padding: const EdgeInsets.all(16), -// child: Text( -// // If listening is active show the recognized words -// _speechToText.isListening -// ? prompt -// // If listening isn't active but could be tell the user -// // how to start it, otherwise indicate that speech -// // recognition is not yet ready or not supported on -// // the target device -// : _speechEnabled -// ? responseFromAI -// : 'Speech disabled', -// ), -// ), -// ), -// ], -// ), -// ), -// floatingActionButton: FloatingActionButton( -// onPressed: -// // If not yet listening for speech start, otherwise stop -// //sendPrompt, -// _speechToText.isNotListening ? _startListening : sendPrompt, -// tooltip: 'Listen', -// child: Icon(_speechToText.isNotListening ? Icons.mic_off : Icons.mic), -// ), +} \ No newline at end of file