Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance/chat #975

Merged
merged 11 commits into from
Jun 6, 2024
7 changes: 7 additions & 0 deletions packages/app/assets/packrat_message_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/app/components/chat/chat.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,16 @@ export const loadStyles = (theme: any) => {
newChatButtonText: {
color: currentTheme.colors.white,
},
floatingChatContainer: {
position: 'absolute',
bottom: 50,
right: 50,
width: 300,
height: 500,
backgroundColor: '#fff',
borderRadius: 10,
padding: 10,
zIndex: 1000,
},
};
};
230 changes: 154 additions & 76 deletions packages/app/components/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { BaseModal, RButton, RInput, RStack } from '@packrat/ui';
import React, { useEffect, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
ScrollView,
Animated,
} from 'react-native';
import { RButton, RImage, RInput, RStack } from '@packrat/ui';
import useCustomStyles from 'app/hooks/useCustomStyles';
import { useChat } from 'app/hooks/chat/useChat';
import { loadStyles } from './chat.style';
// import { Select } from "tamagui";
import { ChatList } from '@packrat/ui/src/Bento/elements/list';
import { Button } from 'tamagui';
import { X } from '@tamagui/lucide-icons';

// TODO check if we've fixed the chat screen on another branch
// link: https://github.com/andrew-bierman/PackRat/issues/ ???

interface Message {
role: string;
content: string;
}

interface Chat {
id: string;
}

interface MessageBubbleProps {
message: Message;
}

interface ChatSelectorProps {
conversation: Chat;
onSelect: (id: string) => void;
isActive: boolean;
}

interface ChatComponentProps {
showChatSelector?: boolean;
defaultChatId?: string | null;
Expand All @@ -40,49 +29,6 @@ interface ChatModalTriggerProps {
itemTypeId: string | null;
}

const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
const styles = useCustomStyles(loadStyles);
const isAI = message.role === 'ai';
return (
<View style={isAI ? styles.aiBubble : styles.userBubble}>
<Text style={isAI ? styles.aiText : styles.userText}>
{message.content}
</Text>
</View>
);
};

interface MessageListProps {
messages: any[];
}

const MessageList = ({ messages }: MessageListProps) => {
return (
<FlatList
data={messages}
renderItem={({ item }) => <MessageBubble message={item} />}
keyExtractor={(item, index) => index.toString()}
/>
);
};

// const ChatSelector: React.FC<ChatSelectorProps> = ({
// conversation,
// onSelect,
// isActive,
// }) => {
// const styles = useCustomStyles(loadStyles);
// return (
// <TouchableOpacity
// key={conversation._id}
// onPress={() => onSelect(conversation._id)}
// style={[styles.chator, isActive && styles.activeChator]}
// >
// <Text style={styles.chatorText}>{conversation._id}</Text>
// </TouchableOpacity>
// );
// };

