Skip to content

Commit

Permalink
Merge pull request #4 from varunon9/content_generator
Browse files Browse the repository at this point in the history
New feature: Generate content e.g. poem, quotes, thoughts etc and share with world.
  • Loading branch information
varunon9 authored Apr 14, 2023
2 parents bd5bd16 + 97365a1 commit f0cc2da
Show file tree
Hide file tree
Showing 17 changed files with 493 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

# Scripts
/server
Binary file added assets/images/content-background.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:pocket_ai/firebase_options.dart';
import 'package:pocket_ai/src/modules/chat/screens/chat_screen.dart';
import 'package:pocket_ai/src/modules/faqs/screens/faqs_screen.dart';
import 'package:pocket_ai/src/modules/content_generator/screens/content_generator_screen.dart';
import 'package:pocket_ai/src/modules/settings/screens/settings_screen.dart';
import 'package:pocket_ai/src/modules/splash/screens/splash_screen.dart';
import 'package:pocket_ai/src/widgets/custom_colors.dart';
Expand Down Expand Up @@ -53,6 +54,8 @@ class MyApp extends StatelessWidget {
ChatScreen.routeName: (context) => const ChatScreen(),
FaqsScreen.routeName: (context) => const FaqsScreen(),
SettingsScreen.routeName: (context) => const SettingsScreen(),
ContentGeneratorScreen.routeName: (context) =>
const ContentGeneratorScreen(),
},
);
}
Expand Down
14 changes: 7 additions & 7 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
class AiBotConstants {
static String introMessage =
'Hi, I am pocket AI bot. How can I help you today?';
static List<String> gptModels = [
'text-davinci-003',
'text-curie-001',
'text-babbage-001',
'text-ada-001',
];
static String introMessageForContentGenerator =
'Generate quote/poem/thoughts or any content and share with world. Copy and paste below example prompt to get started.';
static String contentGeneratorSamplePrompt =
"Generate a quote in Hindi based on following emotions- love, life, dream. Translation is not required.";
}

class FirestoreCollectionsConst {
Expand All @@ -15,12 +13,14 @@ class FirestoreCollectionsConst {
static String messages = 'messages';
static String openAiApiKeys = 'openAiApiKeys';
static String userSessionsCount = 'userSessionsCount';
static String contentGeneratorPrompts = 'contentGeneratorPrompts';
static String prompts = 'prompts';
}

String androidPackageName = 'me.varunon9.pocket_ai';

class SharedPrefsKeys {
static String maxTokensCount = 'maxTokensCount';
static String openAiApiKey = 'openAiApiKey';
static String gpt3Model = 'gpt3Model';
static String generatedContentSignature = 'generatedContentSignature';
}
6 changes: 4 additions & 2 deletions lib/src/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class Globals {
static String? freeOpenAiApiKey;

// this will be overridden in splash screen init
static AppSettings appSettings =
AppSettings(maxTokensCount: 150, openAiApiKey: null);
static AppSettings appSettings = AppSettings(
maxTokensCount: 150,
openAiApiKey: null,
generatedContentSignature: '- Pocket AI');
}
37 changes: 25 additions & 12 deletions lib/src/modules/chat/screens/chat_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import 'package:pocket_ai/src/globals.dart';
import 'package:pocket_ai/src/modules/chat/chat_actions.dart';
import 'package:pocket_ai/src/modules/chat/models/chat_message.dart';
import 'package:pocket_ai/src/modules/faqs/screens/faqs_screen.dart';
import 'package:pocket_ai/src/modules/content_generator/screens/content_generator_screen.dart';
import 'package:pocket_ai/src/modules/settings/screens/settings_screen.dart';
import 'package:pocket_ai/src/utils/analytics.dart';
import 'package:pocket_ai/src/utils/common.dart';
import 'package:pocket_ai/src/widgets/custom_colors.dart';
import 'package:pocket_ai/src/widgets/custom_text.dart';
import 'package:pocket_ai/src/widgets/custom_text_form_field.dart';
import 'package:pocket_ai/src/widgets/heading.dart';

Expand Down Expand Up @@ -163,6 +163,19 @@ class _ChatScreen extends State<ChatScreen> {
});
}

