enabled togglable chat history (during runtime) that uses different api endpoints.
This commit is contained in:
@@ -1,9 +1,22 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
class Chat {
|
class Chat {
|
||||||
late String message;
|
final String content;
|
||||||
late bool isMe;
|
final bool role;
|
||||||
|
|
||||||
// Most basic chat, text from server or from user.
|
// Most basic chat, text from server or from user.
|
||||||
Chat(this.message, this.isMe);
|
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.
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ class ChatBubbleWidget extends StatelessWidget{
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: c.isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
mainAxisAlignment: c.role ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -18,14 +18,14 @@ class ChatBubbleWidget extends StatelessWidget{
|
|||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: const Radius.circular(12.0),
|
topLeft: const Radius.circular(12.0),
|
||||||
topRight: 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),
|
.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),
|
.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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@@ -58,8 +59,9 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
// TODO: Implement the context upload buttons
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded( // TODO: File context upload
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: FlutterFlowIconButton(
|
child: FlutterFlowIconButton(
|
||||||
@@ -78,7 +80,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded( // TODO: Photo context upload
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: FlutterFlowIconButton(
|
child: FlutterFlowIconButton(
|
||||||
@@ -97,7 +99,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded( // TODO: Microphone context upload -> or STT??
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: FlutterFlowIconButton(
|
child: FlutterFlowIconButton(
|
||||||
@@ -116,7 +118,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded( // TODO: Video context upload
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: FlutterFlowIconButton(
|
child: FlutterFlowIconButton(
|
||||||
@@ -135,7 +137,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded( // TODO: Camera context upload
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: FlutterFlowIconButton(
|
child: FlutterFlowIconButton(
|
||||||
@@ -173,8 +175,6 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
textCapitalization: TextCapitalization.none,
|
textCapitalization: TextCapitalization.none,
|
||||||
textInputAction: TextInputAction.go,
|
|
||||||
obscureText: false,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: false,
|
isDense: false,
|
||||||
counterStyle: AppTheme.bodyMedium,
|
counterStyle: AppTheme.bodyMedium,
|
||||||
@@ -218,8 +218,11 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
|
|||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
validator:
|
validator: _model.textControllerValidator.asValidator(context),
|
||||||
_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<PromptBoxWidget> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateFromLatestInput(String input) {
|
||||||
|
Server s = Server();
|
||||||
|
s.setPrompt(input);
|
||||||
|
print(s.getResponse());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ class Server {
|
|||||||
|
|
||||||
// Status variables
|
// Status variables
|
||||||
late bool _speechEnabled;
|
late bool _speechEnabled;
|
||||||
|
bool _historyEnabled = true;
|
||||||
|
|
||||||
|
// Context variables
|
||||||
|
List<Chat> chats = [
|
||||||
|
(Chat("This is just a test. Only reply Yes.", true)),
|
||||||
|
(Chat("Yes.", false)),
|
||||||
|
];
|
||||||
|
|
||||||
// Runtime variables
|
// Runtime variables
|
||||||
String prompt = "";
|
String prompt = "";
|
||||||
@@ -31,39 +38,47 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Chat> getConversationByID(conversationID) {
|
List<Chat> getConversationByID(conversationID) {
|
||||||
this.conversationID = conversationID;
|
|
||||||
|
|
||||||
// TODO: Implement conversation IDs & retrieval from server
|
// TODO: Implement conversation IDs & retrieval from server
|
||||||
List<Chat> chats = [
|
this.conversationID = conversationID;
|
||||||
(Chat("hello world!", true)),
|
|
||||||
(Chat("hello to you too!", false)),
|
|
||||||
];
|
|
||||||
|
|
||||||
return chats;
|
return chats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Integrate with getConversationByID()
|
||||||
|
List<Map<String, String>> convertMessages() {
|
||||||
|
List<Map<String, String>> 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.
|
// Using native on-device Speech-To-Text capability, get the server response when ready.
|
||||||
Future<String> sttGetResponseWhenReady() async {
|
Future<String> sttGetResponseWhenReady() async {
|
||||||
// TODO: In case on-device STT is unavailable, use server-side STT service.
|
// TODO: In case on-device STT is unavailable, use server-side STT service.
|
||||||
if (!_speechEnabled) "Unable to process Speech-To-Text on-device.";
|
if (!_speechEnabled) "Unable to process Speech-To-Text on-device.";
|
||||||
|
|
||||||
setPrompt(stt.getTextWhenReady() as String);
|
setPrompt(stt.getTextWhenReady() as String);
|
||||||
constructPrompt();
|
|
||||||
httpSendRequest();
|
httpSendRequest();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getResponseWhenReady(String p) async {
|
||||||
|
prompt = p;
|
||||||
|
httpSendRequest();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
// Arbitrary prompt, used for standard text interaction rather than STT.
|
// Arbitrary prompt, used for standard text interaction rather than STT.
|
||||||
void setPrompt(String p) => prompt = p;
|
void setPrompt(String p) => prompt = p;
|
||||||
|
|
||||||
// Get the last response or request a response from the server.
|
// Get the last response or request a response from the server.
|
||||||
String getResponse() {
|
String getResponse() {
|
||||||
if(response.isEmpty) {
|
|
||||||
constructPrompt();
|
|
||||||
httpSendRequest();
|
httpSendRequest();
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,28 +87,46 @@ class Server {
|
|||||||
|
|
||||||
// Formats the prompt in JSON.
|
// Formats the prompt in JSON.
|
||||||
String constructPrompt() {
|
String constructPrompt() {
|
||||||
// Construct the JSON payload
|
|
||||||
final Map<String, dynamic> 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<String, dynamic> data;
|
||||||
|
|
||||||
|
if(_historyEnabled) {
|
||||||
|
chats = [...chats, Chat(prompt, true)];
|
||||||
|
|
||||||
|
List<Map<String, String>> messageList = convertMessages();
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": "llama3",
|
||||||
|
"messages": messageList,
|
||||||
|
"stream": false,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data = {
|
||||||
|
"model": "llama3",
|
||||||
|
"prompt": prompt,
|
||||||
|
"stream": false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Encode the JSON payload
|
// Encode the JSON payload
|
||||||
final String body = json.encode(data);
|
return json.encode(data);
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the HTTP request to the server.
|
// Send the HTTP request to the server.
|
||||||
void httpSendRequest() async {
|
void httpSendRequest() async {
|
||||||
String json = constructPrompt();
|
String json = constructPrompt();
|
||||||
|
print("\n\n$json\n\n");
|
||||||
|
|
||||||
|
String endpoint = "/api/";
|
||||||
|
if(_historyEnabled) endpoint += "chat";
|
||||||
|
else endpoint += "generate";
|
||||||
|
|
||||||
// Send the HTTP POST request
|
// Send the HTTP POST request
|
||||||
final serverResponse = await http.post(
|
final serverResponse = await http.post(
|
||||||
Uri.parse('$url/api/generate'),
|
Uri.parse('$url$endpoint'),
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: json,
|
body: json,
|
||||||
);
|
);
|
||||||
@@ -104,46 +137,6 @@ class Server {
|
|||||||
} else {
|
} else {
|
||||||
response = ('Error: ${serverResponse.statusCode}');
|
response = ('Error: ${serverResponse.statusCode}');
|
||||||
}
|
}
|
||||||
|
chats.add(Chat(response, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
//
|
|
||||||
// // GUI components
|
|
||||||
// child: Column(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
// children: <Widget>[
|
|
||||||
// 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),
|
|
||||||
// ),
|
|
||||||
|
|||||||
Reference in New Issue
Block a user