const ChatComponent: React.FC<ChatComponentProps> = ({
showChatSelector = true,
defaultChatId = null,
Expand All @@ -100,26 +46,65 @@ const ChatComponent: React.FC<ChatComponentProps> = ({
isLoading,
} = useChat({ itemTypeId });

const [messages, setMessages] = useState(parsedMessages);

useEffect(() => {
if (parsedMessages.length > messages.length) {
setMessages(parsedMessages);
}
}, [parsedMessages]);

const scrollViewRef = useRef<ScrollView>(null);

const handleLayout = () => {
scrollViewRef.current?.scrollToEnd({ animated: false });
};

return (
<View style={{ maxHeight: 280 }}>
<RStack style={{ alignItems: 'center' }}>
<View>
<RStack style={{ flex: 1 }}>
{showChatSelector && (
<>
{!parsedMessages?.length && (
<Text>You don't have conversations yet</Text>
{!messages?.length && (
<Text style={{ width: '100%', textAlign: 'center', padding: 8 }}>
You don't have conversations yet
</Text>
)}
</>
)}
<MessageList messages={parsedMessages} />
<RStack style={{ marginTop: 16, gap: 8 }}>
<ScrollView
ref={scrollViewRef}
onContentSizeChange={handleLayout}
style={{ maxHeight: 620, width: '100%', borderRadius: 10 }}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ flexGrow: 1, justifyContent: 'flex-end' }}
>
<ChatList data={messages} />
</ScrollView>
<RStack
style={{
marginTop: 10,
gap: 8,
flexDirection: 'row',
width: '100%',
}}
>
<RInput
placeholder="Type a message..."
value={userInput}
onChangeText={(text) => setUserInput(text)}
style={{ flex: 1 }}
/>
<RButton
disabled={!userInput}
onClick={() => handleSendMessage({ message: userInput })}
onClick={() => {
setMessages((prevMessages) => [
...prevMessages,
{ role: 'user', content: userInput },
]);
handleSendMessage({ message: userInput });
}}
style={{ width: 80 }}
>
<Text style={styles.sendText}>
{isLoading ? 'Loading...' : 'Send'}
Expand All @@ -133,12 +118,105 @@ const ChatComponent: React.FC<ChatComponentProps> = ({

const ChatModalTrigger: React.FC<ChatModalTriggerProps> = ({ itemTypeId }) => {
const styles = useCustomStyles(loadStyles);
const [isChatOpen, setIsChatOpen] = useState(false);
const animationValue = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.timing(animationValue, {
toValue: isChatOpen ? 1 : 0,
duration: 200,
useNativeDriver: false,
}).start();
}, [isChatOpen]);

return (
<View style={styles.container}>
<BaseModal title="Chat" trigger="Open Chat" footerComponent={undefined}>
<ChatComponent itemTypeId={itemTypeId} />
</BaseModal>
<TouchableOpacity onPress={() => setIsChatOpen(!isChatOpen)}>
<TouchableOpacity
style={{
width: 50,
height: 50,
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
}}
onPress={() => setIsChatOpen(!isChatOpen)}
>
<RImage
source={{
// TODO: Update this to use the intended chat logo
uri: 'https://raw.githubusercontent.com/andrew-bierman/PackRat/4ad449702c088e505c4b484219121d365150f971/packages/app/assets/chat-svgrepo-com%20(1).svg',
width: 50,
height: 50,
}}
width={40}
height={40}
style={{
...styles.logo,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.75,
shadowRadius: 1.24,
elevation: 3,
}}
alt="PackRat Logo"
/>
</TouchableOpacity>
</TouchableOpacity>
{isChatOpen && (
<Animated.View
style={{
position: 'absolute',
bottom: 50,
right: 20,
width: 450,
// height: 700,
backgroundColor: '#fff',
borderRadius: 10,
padding: 4,
zIndex: 1000,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
transform: [
{
translateX: animationValue.interpolate({
inputRange: [0, 1],
outputRange: [225, 0], // half of your view width
}),
},
{
translateY: animationValue.interpolate({
inputRange: [0, 1],
outputRange: [350, 0], // half of your view height
}),
},
{
scale: animationValue,
},
],
opacity: animationValue,
}}
>
<Button
position="absolute"
backgroundColor="$background"
top="$2"
right="$2"
size="$2"
circular
icon={X}
onPress={() => setIsChatOpen(false)}
style={{ zIndex: 1001 }}
/>
<ChatComponent itemTypeId={itemTypeId} />
</Animated.View>
)}
</View>
);
};
Expand Down
26 changes: 16 additions & 10 deletions packages/app/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Platform, View } from "react-native";
import { Platform, View } from 'react-native';

const Layout = ({ children }) => {
return (
<View
style={{
flex: 1,
backgroundColor: 'transparent',
width: Platform.OS === 'web' ? '60vw' : '100%',
alignSelf: 'center',
}}
>
{children}
</View>
);
};

const Layout = ({children}) => {
return (
<View style={{backgroundColor:'transparent', width:Platform.OS === 'web' ? '60vw' : '100%', alignSelf:'center'}}>
{children}
</View>
)
}

export default Layout;
export default Layout;
Loading
Loading