Skip to content

Commit

Permalink
Move ChatSession behind pro version (Behind firebase remove config va…
Browse files Browse the repository at this point in the history
…lue) (#206)

* move ChatSession behind pro version

* refactor example phases

* fix issue with sending first message

* add events and error capture
  • Loading branch information
kamalkishor1991 authored Mar 30, 2024
1 parent a5dcb8b commit 94eaa6f
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 97 deletions.
10 changes: 3 additions & 7 deletions hooks/getUserData.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ const useUserData = function () {

const getUserData = useCallback(async () => {
setIsUserDataLoading(true);
try {
const userData = await retrieveUserSession();
if (userData) {
setUserData(userData);
}
} catch (error) {
console.error('Error retrieving user data', error);
const userData = await retrieveUserSession();
if (userData) {
setUserData(userData);
}
setIsUserDataLoading(false);
}, []);
Expand Down
72 changes: 72 additions & 0 deletions hooks/useChatSession.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState } from 'react';
import { createChatSession, followUpChatSession, getChatSession } from '../network/chat_session';
import { sendClientError } from '../libs/Helpers';

const useChatSession = (initialMessages) => {
const [messages, setMessages] = useState(initialMessages || []);
const [isLoading, setIsLoading] = useState(false);
const [isCreatingSession, setIsCreatingSession] = useState(false);
const [error, setError] = useState();

const fetchNewMessages = async (chatSession, latestMessage) => {
const interval = setInterval(async () => {
try {
const messageResponse = await getChatSession(chatSession.data.id, latestMessage.id);
if (messageResponse.data.length > 0) {
clearInterval(interval);
setMessages((prevMessages) => [...prevMessages, ...messageResponse.data]);
setIsLoading(false);
}
} catch (error) {
console.error('Error fetching chat session updates', error);
setError(error);
sendClientError('fetch_new_message', error.message);
clearInterval(interval);
}
}, 2000);
};

const createSession = async (message) => {
setIsLoading(true);
setIsCreatingSession(true);
setError(null);
try {
const chatSession = await createChatSession(message);
const latestMessage = chatSession.data.messages[chatSession.data.messages.length - 1];
setMessages([...messages, latestMessage]);
await fetchNewMessages(chatSession, latestMessage);
return chatSession;
} catch (error) {
console.error('Error creating chat session', error);
setError(error);
sendClientError('create_session', error.message);
throw error;
} finally {
setIsCreatingSession(false);
}
};

const followUpSession = async (sessionId, message) => {
setIsLoading(true);
setError(null);

try {
const chatSession = await followUpChatSession(sessionId, message);
const latestMessage = chatSession.data.messages[chatSession.data.messages.length - 1];
setMessages((prevMessages) => [...prevMessages, latestMessage]);
await fetchNewMessages(chatSession, latestMessage);
return chatSession;
} catch (error) {
console.error('Error following up chat session', error);
setError(error);
sendClientError('followUpSession', error.message);
throw error;
} finally {
setIsLoading(false);
}
};

return { messages, isLoading, isCreatingSession, error, createSession, followUpSession };
};

export default useChatSession;
2 changes: 0 additions & 2 deletions libs/EncryptedStoreage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ export async function storeUserSession(fullName, email, token, avatar_url) {
export async function retrieveUserSession() {
try {
const session = await EncryptedStorage.getItem('user_session');

if (session !== undefined) {
// Congrats! You've just retrieved your first value!
return JSON.parse(session);
}
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion libs/Helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NativeModules, Platform, Alert, ToastAndroid } from 'react-native';
import * as RNIap from 'react-native-iap';
import { requestPurchase, getProducts } from 'react-native-iap';
import {sendClientErrorAsync} from '../network/errors';
import { sendClientErrorAsync } from '../network/errors';

const productSku = function () {
//return 'local_test1';
Expand Down
179 changes: 101 additions & 78 deletions screens/ChatSessionScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ import {
ImageBackground
} from 'react-native';
import Colors from '../constants/Colors';
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, NativeModules } from 'react';
import { material } from 'react-native-typography';
import { logEvent } from '../libs/Helpers';
import { createChatSession, followUpChatSession, getChatSession } from '../network/chat_session';
import ChatCard from '../components/ChatCard';
import LoginScreen from './LoginScreen';
import useUserData from '../hooks/getUserData';
import CromaButton from '../components/CromaButton';
import { CromaContext } from '../store/store';
import useChatSession from '../hooks/useChatSession';

// eslint-disable-next-line no-undef
const bgImage = require('../assets/images/colorful_background.jpg');

const EXAMPLE_PHRASES = [
'Create a color palette for a kids website',
'Design a color palette for a fashion brand',
'Generate a color scheme for a travel website',
'Create a color palette for a food blog',
'Design a color scheme for a fitness app'
];
const ExamplePhrase = ({ phrase, onPress }) => (
<TouchableOpacity onPress={() => onPress(phrase)}>
<Text style={styles.examplePhrase}>{phrase}</Text>
Expand All @@ -29,19 +37,38 @@ const ExamplePhrase = ({ phrase, onPress }) => (

const ChatSessionScreen = (props) => {
const { route, navigation } = props;
const [messages, setMessages] = useState(route.params?.messages || []);

const [inputText, setInputText] = useState('');
const scrollViewRef = useRef();
const [isLoading, setIsLoading] = useState(false);
const [isCreatingSession, setIsCreatingSession] = useState(false);

const { isPro } = React.useContext(CromaContext);
const { messages, isLoading, isCreatingSession, error, createSession, followUpSession } =
useChatSession(route.params?.messages);

const { userData, isUserDataLoading, getUserData } = useUserData();
const [canUserCreateChat, setCanUserCreateChat] = useState();

useEffect(() => {
const fetchData = async () => {
if (!canUserCreateChat && !isUserDataLoading) {
if (userData && !isPro) {
setCanUserCreateChat(
await NativeModules.CromaModule.getConfigString('ai_behind_pro_version')
);
}
if (isPro && userData) {
setCanUserCreateChat(true);
}
}
};
fetchData();
}, [canUserCreateChat, isPro, isUserDataLoading, userData]);

useEffect(() => {
logEvent('chat_session_screen');
}, []);

const handleSend = async () => {
const handleSendMessage = async () => {
const message = {
chat_session: {
chat_session_type: 'color_palette',
Expand All @@ -53,34 +80,15 @@ const ChatSessionScreen = (props) => {
]
}
};
try {
let chatSession;
setIsLoading(true);
if (messages.length === 0) {
setIsCreatingSession(true);
chatSession = await createChatSession(message);
setIsCreatingSession(false);
} else {
chatSession = await followUpChatSession(messages[0].chat_session_id, message);
}
const latestMessage = chatSession.data.messages[chatSession.data.messages.length - 1];
setMessages([...messages, latestMessage]);
const interval = setInterval(async () => {
const messageResponse = await getChatSession(chatSession.data.id, latestMessage.id);
if (messageResponse.data.length > 0) {
clearInterval(interval);
setMessages((messages) => [...messages, ...messageResponse.data]);
scrollViewRef.current.scrollToEnd({ animated: true });
setIsLoading(false);
}
}, 2000);
setInputText('');
scrollViewRef.current.scrollToEnd({ animated: true });
} catch (error) {
console.error('Error sending message', error);
setIsLoading(false);
setIsCreatingSession(false);
if (messages.length === 0) {
logEvent('chat_session_create');
await createSession(message);
} else {
logEvent('chat_session_follow_up');
await followUpSession(messages[0].chat_session_id, message);
}
setInputText('');
scrollViewRef.current.scrollToEnd({ animated: true });
};

useEffect(() => {
Expand Down Expand Up @@ -131,26 +139,9 @@ const ChatSessionScreen = (props) => {
</Text>
<Text style={styles.emptyChatSubtext}>Here are few examples</Text>
<View style={styles.examplePhrasesContainer}>
<ExamplePhrase
phrase="Create a color palette for a kids website"
onPress={setInputText}
/>
<ExamplePhrase
phrase="Design a color palette for a fashion brand"
onPress={setInputText}
/>
<ExamplePhrase
phrase="Generate a color scheme for a travel website"
onPress={setInputText}
/>
<ExamplePhrase
phrase="Create a color palette for a food blog"
onPress={setInputText}
/>
<ExamplePhrase
phrase="Design a color scheme for a fitness app"
onPress={setInputText}
/>
{EXAMPLE_PHRASES.map((phrase, index) => (
<ExamplePhrase key={index} phrase={phrase} onPress={setInputText} />
))}
</View>
</View>
) : (
Expand All @@ -164,31 +155,48 @@ const ChatSessionScreen = (props) => {
/>
))
)}
<ActivityIndicator animating={isLoading} size="large" color="#ff7875" />
</ScrollView>

<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={inputText}
onChangeText={setInputText}
placeholder={
messages.length == 0
? 'Ex: create a color palette for kids website'
: 'follow up message to change the color palette'
}
/>
<TouchableOpacity
disabled={isLoading || inputText.trim() === ''}
onPress={handleSend}
style={
isLoading || inputText.trim() === ''
? styles.disableSendButton
: styles.sendButton
}>
<Text style={styles.textSend}> Send </Text>
</TouchableOpacity>
</View>
{error && (
<View style={styles.errorMessageContainer}>
<Text style={styles.errorMessageTitle}>Error: </Text>
<Text style={styles.errorMessageText}>{error}</Text>
</View>
)}
<ActivityIndicator animating={isLoading} size="large" color="#ff7875" />
{canUserCreateChat ? (
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={inputText}
onChangeText={setInputText}
placeholder={
messages.length == 0
? 'Ex: create a color palette for kids website'
: 'follow up message to change the color palette'
}
/>
<TouchableOpacity
disabled={isLoading || inputText.trim() === ''}
onPress={handleSendMessage}
style={
isLoading || inputText.trim() === ''
? styles.disableSendButton
: styles.sendButton
}>
<Text style={styles.textSend}> Send </Text>
</TouchableOpacity>
</View>
) : (
<CromaButton
style={{ backgroundColor: '#ff5c59', margin: 10 }}
textStyle={{ color: '#fff' }}
onPress={() => {
logEvent('chat_session_pro_button');
navigation.navigate('ProVersion');
}}>
Unlock pro to use this feature
</CromaButton>
)}
</>
)}
</View>
Expand Down Expand Up @@ -315,6 +323,21 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center'
},
errorMessageContainer: {
marginLeft: 10,
marginRight: 10,
backgroundColor: '#ff5c59',
borderRadius: 8
},
errorMessageText: {
color: 'white',
padding: 5
},
errorMessageTitle: {
color: 'white',
paddingLeft: 5,
paddingRight: 5
}
});

Expand Down
5 changes: 4 additions & 1 deletion screens/ColorPickerScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import { CromaContext } from '../store/store';
import SliderColorPicker from '../components/SliderColorPicker';
import AIColorPicker from '../components/AIColorPicker';
import Colors from '../constants/Colors';
import { useEffect } from 'react/cjs/react.production.min';

export default function ColorPickerScreen({ navigation }) {
const [color, setColor] = useState('#db0a5b');
const [activeTab, setActiveTab] = useState('basic');
const { colorPickerCallback } = useContext(CromaContext);

logEvent('color_picker_screen');
useEffect(() => {
logEvent('color_picker_screen_' + activeTab);
}, [activeTab]);
return (
<View style={styles.container}>
<View style={styles.tabContainer}>
Expand Down
Loading

0 comments on commit 94eaa6f

Please sign in to comment.