added send functionality to icon.

This commit is contained in:
Joshua
2024-07-10 13:44:16 -06:00
parent 097b99e0e6
commit 711991c7bf
13 changed files with 363 additions and 281 deletions
@@ -1,3 +1,4 @@
import 'package:app/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'chat.dart'; import 'chat.dart';
@@ -26,9 +27,12 @@ class ChatBubbleWidget extends StatelessWidget{
bottomRight: c.role ? 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.role ? Colors.blue[200] : Colors.grey[200], color: c.role ? AppTheme.darkest : AppTheme.dark,
),
child: Text(
c.content,
style: AppTheme.bodyMedium,
), ),
child: Text(c.content),
), ),
], ],
); );
@@ -18,7 +18,7 @@ class Conversation with ChangeNotifier {
} }
void loadChats() { void loadChats() {
chats = Server.getChatsByConversationID(conversationID); chats = Backend.getChatsByConversationID(conversationID);
} }
void setLabel() { void setLabel() {
@@ -20,7 +20,7 @@ class ConversationThreadModel extends FlutterFlowModel<ConversationThreadWidget>
@override @override
void initState(BuildContext context) { void initState(BuildContext context) {
conversation = Server.getLoadedConversation(); conversation = Backend.getLoadedConversation();
promptBoxModel = createModel(context, () => PromptBoxModel()); promptBoxModel = createModel(context, () => PromptBoxModel());
} }
@@ -53,9 +53,7 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
return Container( return Container(
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
decoration: const BoxDecoration(
color: AppTheme.primaryBackground,
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -65,12 +63,12 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
listenable: _model.conversation, listenable: _model.conversation,
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
return ListView.builder( return ListView.builder(
itemCount: Server.getLoadedConversation().chats.length, itemCount: Backend.getLoadedConversation().chats.length,
padding: const EdgeInsets.fromLTRB(0, 12, 0, 24,), padding: const EdgeInsets.fromLTRB(0, 12, 0, 24,),
reverse: false, reverse: false,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ChatBubbleWidget(c: Server.getLoadedConversation().chats[index]); return ChatBubbleWidget(c: Backend.getLoadedConversation().chats[index]);
}, },
); );
}, },
@@ -79,7 +77,7 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
Container( Container(
width: double.infinity, width: double.infinity,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppTheme.secondaryBackground, color: AppTheme.medium,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 3, blurRadius: 3,
@@ -95,74 +93,74 @@ class _ConversationThreadWidgetState extends State<ConversationThreadWidget> {
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: () { // onPressed: () {
print('IconButton pressed ...'); // 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,
@@ -1,3 +1,4 @@
import 'package:app/components/conversation_thread/conversation.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:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
@@ -9,14 +10,18 @@ import 'model_item_model.dart';
export 'model_item_model.dart'; export 'model_item_model.dart';
class ModelItemWidget extends StatefulWidget { class ModelItemWidget extends StatefulWidget {
const ModelItemWidget({super.key}); final Conversation conversation;
const ModelItemWidget({super.key, required this.conversation});
@override @override
State<ModelItemWidget> createState() => _ModelItemWidgetState(); State<ModelItemWidget> createState() => _ModelItemWidgetState(conversation);
} }
class _ModelItemWidgetState extends State<ModelItemWidget> { class _ModelItemWidgetState extends State<ModelItemWidget> {
late ModelItemModel _model; late ModelItemModel _model;
late Conversation conversation;
_ModelItemWidgetState(this.conversation);
@override @override
void setState(VoidCallback callback) { void setState(VoidCallback callback) {
@@ -78,22 +83,22 @@ class _ModelItemWidgetState extends State<ModelItemWidget> {
), ),
), ),
), ),
const Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(12, 0, 8, 0), padding: const EdgeInsetsDirectional.fromSTEB(12, 0, 8, 0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text( // Label
'Randy Peterson', conversation.conversationLabel,
style: AppTheme.bodyMedium, style: AppTheme.bodyMedium,
), ),
Padding( Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 4, 0, 0), padding: const EdgeInsetsDirectional.fromSTEB(0, 4, 0, 0),
child: Text( child: Text( // Sub-label
'name@domainname.com', conversation.conversationSubLabel,
style: AppTheme.bodySmall, style: AppTheme.bodySmall,
), ),
), ),
@@ -17,6 +17,7 @@ class PromptBoxWidget extends StatefulWidget {
class _PromptBoxWidgetState extends State<PromptBoxWidget> { class _PromptBoxWidgetState extends State<PromptBoxWidget> {
late PromptBoxModel _model; late PromptBoxModel _model;
late String entry_text;
@override @override
void setState(VoidCallback callback) { void setState(VoidCallback callback) {
@@ -69,7 +70,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
buttonSize: 40, buttonSize: 40,
icon: const Icon( icon: const Icon(
Icons.file_upload_outlined, Icons.file_upload_outlined,
color: AppTheme.secondaryText, color: AppTheme.darkest,
size: 30, size: 30,
), ),
onPressed: () { onPressed: () {
@@ -88,7 +89,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
buttonSize: 40, buttonSize: 40,
icon: const Icon( icon: const Icon(
Icons.photo_outlined, Icons.photo_outlined,
color: AppTheme.secondaryText, color: AppTheme.darkest,
size: 30, size: 30,
), ),
onPressed: () { onPressed: () {
@@ -105,13 +106,28 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
borderRadius: 30, borderRadius: 30,
borderWidth: 1, borderWidth: 1,
buttonSize: 40, buttonSize: 40,
icon: const Icon( icon: // Backend.stt.speechToText.isListening ?
Icons.mic_none, // const Icon(
color: AppTheme.secondaryText, // Icons.mic_none,
// color: AppTheme.error,
// size: 30,
// ) :
const Icon(
Icons.mic_off_outlined,
color: AppTheme.darkest,
size: 30, size: 30,
), ),
onPressed: () { onPressed: () {
print('IconButton pressed ...'); if(Backend.stt.isSpeechEnabled()) {
if(Backend.stt.speechToText.isListening) {
print('De-activating STT.');
Backend.stt.stopListening();
}
print('Activating STT.');
entry_text = Backend.stt.startListening() as String;
} else {
print('Microphone is unavailable for STT');
}
}, },
), ),
), ),
@@ -126,7 +142,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
buttonSize: 40, buttonSize: 40,
icon: const Icon( icon: const Icon(
Icons.videocam_outlined, Icons.videocam_outlined,
color: AppTheme.secondaryText, color: AppTheme.darkest,
size: 30, size: 30,
), ),
onPressed: () { onPressed: () {
@@ -145,7 +161,7 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
buttonSize: 40, buttonSize: 40,
icon: const Icon( icon: const Icon(
Icons.camera_alt_outlined, Icons.camera_alt_outlined,
color: AppTheme.secondaryText, color: AppTheme.darkest,
size: 30, size: 30,
), ),
onPressed: () { onPressed: () {
@@ -206,21 +222,28 @@ class _PromptBoxWidgetState extends State<PromptBoxWidget> {
), ),
filled: true, filled: true,
fillColor: AppTheme.alternate, fillColor: AppTheme.alternate,
suffixIcon: const Icon( suffixIcon: GestureDetector(
Icons.send, onTap: () {
color: AppTheme.primary, // TODO: Connect with multimedia
size: 30, entry_text = _model.textController.text;
Backend.respondWhenReady(entry_text);
_model.textController?.clear(); // Clear the text field
},
child: const Icon(
Icons.send,
color: AppTheme.primary,
size: 30,
),
), ),
), ),
style: AppTheme.bodyMedium, style: AppTheme.displayMedium,
textAlign: TextAlign.start, textAlign: TextAlign.start,
maxLines: 5, maxLines: 5,
minLines: 1, minLines: 1,
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) {
// TODO: Clear text box Backend.respondWhenReady(text); // TODO: Connect with multimedia
Server.respondWhenReady(text); // TODO: Connect with multimedia
_model.textController?.clear(); // Clear the text field _model.textController?.clear(); // Clear the text field
}, },
), ),
+9 -6
View File
@@ -28,13 +28,14 @@ BottomNavigationBarItem _buildNavigationItem(int index) {
iconData = Icons.settings; iconData = Icons.settings;
break; break;
case 1: case 1:
label = 'Home'; label = 'Chats';
iconData = Icons.home; iconData = Icons.chat;
break; break;
} }
return BottomNavigationBarItem( return BottomNavigationBarItem(
icon: Icon(iconData), icon: Icon(iconData),
label: label, label: label,
backgroundColor: AppTheme.lightest,
); );
} }
@@ -63,15 +64,15 @@ class MyApp extends ConsumerWidget {
final selectedIndex = ref.watch(selectedIndexProvider); final selectedIndex = ref.watch(selectedIndexProvider);
return MaterialApp( return MaterialApp(
title: 'App', title: 'One AI',
home: Scaffold( home: Scaffold(
backgroundColor: AppTheme.secondaryBackground, backgroundColor: AppTheme.medium,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppTheme.secondaryBackground, backgroundColor: AppTheme.secondaryBackground,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: const Text( title: const Text(
'App', 'One AI',
style: AppTheme.headlineLarge, style: AppTheme.displayLarge,
), ),
actions: const [], actions: const [],
centerTitle: false, centerTitle: false,
@@ -79,6 +80,8 @@ class MyApp extends ConsumerWidget {
), ),
body: _pages[selectedIndex], body: _pages[selectedIndex],
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
backgroundColor: AppTheme.darkest,
selectedItemColor: AppTheme.lightest,
// Set the number of items to the length of the pages list // Set the number of items to the length of the pages list
items: List.generate(_pages.length, (index) => _buildNavigationItem(index)), items: List.generate(_pages.length, (index) => _buildNavigationItem(index)),
currentIndex: selectedIndex, currentIndex: selectedIndex,
@@ -59,7 +59,7 @@ class _ConversationWidgetState extends State<ConversationWidget> {
buttonSize: 60, buttonSize: 60,
icon: const Icon( icon: const Icon(
Icons.arrow_back_rounded, Icons.arrow_back_rounded,
color: AppTheme.primaryTextColor, color: AppTheme.lightest,
size: 30, size: 30,
), ),
onPressed: () async { onPressed: () async {
@@ -80,21 +80,16 @@ class _ConversationWidgetState extends State<ConversationWidget> {
title: wrapWithModel( title: wrapWithModel(
model: _model.modelItemModel, model: _model.modelItemModel,
updateCallback: () => setState(() {}), updateCallback: () => setState(() {}),
child: const ModelItemWidget(), child: ModelItemWidget(conversation: conversation,),
), ),
actions: [ actions: [
Padding( Padding(
padding: const EdgeInsetsDirectional.fromSTEB(0, 8, 16, 8), padding: const EdgeInsetsDirectional.fromSTEB(0, 8, 16, 8),
// ---------- Options Button // ---------- Options Button
child: FlutterFlowIconButton( child: FlutterFlowIconButton(
borderColor: AppTheme.alternate,
borderRadius: 12,
borderWidth: 2,
buttonSize: 40,
fillColor: AppTheme.primaryBackground,
icon: const Icon( icon: const Icon(
Icons.more_vert, Icons.more_vert,
color: AppTheme.primaryTextColor, color: AppTheme.lightest,
size: 24, size: 24,
), ),
onPressed: () async { onPressed: () async {
@@ -6,7 +6,6 @@ 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';
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';
@@ -29,10 +28,10 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Server().init(); Backend().init();
if(conversations.isEmpty) { if(conversations.isEmpty) {
List<Conversation> convoys = Server.conversations; List<Conversation> convoys = Backend.conversations;
for(Conversation c in convoys) { for(Conversation c in convoys) {
conversations.add(ConversationWidget(conversation: c,)); conversations.add(ConversationWidget(conversation: c,));
} }
@@ -55,138 +54,162 @@ class _ConversationsListWidgetState extends State<ConversationsListWidget> {
: FocusScope.of(context).unfocus(), : FocusScope.of(context).unfocus(),
child: SafeArea( child: SafeArea(
top: true, top: true,
child: Column( child: Stack(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Padding( Column(
padding: EdgeInsetsDirectional.fromSTEB(16, 0, 0, 0), mainAxisSize: MainAxisSize.max,
child: Text( crossAxisAlignment: CrossAxisAlignment.start,
'Conversations', children: [
style: AppTheme.labelMedium, Expanded(
), child: Align(
), alignment: const AlignmentDirectional(0, 0),
Expanded( child: ListView.builder(
child: Align( itemCount: conversations.length,
alignment: const AlignmentDirectional(0, 0), padding: EdgeInsets.zero,
child: ListView.builder( reverse: true,
itemCount: conversations.length, scrollDirection: Axis.vertical,
padding: EdgeInsets.zero, itemBuilder: (context, index) {
reverse: true, return Align(
scrollDirection: Axis.vertical, alignment: const AlignmentDirectional(0, 0),
itemBuilder: (context, index) { child: InkWell(
return Align( splashColor: AppTheme.accent,
alignment: const AlignmentDirectional(0, 0), focusColor: AppTheme.lightest,
child: InkWell( hoverColor: AppTheme.dark,
splashColor: Colors.transparent, highlightColor: AppTheme.darkest,
focusColor: Colors.transparent, // TODO
hoverColor: Colors.transparent, // onTap: () async {
highlightColor: Colors.transparent, // context.pushNamed('Conversation');
// TODO // },
// onTap: () async { child: Container(
// context.pushNamed('Conversation'); decoration: const BoxDecoration(
// }, //color: AppTheme.darkest,
child: Row( border: Border(
mainAxisSize: MainAxisSize.max, top: BorderSide(width: 2.0, color: AppTheme.darkest),
mainAxisAlignment: MainAxisAlignment.start, ),
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: const BoxDecoration(color: AppTheme.secondaryBackground,),
child: const Align(
alignment: AlignmentDirectional(0, 0),
child: Icon(
Icons.settings_outlined, // TODO: Dynamically generate or allow users to color code/tag conversations.
color: AppTheme.secondaryText,
size: 48,
), ),
), child: Row(
), mainAxisSize: MainAxisSize.max,
Expanded( mainAxisAlignment: MainAxisAlignment.start,
child: Align( crossAxisAlignment: CrossAxisAlignment.center,
alignment: const AlignmentDirectional(-1, 0),
child: Container( children: [
width: 100, Container(
height: 100, width: 100,
constraints: BoxConstraints( height: 100,
minWidth: MediaQuery.sizeOf(context).width, decoration: const BoxDecoration(color: AppTheme.lightest,),
), child: const Align(
decoration: const BoxDecoration( alignment: AlignmentDirectional(0, 0),
color: AppTheme.secondaryBackground, child: Icon(
shape: BoxShape.rectangle, Icons.settings_outlined, // TODO: Dynamically generate or allow users to color code/tag conversations.
), color: AppTheme.secondaryText,
child: Column( size: 48,
mainAxisSize: MainAxisSize.max, ),
mainAxisAlignment: MainAxisAlignment.spaceEvenly, ),
crossAxisAlignment: CrossAxisAlignment.center, ),
children: [ Expanded(
Align( child: Align(
alignment: const AlignmentDirectional(-1, 0), alignment: const AlignmentDirectional(-1, 0),
child: Row( child: Container(
mainAxisSize: MainAxisSize.max, width: 100,
children: [ height: 100,
Text( constraints: BoxConstraints(
conversations[index].conversation.conversationLabel, minWidth: MediaQuery.sizeOf(context).width,
style: AppTheme.headlineLarge, ),
), decoration: const BoxDecoration(
], color: AppTheme.lightest,
), shape: BoxShape.rectangle,
), ),
Align( child: Column(
alignment: mainAxisSize: MainAxisSize.max,
const AlignmentDirectional(-1, 0), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
child: Row( crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max, children: [
children: [ Align(
Expanded( alignment: const AlignmentDirectional(-1, 0),
child: Text( child: Row(
conversations[index].conversation.conversationSubLabel, mainAxisSize: MainAxisSize.max,
style: AppTheme.bodyMedium, children: [
Text(
conversations[index].conversation.conversationLabel,
style: AppTheme.labelLarge,
),
],
),
), ),
), Align(
], alignment:
const AlignmentDirectional(-1, 0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
conversations[index].conversation.conversationSubLabel,
style: AppTheme.labelSmall,
),
),
],
),
),
],
),
), ),
), ),
], ),
), Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: AppTheme.lightest,
),
child: const Icon(
Icons.chevron_right_rounded,
color: AppTheme.secondaryText,
size: 48,
),
),
],
), ),
), ),
onTap: () => {
Backend.loadConversation(index),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationWidget(conversation: Backend.getLoadedConversation(),),
),
)
},
onLongPress: () => {
// TODO: Need to implement.
print("opening options")
},
), ),
Container( );
width: 100, }
height: 100, ),
decoration: const BoxDecoration( ),
color: AppTheme.secondaryBackground, ),
), ],
child: const Icon( ),
Icons.chevron_right_rounded, Positioned(
color: AppTheme.secondaryText, bottom: 20.0, // Adjust positioning as needed
size: 48, right: 20.0, // Adjust positioning as needed
), child: FloatingActionButton(
), onPressed: () => {
], Backend.loadConversation(Backend.maxConversationID), // TODO: Dynamically get the next ID
), conversations.add(ConversationWidget(conversation: Backend.getLoadedConversation())),
onTap: () => { Navigator.push(
// TODO: Need to implement. context,
// Get the conversation object based on the index MaterialPageRoute(
// Use Navigator to push a new ConversationWidget with conversation data builder: (context) => ConversationWidget(conversation: Backend.getLoadedConversation(),),
Server.loadConversation(index), ),
Navigator.push( ), // Handle the button press
context, },
MaterialPageRoute( backgroundColor: AppTheme.darkest,
builder: (context) => ConversationWidget(conversation: Server.getLoadedConversation(),), child: const Icon(
), Icons.bubble_chart,
) color: AppTheme.lightest,
},
onLongPress: () => {
// TODO: Need to implement.
print("opening options")
},
),
);
}
), ),
), ),
), ),
+30 -22
View File
@@ -1,37 +1,45 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppTheme { class AppTheme {
static const primary = Colors.deepPurple; static const darkest = Color.fromARGB(255, 45, 45, 145);
static const secondary = Colors.deepPurpleAccent; static const dark = Color.fromARGB(255, 81, 81, 196);
static const tertiary = Colors.white; static const medium = Color.fromARGB(255, 107, 107, 201);
static const alternate = Colors.deepPurple; static const light = Color.fromARGB(255, 155, 155, 255);
static const lightest = Color.fromARGB(255, 172, 175, 255);
static const accent = Color.fromARGB(255, 255, 113, 0);
static const accent2 = Color.fromARGB(255, 255, 164, 79);
static const accent1 = Colors.amber; static const primaryBackground = darkest;
static const accent4 = Colors.amber; static const secondaryBackground = medium;
static const primaryTextColor = darkest;
static const primaryText = TextStyle(color: primaryTextColor, fontSize: 16);
static const secondaryText = dark;
static const primaryBackground = Colors.deepPurpleAccent; static const accent1 = lightest;
static const secondaryBackground = Colors.deepPurple; static const accent4 = accent2;
static const primary = dark;
static const secondary = medium;
static const tertiary = light;
static const alternate = lightest;
static const primaryTextColor = Colors.white; static const bodyLarge = TextStyle(color: light, fontSize: 24, fontWeight: FontWeight.bold);
static const primaryText = TextStyle(color: primaryTextColor, fontSize: 12); static const bodyMedium = TextStyle(color: lightest, fontSize: 16);
static const secondaryText = Colors.white; static const bodySmall = TextStyle(color: light, fontSize: 12);
static const bodyLarge = TextStyle(color: Colors.white, fontSize: 24);
static const bodyMedium = TextStyle(color: Colors.white, fontSize: 12);
static const bodySmall = TextStyle(color: Colors.white, fontSize: 10);
static const error = Colors.red; static const error = Colors.red;
static const info = Colors.grey; static const info = Colors.grey;
static const displaySmall = TextStyle(color: Colors.white, fontSize: 12); static const displayLarge = TextStyle(color: darkest, fontSize: 24, fontWeight: FontWeight.bold);
static const displayMedium = TextStyle(color: dark, fontSize: 16, fontWeight: FontWeight.bold);
static const displaySmall = TextStyle(color: lightest, fontSize: 12, fontWeight: FontWeight.bold);
static const labelLarge = TextStyle(color: Colors.black, fontSize: 24); static const labelLarge = TextStyle(color: darkest, fontSize: 24, fontWeight: FontWeight.bold);
static const labelMedium = TextStyle(color: Colors.grey, fontSize: 16); static const labelMedium = TextStyle(color: dark, fontSize: 16, fontWeight: FontWeight.bold);
static const labelSmall = TextStyle(color: Colors.grey, fontSize: 12); static const labelSmall = TextStyle(color: medium, fontSize: 12, fontWeight: FontWeight.bold);
static const headlineLarge = TextStyle(color: Colors.blue, fontWeight: FontWeight.bold); static const headlineLarge = TextStyle(color: medium, fontWeight: FontWeight.bold);
static const headlineMedium = TextStyle(color: Colors.blue, fontWeight: FontWeight.bold); static const headlineMedium = TextStyle(color: light, fontWeight: FontWeight.bold);
static const headlineSmall = TextStyle(color: Colors.blue, fontWeight: FontWeight.bold); static const headlineSmall = TextStyle(color: lightest, fontWeight: FontWeight.bold);
static const titleLarge = TextStyle(fontFamily: 'Outfit', letterSpacing: 0, fontSize: 24); static const titleLarge = TextStyle(fontFamily: 'Outfit', letterSpacing: 0, fontSize: 24);
static const titleSmall = TextStyle(fontFamily: 'Outfit', letterSpacing: 0, fontSize: 16); static const titleSmall = TextStyle(fontFamily: 'Outfit', letterSpacing: 0, fontSize: 16);
+5
View File
@@ -0,0 +1,5 @@
class Model {
late String name;
Model(this.name);
}
+26 -8
View File
@@ -6,11 +6,13 @@ import 'package:app/components/conversation_thread/chat.dart';
import 'package:app/util/speech_to_text.dart'; import 'package:app/util/speech_to_text.dart';
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 Server { class Backend {
// SERVER VARIABLES // SERVER VARIABLES
static final Server _single_server = Server._internal(); static final Backend _single_backend = Backend._internal();
Server._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 final 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
@@ -28,30 +30,36 @@ class Server {
// PRIVACY VARIABLES // PRIVACY VARIABLES
static bool _historyEnabled = true; static bool _historyEnabled = true;
static late int conversationID; static late int conversationID;
static late int maxConversationID;
static List<Conversation> conversations = []; static List<Conversation> conversations = [];
static List<Chat> loadedChats = []; static List<Chat> loadedChats = [];
// RUNTIME & STATUS VARIABLES // RUNTIME & STATUS VARIABLES
static late Model model;
static String prompt = ""; static String prompt = "";
static String response = ""; static String response = "";
static bool initiated = false; 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
factory Server() { factory Backend() {
// Speech-To-Text engine activation. // Speech-To-Text engine activation.
_speechEnabled = stt.isSpeechEnabled(); _speechEnabled = stt.isSpeechEnabled();
// loadedChats = conversations[conversationID].chats; // loadedChats = conversations[conversationID].chats;
return _single_server; return _single_backend;
} }
void init() { void init() {
initiated = true; initiated = true;
// TODO: Dynamically get available models and models that can be pulled.
// TODO: Dynamically get the user's default model
model = Model("llama3"); // Primarily used llama3, testing gemma2
// TODO: Dynamically obtain conversations. // TODO: Dynamically obtain conversations.
conversations.add( conversations.add(
Conversation.completeConversation( Conversation.completeConversation(
@@ -65,6 +73,8 @@ class Server {
) )
); );
maxConversationID = conversations.length;
// conversations.add( // conversations.add(
// Conversation.completeConversation( // Conversation.completeConversation(
// 1, // 1,
@@ -81,9 +91,12 @@ class Server {
// PRIMARY INTERFACE // PRIMARY INTERFACE
@Deprecated("Use STT interface directly and use respondWhenReady().")
static Future<String> sttGetResponseWhenReady() async { static 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) {
return "Unable to process Speech-To-Text on-device.";
}
prompt = stt.getTextWhenReady() as String; prompt = stt.getTextWhenReady() as String;
conversations[conversationID].add(Chat(prompt, true)); conversations[conversationID].add(Chat(prompt, true));
@@ -109,6 +122,11 @@ class Server {
// 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"));
maxConversationID++;
}
loadedChats = conversations[conversationID].chats; loadedChats = conversations[conversationID].chats;
} }
@@ -144,7 +162,7 @@ class Server {
List<Map<String, String>> messageList = convertMessages(); List<Map<String, String>> messageList = convertMessages();
data = { data = {
"model": "llama3", "model": model.name,
"messages": messageList, "messages": messageList,
"stream": false, "stream": false,
}; };
@@ -152,7 +170,7 @@ class Server {
else { else {
endpoint = "generate"; endpoint = "generate";
data = { data = {
"model": "llama3", "model": model.name,
"prompt": prompt, "prompt": prompt,
"stream": false, "stream": false,
}; };
+9 -9
View File
@@ -3,15 +3,15 @@ import 'package:speech_to_text/speech_to_text.dart' as stt;
/// Handles standard Speech-To-Text using on-device processing. /// Handles standard Speech-To-Text using on-device processing.
class SpeechToText { class SpeechToText {
final stt.SpeechToText _speechToText = stt.SpeechToText(); final stt.SpeechToText speechToText = stt.SpeechToText();
bool _speechEnabled = false; bool speechEnabled = false;
String text = ""; String text = "";
// Check if speech recognition is enabled. // Check if speech recognition is enabled.
bool isSpeechEnabled() => _speechEnabled; bool isSpeechEnabled() => speechEnabled;
Future<String> getTextWhenReady() async { Future<String> getTextWhenReady() async {
await _speechToText.stop(); await speechToText.stop();
return text; return text;
} }
@@ -22,22 +22,22 @@ class SpeechToText {
void reset() => text = ""; void reset() => text = "";
/// Start a speech recognition session /// Start a speech recognition session
void _startListening() async => await _speechToText.listen(onResult: _onSpeechResult); void startListening() async => await speechToText.listen(onResult: onSpeechResult);
/// Manually stop the active speech recognition session /// Manually stop the active speech recognition session
/// Note that there are also timeouts that each platform enforces /// Note that there are also timeouts that each platform enforces
/// and the SpeechToText plugin supports setting timeouts on the /// and the SpeechToText plugin supports setting timeouts on the
/// listen method. /// listen method.
void _stopListening() async => await _speechToText.stop(); void stopListening() async => await speechToText.stop();
SpeechToText() { SpeechToText() {
_initSpeech(); initSpeech();
} }
/// This has to happen only once per app /// This has to happen only once per app
void _initSpeech() async => _speechEnabled = await _speechToText.initialize(); void initSpeech() async => speechEnabled = await speechToText.initialize();
/// This is the callback that the SpeechToText plugin calls when /// This is the callback that the SpeechToText plugin calls when
/// the platform returns recognized words. /// the platform returns recognized words.
void _onSpeechResult(SpeechRecognitionResult result) => text = result.recognizedWords; String onSpeechResult(SpeechRecognitionResult result) => (text = result.recognizedWords);
} }