dynamic chat bubbles implemented along with conversation support. currently only hard-coded dummy data.

This commit is contained in:
Joshua
2024-07-08 19:48:14 -06:00
parent 7547fe110f
commit 78e7f4b896
11 changed files with 308 additions and 259 deletions
@@ -1,7 +1,3 @@
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';
@JsonSerializable()
class Chat { class Chat {
final String content; final String content;
final bool role; final bool role;
@@ -9,14 +5,5 @@ class Chat {
// Most basic chat, text from server or from user. // Most basic chat, text from server or from user.
Chat(this.content, this.role); Chat(this.content, this.role);
String toJson() {
Map<String, dynamic> c = {
"content": content,
"role": role,
};
return json.encode(c);
}
// TODO: Overload constructors to allow for multimedia. // TODO: Overload constructors to allow for multimedia.
} }
@@ -1,15 +1,40 @@
import 'package:app/util/server.dart'; import 'package:app/util/server.dart';
import 'package:flutter/material.dart';
import 'chat.dart'; import 'chat.dart';
class Conversation { class Conversation with ChangeNotifier {
int conversationID; int conversationID;
String conversationLabel;
String conversationSubLabel;
List<Chat> chats = []; List<Chat> chats = [];
Conversation(this.conversationID) { Conversation(this.conversationID, this.conversationLabel, this.conversationSubLabel);
chats = Server().getConversationByID(conversationID);
Conversation.completeConversation(this.conversationID, this.conversationLabel, this.conversationSubLabel, this.chats);
void add(Chat message) {
chats.add(message);
notifyListeners();
}
void loadChats() {
chats = Server.getChatsByConversationID(conversationID);
}
void setLabel() {
// TODO: Need to implement
}
void setSubLabel() {
// TODO: Need to implement
} }
List<Chat> getChats() { List<Chat> getChats() {
return chats; return chats;
} }
void setLabels(String cLabel, String cSubLabel) {
conversationLabel = cLabel;
conversationSubLabel = cSubLabel;
}
} }
@@ -14,16 +14,15 @@ import 'package:cached_network_image/cached_network_image.dart';
class ConversationThreadModel extends FlutterFlowModel<ConversationThreadWidget> { class ConversationThreadModel extends FlutterFlowModel<ConversationThreadWidget> {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
late PromptBoxModel promptBoxModel; late PromptBoxModel promptBoxModel;
late int ?conversationID;
late Conversation conversation; late Conversation conversation;
// If there's a conversation ID, retrieve the conversation history. void updateConversation() {
// Otherwise, start a new conversation with user defaults. conversation = Server.getLoadedConversation();
ConversationThreadModel(this.conversationID); }
@override @override
void initState(BuildContext context) { void initState(BuildContext context) {
conversation = Conversation(conversationID!); conversation = Server.getLoadedConversation();
promptBoxModel = createModel(context, () => PromptBoxModel()); promptBoxModel = createModel(context, () => PromptBoxModel());
} }
@@ -1,4 +1,6 @@
import 'package:app/components/conversation_thread/conversation.dart'; import 'package:app/components/conversation_thread/conversation.dart';
import 'package:app/util/server.dart';
import 'package:provider/provider.dart';
import '/components/conversation_thread/chat_bubble_widget.dart'; import '/components/conversation_thread/chat_bubble_widget.dart';
import 'package:app/components/conversation_thread/conversation_thread_model.dart'; import 'package:app/components/conversation_thread/conversation_thread_model.dart';
@@ -32,7 +34,7 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_model = createModel(context, () => ConversationThreadModel(0)); // TODO _model = createModel(context, () => ConversationThreadModel());
} }
@override @override
@@ -44,132 +46,140 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Conversation c = _model.conversation; return Container(
return Container( width: double.infinity,
width: double.infinity, height: double.infinity,
height: double.infinity, decoration: const BoxDecoration(
decoration: const BoxDecoration( color: AppTheme.primaryBackground,
color: AppTheme.primaryBackground, ),
), child: Column(
child: Column( mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Expanded(
Expanded( child: ListenableBuilder(
child: ListView.builder( listenable: _model.conversation,
itemCount: c.chats.length, builder: (BuildContext context, Widget? child) {
padding: const EdgeInsets.fromLTRB(0, 12, 0, 24,), return ListView.builder(
reverse: false, itemCount: Server.getLoadedConversation().chats.length,
scrollDirection: Axis.vertical, padding: const EdgeInsets.fromLTRB(0, 12, 0, 24,),
itemBuilder: (BuildContext context, int index) { reverse: false,
return ChatBubbleWidget(c: c.chats[index]); scrollDirection: Axis.vertical,
}, itemBuilder: (BuildContext context, int index) {
if(Server.getLoadedConversation().chats.last.role) {
return ChatBubbleWidget(c: Server.getLoadedConversation().chats[index]);
} else {
}
},
);
},
),
), ),
), Container(
Container( width: double.infinity,
width: double.infinity, decoration: const BoxDecoration(
decoration: const BoxDecoration( color: AppTheme.secondaryBackground,
color: AppTheme.secondaryBackground, boxShadow: [
boxShadow: [ BoxShadow(
BoxShadow( blurRadius: 3,
blurRadius: 3, color: Color(0x33000000),
color: Color(0x33000000), offset: Offset(
offset: Offset( 0,
0, -2,
-2, ),
), )
) ],
], ),
), // TODO
// TODO child: Column(
child: Column( mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.max, children: [
children: [ Row(
Row( mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.max, children: [
children: [ Expanded(
Expanded( child: Padding(
child: Padding( padding: const EdgeInsetsDirectional.fromSTEB(0, 12, 0, 0),
padding: const EdgeInsetsDirectional.fromSTEB(0, 12, 0, 0), child: SingleChildScrollView(
child: SingleChildScrollView( scrollDirection: Axis.horizontal,
scrollDirection: Axis.horizontal, child: Row(
child: Row( mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ // FlutterFlowMediaDisplay(
// FlutterFlowMediaDisplay( // path: '',
// path: '', // imageBuilder: (path) => ClipRRect(
// imageBuilder: (path) => ClipRRect( // borderRadius: BorderRadius.circular(8),
// borderRadius: BorderRadius.circular(8), // child: CachedNetworkImage(
// child: CachedNetworkImage( // fadeInDuration: const Duration(milliseconds: 500),
// fadeInDuration: const Duration(milliseconds: 500), // fadeOutDuration:
// fadeOutDuration: // const Duration(milliseconds: 500),
// const Duration(milliseconds: 500), // imageUrl: path,
// imageUrl: path, // width: 120,
// width: 120, // height: 100,
// height: 100, // fit: BoxFit.cover,
// fit: BoxFit.cover, // ),
// ), // ),
// ), // TODO
// TODO // videoPlayerBuilder: (path) =>
// videoPlayerBuilder: (path) => // FlutterFlowVideoPlayer(
// FlutterFlowVideoPlayer( // path: path,
// path: path, // width: 300,
// width: 300, // autoPlay: false,
// autoPlay: false, // looping: true,
// looping: true, // showControls: true,
// showControls: true, // allowFullScreen: true,
// allowFullScreen: true, // allowPlaybackSpeedMenu: false,
// allowPlaybackSpeedMenu: false, // ),
// ), // ),
// ), Align(
Align( alignment: const AlignmentDirectional(-1, -1),
alignment: const AlignmentDirectional(-1, -1), // TODO: Make the delete button conditional upon media upload.
// TODO: Make the delete button conditional upon media upload. child: FlutterFlowIconButton(
child: FlutterFlowIconButton( borderColor:
borderColor: AppTheme.error,
AppTheme.error, borderRadius: 20,
borderRadius: 20, borderWidth: 2,
borderWidth: 2, buttonSize: 40,
buttonSize: 40, fillColor: AppTheme.primaryBackground,
fillColor: AppTheme.primaryBackground, icon: const Icon(
icon: const Icon( Icons.delete_outline_rounded,
Icons.delete_outline_rounded, color: AppTheme.error,
color: AppTheme.error, size: 24,
size: 24, ),
onPressed: () {
print('IconButton pressed ...');
},
), ),
onPressed: () {
print('IconButton pressed ...');
},
), ),
), ]
] .divide(const SizedBox(width: 8))
.divide(const SizedBox(width: 8)) .addToStart(const SizedBox(width: 16))
.addToStart(const SizedBox(width: 16)) .addToEnd(const SizedBox(width: 16)),
.addToEnd(const SizedBox(width: 16)), ),
), ),
), ),
), ),
), ],
], ),
), Form(
Form( key: _model.formKey,
key: _model.formKey, autovalidateMode: AutovalidateMode.disabled,
autovalidateMode: AutovalidateMode.disabled, child: Padding(
child: Padding( padding: const EdgeInsetsDirectional.fromSTEB(0, 0, 0, 8),
padding: const EdgeInsetsDirectional.fromSTEB(0, 0, 0, 8), child: wrapWithModel(
child: wrapWithModel( model: _model.promptBoxModel,
model: _model.promptBoxModel, updateCallback: () => setState(() {}),
updateCallback: () => setState(() {}), child: const PromptBoxWidget(),
child: const PromptBoxWidget(), ),
), ),
), ),
), ],
], ),
), ),
), ],
], ),
), );
);
} }
} }
@@ -2,8 +2,6 @@ import 'package:app/util/server.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';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:app/theme.dart'; import 'package:app/theme.dart';
@@ -221,7 +219,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) {
updateFromLatestInput(text); // TODO: Connect with multimedia // TODO: Clear text box
Server.respondWhenReady(text); // TODO: Connect with multimedia
}, },
), ),
), ),
@@ -233,9 +232,4 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
), ),
); );
} }
void updateFromLatestInput(String input) {
Server().getResponseWhenReady(input);
// TODO: Refresh the page with the prompt & response.
}
} }
+1 -2
View File
@@ -53,13 +53,12 @@ void main() {
class MyApp extends ConsumerWidget { class MyApp extends ConsumerWidget {
MyApp({super.key}); MyApp({super.key});
final selectedIndexProvider = StateProvider<int>((_) => 2); // Initial state final selectedIndexProvider = StateProvider<int>((_) => 1); // Initial state
// This list holds the pages to navigate between // This list holds the pages to navigate between
final List<Widget> _pages = [ final List<Widget> _pages = [
const Center(child: SettingsWidget()), const Center(child: SettingsWidget()),
const Center(child: ConversationsListWidget()), const Center(child: ConversationsListWidget()),
const Center(child: ConversationWidget()),
]; ];
// This widget is the root of your application. // This widget is the root of your application.
+1 -2
View File
@@ -15,8 +15,7 @@ class ConversationModel extends FlutterFlowModel<ConversationWidget> {
@override @override
void initState(BuildContext context) { void initState(BuildContext context) {
conversationThreadModel = conversationThreadModel = createModel(context, () => ConversationThreadModel()); // TODO
createModel(context, () => ConversationThreadModel(0)); // TODO
modelItemModel = createModel(context, () => ModelItemModel()); modelItemModel = createModel(context, () => ModelItemModel());
} }
@@ -19,8 +19,9 @@ class ConversationWidget extends StatefulWidget {
class _ConversationWidgetState extends State<ConversationWidget> { class _ConversationWidgetState extends State<ConversationWidget> {
late ConversationModel _model; late ConversationModel _model;
final scaffoldKey = GlobalKey<ScaffoldState>(); _ConversationWidgetState();
final scaffoldKey = GlobalKey<ScaffoldState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -1,8 +1,6 @@
import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'conversations_list_widget.dart' show ConversationsListWidget; import 'conversations_list_widget.dart' show ConversationsListWidget;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
class ConversationsListModel extends FlutterFlowModel<ConversationsListWidget> { class ConversationsListModel extends FlutterFlowModel<ConversationsListWidget> {
@@ -1,7 +1,9 @@
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/components/conversation_options/conversation_options_widget.dart'; import 'package:app/components/conversation_options/conversation_options_widget.dart';
import 'package:app/util/server.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';
@@ -10,19 +12,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'conversations_list_model.dart'; import 'conversations_list_model.dart';
export 'conversations_list_model.dart'; export 'conversations_list_model.dart';
final conversationIndex = ValueNotifier<int>(0);
// State controller using flutter_riverpod API.
// This handles changing between different conversations.
class MyState with ChangeNotifier {
int get selectedIndex => conversationIndex.value;
set selectedIndex(int value) {
conversationIndex.value = value;
notifyListeners();
}
}
class ConversationsListWidget extends StatefulWidget { class ConversationsListWidget extends StatefulWidget {
const ConversationsListWidget({super.key}); const ConversationsListWidget({super.key});
@@ -35,17 +24,24 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
late ConversationsListModel _model; late ConversationsListModel _model;
final selectedIndexProvider = StateProvider<int>((_) => 0); // Initial state final selectedIndexProvider = StateProvider<int>((_) => 0); // Initial state
final List<Widget> conversations = [ late List<Widget> conversations = [];
// TODO: Dynamically generate list of pages based upon existing conversations. // = [
// This requires retrievable conversations from the server side. // // TODO: Dynamically generate list of pages based upon existing conversations.
const Center(child: ConversationWidget()), // // This requires retrievable conversations from the server side.
]; // const Center(child: ConversationWidget()),
// ];
final scaffoldKey = GlobalKey<ScaffoldState>(); final scaffoldKey = GlobalKey<ScaffoldState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Server().init();
List<Conversation> convos = Server.conversations;
for(Conversation c in convos) {
conversations.add(const ConversationWidget());
}
_model = createModel(context, () => ConversationsListModel()); _model = createModel(context, () => ConversationsListModel());
} }
@@ -57,6 +53,7 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Conversation> conversations = Server.getConversationsList();
return GestureDetector( return GestureDetector(
onTap: () => _model.unfocusNode.canRequestFocus onTap: () => _model.unfocusNode.canRequestFocus
? FocusScope.of(context).requestFocus(_model.unfocusNode) ? FocusScope.of(context).requestFocus(_model.unfocusNode)
@@ -77,12 +74,13 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
Expanded( Expanded(
child: Align( child: Align(
alignment: const AlignmentDirectional(0, 0), alignment: const AlignmentDirectional(0, 0),
child: ListView( child: ListView.builder(
itemCount: conversations.length,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
reverse: true, reverse: true,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
children: [ itemBuilder: (context, index) {
Align( return Align(
alignment: const AlignmentDirectional(0, 0), alignment: const AlignmentDirectional(0, 0),
child: InkWell( child: InkWell(
splashColor: Colors.transparent, splashColor: Colors.transparent,
@@ -101,9 +99,7 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
Container( Container(
width: 100, width: 100,
height: 100, height: 100,
decoration: const BoxDecoration( decoration: const BoxDecoration(color: AppTheme.secondaryBackground,),
color: AppTheme.secondaryBackground,
),
child: const Align( child: const Align(
alignment: AlignmentDirectional(0, 0), alignment: AlignmentDirectional(0, 0),
child: Icon( child: Icon(
@@ -120,28 +116,24 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
width: 100, width: 100,
height: 100, height: 100,
constraints: BoxConstraints( constraints: BoxConstraints(
minWidth: minWidth: MediaQuery.sizeOf(context).width,
MediaQuery.sizeOf(context).width,
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppTheme.secondaryBackground, color: AppTheme.secondaryBackground,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
), ),
child: const Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceEvenly,
MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [ children: [
Align( Align(
alignment: alignment: const AlignmentDirectional(-1, 0),
AlignmentDirectional(-1, 0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Text(
'Hello World', // TODO: Dynamically obtain conversation title from server. conversations[index].conversationLabel,
style: AppTheme.headlineLarge, style: AppTheme.headlineLarge,
), ),
], ],
@@ -149,13 +141,13 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
), ),
Align( Align(
alignment: alignment:
AlignmentDirectional(-1, 0), const AlignmentDirectional(-1, 0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
'Hello World', // TODO: Summary or list of models used conversations[index].conversationSubLabel,
style: AppTheme.bodyMedium, style: AppTheme.bodyMedium,
), ),
), ),
@@ -183,15 +175,23 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
), ),
onTap: () => { onTap: () => {
// TODO: Need to implement. // TODO: Need to implement.
print("opening existing conversation") // Get the conversation object based on the index
// Use Navigator to push a new ConversationWidget with conversation data
Server.loadConversation(index),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConversationWidget(),
),
)
}, },
onLongPress: () => { onLongPress: () => {
// TODO: Need to implement. // TODO: Need to implement.
print("opening options") print("opening options")
}, },
), ),
), );
], }
), ),
), ),
), ),
+102 -65
View File
@@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:app/components/conversation_thread/conversation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:app/components/conversation_thread/chat.dart'; import 'package:app/components/conversation_thread/chat.dart';
@@ -7,12 +8,34 @@ import 'package:app/util/speech_to_text.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 Server { class Server {
// SERVER VARIABLES
static final Server _single_server = Server._internal();
Server._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/";
static String endpoint = "";
static final Map<String, String> headers = {'Content-Type': 'application/json'};
// STT variables
// AI VARIABLES
static final SpeechToText stt = SpeechToText(); static final SpeechToText stt = SpeechToText();
static late bool _speechEnabled; static late bool _speechEnabled;
static final Server _single_server = Server._internal();
// PRIVACY VARIABLES
static bool _historyEnabled = true;
static late int conversationID;
static List<Conversation> conversations = [];
static List<Chat> loadedChats = [];
// RUNTIME & STATUS VARIABLES
static String prompt = "";
static String response = "";
static bool initiated = false;
// TODO: Verify all different components of STT, TTS, LLM, etc have been initialized. // TODO: Verify all different components of STT, TTS, LLM, etc have been initialized.
// TODO: Handshake w/ server to validate identity // TODO: Handshake w/ server to validate identity
@@ -20,41 +43,83 @@ class Server {
// Speech-To-Text engine activation. // Speech-To-Text engine activation.
_speechEnabled = stt.isSpeechEnabled(); _speechEnabled = stt.isSpeechEnabled();
// loadedChats = conversations[conversationID].chats;
return _single_server; return _single_server;
} }
Server._internal(); void init() {
initiated = true;
// TODO: One-time process to establish server connection is required for easy setup/maintenance. // TODO: Dynamically obtain conversations.
// Server variables conversations.add(
final url = "http://10.0.2.2:11434/api/"; Conversation.completeConversation(
String endpoint = ""; 0,
Map<String, String> headers = {'Content-Type': 'application/json'}; "Test label",
"Test sub-label",
[
(Chat("This is just a test. Only reply Yes.", true)),
(Chat("Yes.", false)),
]
)
);
// Status variables conversations.add(
bool _historyEnabled = true; Conversation.completeConversation(
1,
// Context variables "Label test",
List<Chat> chats = [ "Sub-label Test",
(Chat("This is just a test. Only reply Yes.", true)), [
(Chat("Yes.", false)), (Chat("A different test. Only reply No.", true)),
]; (Chat("No.", false)),
]
// Runtime variables )
String prompt = ""; );
String response = "";
int conversationID = 0;
List<Chat> getConversationByID(conversationID) {
// TODO: Implement conversation IDs & retrieval from server
this.conversationID = conversationID;
return chats;
} }
// TODO: Integrate with getConversationByID()
List<Map<String, String>> convertMessages() {
// PRIMARY INTERFACE
static Future<String> 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.";
prompt = stt.getTextWhenReady() as String;
conversations[conversationID].add(Chat(prompt, true));
httpSendRequest();
conversations[conversationID].add(Chat(response, false));
return response;
}
static void respondWhenReady(String p) async {
prompt = p;
conversations[conversationID].add(Chat(prompt, true));
httpSendRequest();
}
// CONVERSATION MANAGEMENT
static List<Conversation> getConversationsList() { return conversations; }
static Conversation getLoadedConversation() { return conversations[conversationID]; }
// Load conversation should be called prior to any other server requests.
static void loadConversation(id) {
conversationID = id;
loadedChats = conversations[conversationID].chats;
}
static List<Chat> getChatsByConversationID(id) { return conversations[id].chats; }
// SERVER UTILS
static List<Map<String, String>> convertMessages() {
// TODO: Integrate with getConversationByID()
List<Map<String, String>> messageList = []; List<Map<String, String>> messageList = [];
for (Chat c in chats) { for (Chat c in loadedChats) {
messageList.add({ messageList.add({
"role": c.role ? "user" : "assistant", "role": c.role ? "user" : "assistant",
"content": c.content, "content": c.content,
@@ -64,41 +129,15 @@ class Server {
return messageList; return messageList;
} }
// Using native on-device Speech-To-Text capability, get the server response when ready.
Future<String> 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.";
prompt = stt.getTextWhenReady() as String;
httpSendRequest();
return response;
}
Future<String> getResponseWhenReady(String p) async {
prompt = p;
httpSendRequest();
return response;
}
// Get the last response or request a response from the server.
String getResponse() {
httpSendRequest();
return response;
}
// Get the latest prompt.
String getPrompt() => prompt;
// Formats the prompt in JSON. // Formats the prompt in JSON.
String constructPrompt() { static String constructPrompt() {
// 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 toggleable states between stream
// 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(_historyEnabled) {
chats = [...chats, Chat(prompt, true)]; loadedChats = [...loadedChats, Chat(prompt, true)];
endpoint = "chat"; endpoint = "chat";
List<Map<String, String>> messageList = convertMessages(); List<Map<String, String>> messageList = convertMessages();
@@ -123,9 +162,8 @@ class Server {
} }
// Send the HTTP request to the server. // Send the HTTP request to the server.
void httpSendRequest() async { static void httpSendRequest() async {
String json = constructPrompt(); String json = constructPrompt();
print("\n\nJSON:\n$json\n\n");
// Send the HTTP POST request // Send the HTTP POST request
final serverResponse = await http.post( final serverResponse = await http.post(
@@ -138,15 +176,14 @@ class Server {
Map<String, dynamic> re = jsonDecode(serverResponse.body); Map<String, dynamic> re = jsonDecode(serverResponse.body);
if(_historyEnabled) { if(_historyEnabled) {
response = re["message"]["content"]; response = re["message"]["content"];
chats = [...chats, Chat(response, false)]; loadedChats = [...loadedChats, Chat(response, false)];
} else { } else {
response = re["response"]; response = re["response"];
} }
} }
print("\n\nresponse:\n$response"); conversations[conversationID].add(Chat(response, false));
print("\nRaw chats variable:\n");
for(Chat c in chats) { prompt = "";
print(c.content); response = "";
}
} }
} }