void handlePopupMenuClick(int item) {
switch (item) {
case 0:
logEvent(EventNames.helpIconClicked, {});
navigateToScreen(context, FaqsScreen.routeName);
break;
case 1:
logEvent(EventNames.settingsIconClicked, {});
navigateToScreen(context, SettingsScreen.routeName);
break;
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand All @@ -175,19 +188,19 @@ class _ChatScreen extends State<ChatScreen> {
backgroundColor: CustomColors.darkBackground,
actions: <Widget>[
IconButton(
tooltip: 'Help',
onPressed: (() {
logEvent(EventNames.helpIconClicked, {});
navigateToScreen(context, FaqsScreen.routeName);
}),
icon: const Icon(Icons.help)),
IconButton(
tooltip: 'Settings',
tooltip: 'Content Generator',
onPressed: (() {
logEvent(EventNames.settingsIconClicked, {});
navigateToScreen(context, SettingsScreen.routeName);
logEvent(EventNames.contentGeneratorIconClicked, {});
navigateToScreen(context, ContentGeneratorScreen.routeName);
}),
icon: const Icon(Icons.settings))
icon: const Icon(Icons.mood)),
PopupMenuButton<int>(
onSelected: (item) => handlePopupMenuClick(item),
itemBuilder: (context) => [
const PopupMenuItem<int>(value: 0, child: Text('Help')),
const PopupMenuItem<int>(value: 1, child: Text('Settings')),
],
),
]),
body: Stack(children: [
Container(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import 'package:bubble/bubble.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:pocket_ai/src/constants.dart';
import 'package:pocket_ai/src/globals.dart';
import 'package:pocket_ai/src/modules/chat/chat_actions.dart';
import 'package:pocket_ai/src/modules/chat/models/chat_message.dart';
import 'package:pocket_ai/src/modules/content_generator/widgets/content_image.dart';
import 'package:pocket_ai/src/modules/faqs/screens/faqs_screen.dart';
import 'package:pocket_ai/src/modules/settings/screens/settings_screen.dart';
import 'package:pocket_ai/src/utils/analytics.dart';
import 'package:pocket_ai/src/utils/common.dart';
import 'package:pocket_ai/src/widgets/custom_colors.dart';
import 'package:pocket_ai/src/widgets/custom_text.dart';
import 'package:pocket_ai/src/widgets/custom_text_form_field.dart';
import 'package:pocket_ai/src/widgets/heading.dart';

class ContentGeneratorScreen extends StatefulWidget {
static const routeName = '/content-generator';

const ContentGeneratorScreen({Key? key}) : super(key: key);

@override
State<StatefulWidget> createState() => _ContentGeneratorScreen();
}

class _ContentGeneratorScreen extends State<ContentGeneratorScreen> {
List<ChatMessage> chatMessages = [
ChatMessage(
content: AiBotConstants.introMessageForContentGenerator,
role: ChatRole.system),
ChatMessage(
content: AiBotConstants.contentGeneratorSamplePrompt,
role: ChatRole.system)
];
bool apiCallInProgress = false;
FirebaseFirestore db = FirebaseFirestore.instance;

TextEditingController contentGeneratorPromptController =
TextEditingController();
ScrollController listViewController = ScrollController();

@override
void initState() {
super.initState();
logEvent(EventNames.contentGeneratorScreenViewed, {});
}

void onChatMessageLongPress(ChatMessage chatItem) {
Clipboard.setData(ClipboardData(text: chatItem.content)).then((value) {
showToastMessage('Copied to Clipboard');
});
}

void saveContentGeneratorPromptsToFirestore(String prompt) {
// store prompts to Firestore for study & analytics
String? deviceId = Globals.deviceId;
if (deviceId != null) {
db
.collection(FirestoreCollectionsConst.contentGeneratorPrompts)
.doc(deviceId)
.collection(FirestoreCollectionsConst.prompts)
.doc()
.set({'prompt': prompt, 'time': FieldValue.serverTimestamp()});
}
}

void onSendPress() {
String prompt = contentGeneratorPromptController.text;
if (prompt.isEmpty) {
return;
}
logEvent(EventNames.generateContentClicked, {});

setState(() {
chatMessages = [
...chatMessages,
ChatMessage(content: prompt, role: ChatRole.user)
];
apiCallInProgress = true;
});
contentGeneratorPromptController.text = '';

// adding delay so that list view is scrolled after setState re-render has been completed
Future.delayed(const Duration(milliseconds: 100), () {
listViewController.jumpTo(listViewController.position.maxScrollExtent);
});
saveContentGeneratorPromptsToFirestore(prompt);

getResponseFromOpenAi([ChatMessage(content: prompt, role: ChatRole.user)])
.then((response) {
String botMessage = '${response['choices'][0]['message']['content']}';
setState(() {
chatMessages = [
...chatMessages,
ChatMessage(content: botMessage, role: ChatRole.assistant)
];
});
logEvent(EventNames.openAiResponseSuccess, {});
}).catchError((error) {
logApiErrorAndShowMessage(context, exception: error);
logEvent(EventNames.openAiResponseFailed, {});
}).then((value) {
setState(() {
apiCallInProgress = false;
});
Future.delayed(const Duration(milliseconds: 100), () {
listViewController.jumpTo(listViewController.position.maxScrollExtent);
});
});
}

void handlePopupMenuClick(int item) {
switch (item) {
case 0:
logEvent(EventNames.helpIconClicked, {});
navigateToScreen(context, FaqsScreen.routeName);
break;
case 1:
logEvent(EventNames.settingsIconClicked, {});
navigateToScreen(context, SettingsScreen.routeName);
break;
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Heading(
'Content Generator',
type: HeadingType.h4,
),
backgroundColor: CustomColors.darkBackground,
actions: <Widget>[
PopupMenuButton<int>(
onSelected: (item) => handlePopupMenuClick(item),
itemBuilder: (context) => [
const PopupMenuItem<int>(value: 0, child: Text('Help')),
const PopupMenuItem<int>(value: 1, child: Text('Settings')),
],
),
]),
body: Stack(children: [
Container(
margin: const EdgeInsets.only(bottom: 72),
child: ListView.builder(
controller: listViewController,
itemCount: chatMessages.length,
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 12, bottom: 12),
itemBuilder: (context, index) {
var chatItem = chatMessages[index];
bool fromBot = chatItem.role == ChatRole.assistant;
bool fromSystem = chatItem.role == ChatRole.system;
return GestureDetector(
onLongPress: () {
onChatMessageLongPress(chatItem);
},
child: fromSystem
? Container(
margin: const EdgeInsets.only(
top: 8, bottom: 0, left: 16, right: 16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: CustomColors.secondary,
),
child: CustomText(
chatItem.content,
style: const TextStyle(
color: Colors.white, fontSize: 10),
),
)
: Bubble(
nip: fromBot
? BubbleNip.leftTop
: BubbleNip.rightTop,
margin: const BubbleEdges.only(
top: 16, left: 8, right: 16),
color:
fromBot ? Colors.white : CustomColors.lightText,
alignment: fromBot
? Alignment.topLeft
: Alignment.topRight,
child: fromBot
? ContentImage(content: chatItem.content)
: MarkdownBody(data: chatItem.content)),
);
})),
Align(
alignment: Alignment.bottomLeft,
child: Container(
margin:
const EdgeInsets.only(top: 8, left: 8, right: 8, bottom: 8),
child: Row(
children: [
Expanded(
child: Container(
margin: const EdgeInsets.only(right: 8),
child: CustomTextFormField(
onChanged: (value) => {},
controller: contentGeneratorPromptController,
minLines: 1,
maxLines: 4,
textInputType: TextInputType.multiline,
hintText: 'Your prompt to generate content'),
),
),
Ink(
decoration: const ShapeDecoration(
color: CustomColors.secondary,
shape: CircleBorder(),
),
width: 48,
height: 48,
child: apiCallInProgress
? const CircularProgressIndicator(
color: CustomColors.primary,
)
: IconButton(
tooltip: 'Send',
onPressed: onSendPress,
color: CustomColors.primary,
icon: const Icon(Icons.send_rounded)),
)
],
)),
)
]),
);
}
}
Loading

0 comments on commit f0cc2da

Please sign in to comment.