From b081152238e0c4d243d7029057e9a8c22469a26d Mon Sep 17 00:00:00 2001 From: Michael Ilerioluwa Adeniyi <77995341+mickey4653@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:03:48 -0400 Subject: [PATCH 1/7] Made Some Changes: - created a component for discovery library feature - integrated the discovery library feature - created a discovery library window - added images necessary for discovery library - integrated discovery library via the main layout to follow code pattern in the project - created custom prompts in the constants folder --- .firebaserc | 6 +- frontend/assets/svg/ReadyPlayerMeAvatar.svg | 9 + frontend/assets/svg/StarGroupIcon.svg | 30 + frontend/assets/svg/Union.svg | 9 + frontend/assets/svg/UnionPurple.svg | 9 + frontend/assets/svg/add-block2.svg | 3 + frontend/constants/prompts.js | 25 + .../layouts/MainAppLayout/ MainAppLayout.jsx | 26 +- .../layouts/MainAppLayout/NavBar/Navbar.jsx | 26 +- frontend/pages/discovery/index.jsx | 13 + frontend/templates/Chat/Chat.jsx | 3 +- .../DiscoveryLibrary/DiscoveryLibrary.jsx | 110 + .../templates/Chat/DiscoveryLibrary/index.js | 1 + .../templates/Chat/DiscoveryLibrary/styles.js | 272 + .../DiscoveryLibraryWindow.jsx | 391 + .../Chat/DiscoveryLibraryWindow/index.js | 1 + .../Chat/DiscoveryLibraryWindow/styles.js | 270 + frontend/templates/Chat/styles.js | 12 + package-lock.json | 9721 ++++++++++++++++- 19 files changed, 10784 insertions(+), 153 deletions(-) create mode 100644 frontend/assets/svg/ReadyPlayerMeAvatar.svg create mode 100644 frontend/assets/svg/StarGroupIcon.svg create mode 100644 frontend/assets/svg/Union.svg create mode 100644 frontend/assets/svg/UnionPurple.svg create mode 100644 frontend/assets/svg/add-block2.svg create mode 100644 frontend/constants/prompts.js create mode 100644 frontend/pages/discovery/index.jsx create mode 100644 frontend/templates/Chat/DiscoveryLibrary/DiscoveryLibrary.jsx create mode 100644 frontend/templates/Chat/DiscoveryLibrary/index.js create mode 100644 frontend/templates/Chat/DiscoveryLibrary/styles.js create mode 100644 frontend/templates/Chat/DiscoveryLibraryWindow/DiscoveryLibraryWindow.jsx create mode 100644 frontend/templates/Chat/DiscoveryLibraryWindow/index.js create mode 100644 frontend/templates/Chat/DiscoveryLibraryWindow/styles.js diff --git a/.firebaserc b/.firebaserc index a665c764a..3c03f0e9c 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,6 +1,7 @@ { "projects": { - "default": "kai-ai-b8ac9" + "default": "kai-ai-b8ac9", + "mickey's-app-firebase": "kai-platform-b53aa" }, "targets": { "kai-ai-b8ac9": { @@ -11,5 +12,6 @@ } } }, - "etags": {} + "etags": {}, + "dataconnectEmulatorConfig": {} } \ No newline at end of file diff --git a/frontend/assets/svg/ReadyPlayerMeAvatar.svg b/frontend/assets/svg/ReadyPlayerMeAvatar.svg new file mode 100644 index 000000000..430d51ebb --- /dev/null +++ b/frontend/assets/svg/ReadyPlayerMeAvatar.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/assets/svg/StarGroupIcon.svg b/frontend/assets/svg/StarGroupIcon.svg new file mode 100644 index 000000000..553fd36ce --- /dev/null +++ b/frontend/assets/svg/StarGroupIcon.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/svg/Union.svg b/frontend/assets/svg/Union.svg new file mode 100644 index 000000000..821c193c4 --- /dev/null +++ b/frontend/assets/svg/Union.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/assets/svg/UnionPurple.svg b/frontend/assets/svg/UnionPurple.svg new file mode 100644 index 000000000..45e12fb08 --- /dev/null +++ b/frontend/assets/svg/UnionPurple.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/assets/svg/add-block2.svg b/frontend/assets/svg/add-block2.svg new file mode 100644 index 000000000..52530a107 --- /dev/null +++ b/frontend/assets/svg/add-block2.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/constants/prompts.js b/frontend/constants/prompts.js new file mode 100644 index 000000000..3508e0a9f --- /dev/null +++ b/frontend/constants/prompts.js @@ -0,0 +1,25 @@ +const categorizePrompts = [ + { + title: 'Math Tutor', + description: + 'From now on, I want you to act as a math tutor. I will be asking you questions related to various mathematical concepts, including algebra, geometry, calculus, and statistics. Please provide detailed explanations, step-by-step solutions, and relevant examples for each topic we discuss.', + }, + { + title: 'Biology Tutor', + description: + 'Please act as my biology tutor. I will ask you about topics such as cell biology, genetics, evolution, ecology, and human anatomy. Provide comprehensive explanations, diagrams, and examples to help me understand these biological concepts.', + }, + { + title: 'Programming Tutor', + description: + 'I want you to be my programming tutor. I will ask you about various programming languages, coding concepts, algorithms, and debugging techniques. Provide clear explanations, code examples, and step-by-step guidance for writing and understanding code.', + }, + { + title: 'Music Tutor', + description: + 'Act as a music tutor for our conversation. I will ask you about music theory, instruments, composition, and performance techniques. Provide detailed explanations, sheet music examples, and exercises to help me understand and improve my musical abilities.', + }, + // Add more prompts as needed +]; + +export default categorizePrompts; diff --git a/frontend/layouts/MainAppLayout/ MainAppLayout.jsx b/frontend/layouts/MainAppLayout/ MainAppLayout.jsx index 5b5d995dc..626814a10 100644 --- a/frontend/layouts/MainAppLayout/ MainAppLayout.jsx +++ b/frontend/layouts/MainAppLayout/ MainAppLayout.jsx @@ -1,14 +1,20 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Grid, useMediaQuery } from '@mui/material'; import Head from 'next/head'; +import { useRouter } from 'next/router'; import { useDispatch, useSelector } from 'react-redux'; import AppDisabled from '@/components/AppDisabled'; import Loader from '@/components/Loader'; +import DiscoveryLibraryWindow from '@/templates/Chat/DiscoveryLibraryWindow'; + +import ROUTES from '@/constants/routes'; + import NavBar from './NavBar'; + import styles from './styles'; import { setLoading } from '@/redux/slices/authSlice'; @@ -28,7 +34,8 @@ const MainAppLayout = (props) => { const auth = useSelector((state) => state.auth); const user = useSelector((state) => state.user); - + const [isDiscoveryOpen, setDiscoveryOpen] = useState(false); + const router = useRouter(); const isTabletScreen = useMediaQuery((theme) => theme.breakpoints.down('laptop') ); @@ -49,12 +56,25 @@ const MainAppLayout = (props) => { ); }; + const handleDiscoveryToggle = () => { + setDiscoveryOpen((prev) => !prev); + }; + + const isDiscoveryPage = router.pathname === ROUTES.DISCOVERY; + const renderApp = () => { return ( <> - + {children} + {/* DiscoveryLibraryWindow component displays a sidebar that contains discovery Library. This component is rendered on the right side of the chat interface. */} + {isDiscoveryPage && ( + + )} ); diff --git a/frontend/layouts/MainAppLayout/NavBar/Navbar.jsx b/frontend/layouts/MainAppLayout/NavBar/Navbar.jsx index 21d47507d..c57efb8a8 100644 --- a/frontend/layouts/MainAppLayout/NavBar/Navbar.jsx +++ b/frontend/layouts/MainAppLayout/NavBar/Navbar.jsx @@ -1,11 +1,15 @@ -import { Avatar, Button, Grid, Typography } from '@mui/material'; +import { useState } from 'react'; + +import { Avatar, Button, Grid, Slide, Typography } from '@mui/material'; import { signOut } from 'firebase/auth'; import { useRouter } from 'next/router'; import { useSelector } from 'react-redux'; +import DiscoveryLibrary from '@/templates/Chat/DiscoveryLibrary'; + import ChatIcon from '@/assets/svg/ChatIcon.svg'; -// import DiscoveryIcon from '@/assets/svg/DiscoveryIcon.svg'; +import DiscoveryIcon from '@/assets/svg/DiscoveryIcon.svg'; import HomeIcon from '@/assets/svg/HomeMenuIcon.svg'; import LogoutIcon from '@/assets/svg/LogoutIcon.svg'; @@ -24,12 +28,12 @@ const PAGES = [ icon: , id: 'home', }, - /* { + { name: 'Discovery', link: ROUTES.DISCOVERY, icon: , id: 'discovery', - }, */ + }, { name: 'Chat', link: ROUTES.CHAT, @@ -46,7 +50,8 @@ const PAGES = [ * @component * @returns {JSX.Element} The navigation bar component. */ -const NavBar = () => { +const NavBar = (props) => { + const { isDiscoveryOpen, toggleDiscovery } = props; const router = useRouter(); const user = useSelector((state) => state.user.data); @@ -126,7 +131,7 @@ const NavBar = () => { return isNotHomePage ? false : homeRegex.test(pathname); // TODO: Once Discovery Feature is ready, uncomment below statement. - // if (id === 'discovery') return discoveryRegex.test(pathname); + if (id === 'discovery') return discoveryRegex.test(pathname); if (id === 'chat') return chatRegex.test(pathname); @@ -139,8 +144,12 @@ const NavBar = () => { * @param {string} link - The route to navigate to. * @returns {void} */ - const handleRoute = (link) => { - router.push(link); + const handleRoute = (link, id) => { + if (id === 'discovery' && router.pathname === ROUTES.DISCOVERY) { + toggleDiscovery(); + } else { + router.push(link); + } }; return ( @@ -158,7 +167,6 @@ const NavBar = () => { ); }; - return ( {renderLogo()} diff --git a/frontend/pages/discovery/index.jsx b/frontend/pages/discovery/index.jsx new file mode 100644 index 000000000..b92b995bb --- /dev/null +++ b/frontend/pages/discovery/index.jsx @@ -0,0 +1,13 @@ +import MainAppLayout from '@/layouts/MainAppLayout'; +import DiscoveryLibrary from '@/templates/Chat/DiscoveryLibrary'; + +const MarvelDiscovery = () => { + localStorage.removeItem('sessionId'); + return ; +}; + +MarvelDiscovery.getLayout = function getLayout(page) { + return {page}; +}; + +export default MarvelDiscovery; diff --git a/frontend/templates/Chat/Chat.jsx b/frontend/templates/Chat/Chat.jsx index 92cbad710..b6b6443ab 100644 --- a/frontend/templates/Chat/Chat.jsx +++ b/frontend/templates/Chat/Chat.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { ArrowDownwardOutlined, @@ -27,6 +27,7 @@ import { MESSAGE_ROLE, MESSAGE_TYPES } from '@/constants/bots'; import ChatHistoryWindow from './ChatHistoryWindow'; import ChatSpinner from './ChatSpinner'; import DefaultPrompt from './DefaultPrompt'; +import DiscoveryLibraryWindow from './DiscoveryLibraryWindow'; import Message from './Message'; import QuickActions from './QuickActions'; import styles from './styles'; diff --git a/frontend/templates/Chat/DiscoveryLibrary/DiscoveryLibrary.jsx b/frontend/templates/Chat/DiscoveryLibrary/DiscoveryLibrary.jsx new file mode 100644 index 000000000..8a9493ae6 --- /dev/null +++ b/frontend/templates/Chat/DiscoveryLibrary/DiscoveryLibrary.jsx @@ -0,0 +1,110 @@ +import { useEffect, useState } from 'react'; + +import { + Card, + CardActionArea, + CardContent, + Grid, + IconButton, + Typography, +} from '@mui/material'; + +import { useDispatch, useSelector } from 'react-redux'; + +import UnionPurpleIcon from '@/assets/svg//UnionPurple.svg'; +import DiscoveryIcon from '@/assets/svg/add-block2.svg'; + +import AvatarImage from '@/assets/svg/ReadyPlayerMeAvatar.svg'; +import StarGroupIcon from '@/assets/svg/starGroupIcon.svg'; +import UnionIcon from '@/assets/svg/Union.svg'; + +import categorizePrompts from '@/constants/prompts'; + +import styles from './styles'; + +import { resetChat, setInput } from '@/redux/slices/chatSlice'; + +const DiscoveryLibrary = (props) => { + const { show, selectedPrompt } = props; + const { data: user } = useSelector((state) => state.user); + const [customPrompts, setCustomPrompts] = useState([]); + const dispatch = useDispatch(); + + useEffect(() => { + // Define your custom prompts here + + const fetchPrompts = async () => { + // This could be an API call or local data + setCustomPrompts(categorizePrompts); + }; + + fetchPrompts(); + }, []); + + const handlePromptClick = (prompt) => { + selectedPrompt(prompt); + dispatch(resetChat()); + }; + + return ( + + + + + + + Discovery + + + + + + + + Welcome Back, {user?.fullName || 'User'}! + + + + + + + {/* } alt="Avatar"/> */} + {/* {console.log("Avatar image present: ",)} */} + + + + + + + AI Custom Course Creator + + + Have Kai help you build your class from scratch! + + + + + + {customPrompts?.map((prompt, index) => ( + handlePromptClick(prompt)}> + + + + + {prompt.title} + + + + + + + + + ))} + + + + ); +}; + +export default DiscoveryLibrary; diff --git a/frontend/templates/Chat/DiscoveryLibrary/index.js b/frontend/templates/Chat/DiscoveryLibrary/index.js new file mode 100644 index 000000000..d210741fb --- /dev/null +++ b/frontend/templates/Chat/DiscoveryLibrary/index.js @@ -0,0 +1 @@ +export { default } from './DiscoveryLibrary'; diff --git a/frontend/templates/Chat/DiscoveryLibrary/styles.js b/frontend/templates/Chat/DiscoveryLibrary/styles.js new file mode 100644 index 000000000..b55e1e690 --- /dev/null +++ b/frontend/templates/Chat/DiscoveryLibrary/styles.js @@ -0,0 +1,272 @@ +const styles = { + iconButtonProps: { + sx: { + padding: '8px', + }, + }, + buttonTextProps: { + sx: { + padding: '8px', + }, + }, + menuListProps: { + sx: { + display: 'flex', + flexDirection: 'row', + padding: 1, + margin: '15px', + }, + }, + paperProps: { + sx: { + backgroundColor: 'transparent !important', + boxShadow: 'none', + }, + }, + menuItemProps: (disabled) => ({ + sx: (theme) => ({ + borderRadius: '18px', + margin: '0 5px', + + borderColor: theme.palette.Background.purple3, + background: theme.palette.Background.purple3, + color: theme.palette.Common.White['100p'], + textTransform: 'none', + ':hover': { + backgroundColor: disabled ? 'none' : '#B791FF', + borderColor: disabled + ? theme.palette.Background.purple3 + : theme.palette.Background.purple, + color: disabled + ? theme.palette.Common.White['60p'] + : theme.palette.Common.White['100p'], + }, + padding: '5px 20px', + opacity: disabled ? 0.5 : 1, + cursor: disabled ? 'not-allowed' : 'pointer', + + fontSize: { laptop: '13px', desktop: '12px', desktopMedium: '14px' }, + pl: { laptop: 1, desktop: 1, desktopMedium: 1 }, + pr: { laptop: 1, desktop: 1, desktopMedium: 1 }, + }), + }), + discoveryContainerGrid: (show) => ({ + container: true, + item: true, + mobileSmall: true, + position: 'absolute', + top: -50, + // left: 20, + left: { laptop: -35, desktop: 20 }, + height: '90%', + width: 'fit-content', + display: show ? 'block' : 'none', + backgroundColor: 'transparent !important', + }), + discoveryGridProps: { + container: true, + item: true, + mobileSmall: true, + rowGap: { laptop: 2, desktop: 4 }, + flexDirection: 'column', + + width: { desktopMedium: '37%', desktop: '37.5%', laptop: '100%' }, + sx: { + transform: { + laptop: 'scale(0.8 )', + desktop: 'scale(0.8)', + desktopMedium: 'scale(0.8)', + }, + borderRadius: '15px', + borderColor: '#B791FF', + }, + }, + discoveryPanelProps: { + sx: (theme) => ({ + px: 2, + py: 2, + background: theme.palette.Common.Black['100p'], + borderRadius: '16px 16px 0 0', + width: { desktop: '350px', mobile: '338px', mobileSmall: '330px' }, + }), + }, + unionIconGridProps: { + container: true, + alignItems: 'center', + sx: { + width: { desktop: '350px', mobile: '338px', mobileSmall: '330px' }, + mt: { laptop: -1, desktop: -3 }, + px: 2, + py: 2, + }, + }, + unionIconTextProps: { + sx: { + mb: 1, + color: '#9E94A5', + }, + }, + discoveryIconProps: { + sx: { + fontSize: '24px', + }, + }, + discoveryPanelTextProps: { + sx: (theme) => ({ + mt: 1, + mb: 1, + color: theme.palette.Common.White['100p'], + }), + }, + avatarGridProps: { + container: true, + item: true, + positive: 'relative', + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + sx: (theme) => ({ + ml: 2, + px: 2, + py: 2, + mt: { laptop: 1, desktop: -1 }, + background: theme.palette.Background.gradient.purple, + borderRadius: '16px', + width: { desktop: '320px', mobile: '308px', mobileSmall: '130px' }, + height: { desktop: '190px', mobile: '178px', mobileSmall: '170px' }, + }), + }, + avatarHeaderTextProps: { + fontSize: '18px', + fontWeight: 'bold', + }, + avatarSubTextProps: { + fontSize: '11px', + }, + avatarTextBoxProps: { + item: true, + alignItems: 'center', + sx: (theme) => ({ + background: theme.palette.Background.gradient.grey, + borderRadius: '5px', + padding: '10px 15px 10px 25px', + color: theme.palette.Common.White['100p'], + }), + right: { laptop: 240, desktop: 241 }, + top: { laptop: 40, desktop: 44 }, + position: 'relative', + zIndex: 2, + width: { laptop: '280px', desktop: '300px' }, + height: { laptop: '70px', desktop: '80px' }, + }, + + avatarImageGridProps: { + item: true, + sx: { + position: 'relative', + top: { laptop: -14, desktop: -17 }, + right: { laptop: 3, desktop: -3 }, + svg: { + transform: { laptop: 'scale(1.2)', desktop: 'scale(1.30)' }, + }, + }, + }, + starGroupIconGridProps: { + item: true, + sx: { + position: 'relative', + top: { laptop: -40, desktop: -44 }, + left: { laptop: 50, desktop: 60 }, + }, + }, + cardGridProps: { + container: true, + display: 'flex', + alignItems: 'center', + spacing: 1, + width: { desktop: '320px', mobile: '308px', mobileSmall: '130px' }, + height: { laptop: '44vh', desktop: '42vh' }, + overflow: 'scroll', + sx: { + position: 'relative', + top: { laptop: -5, desktop: -20, desktopMedium: -20 }, + right: { laptop: -25, desktop: -25, desktopMedium: -25 }, + pb: 1, + }, + }, + cardProps: { + sx: { + backgroundColor: 'transparent', + color: (theme) => `${theme.palette.Common.White['100p']}`, + border: '2px solid #9E86FF', + borderRadius: '8px', + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)', + cursor: 'pointer', + transition: '0.3s', + '&:hover': { + boxShadow: '0px 6px 12px rgba(0, 0, 0, 0.2)', + }, + width: { laptop: '142px', desktop: '148px' }, + height: { laptop: '150px', desktop: '150px' }, + }, + }, + cardTitleProps: { + sx: { + alignItems: 'center', + fontSize: '11px', + fontWeight: 'bold', + }, + }, + cardDescriptionProps: { + sx: (theme) => ({ + fontSize: '12px', + color: theme.palette.Greyscale[400], + }), + }, + backImageProps: { + width: '100%', // Adjust width as needed + height: '100%', // Adjust height as needed + }, + chatBoxGridProps: { + container: true, + item: true, + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + }, + chatBoxProps: { + width: { + laptop: '59%', + desktop: '64%', + desktopMedium: '70%', + desktopLarge: '75%', + }, + sx: { + position: 'relative', + top: { laptop: 80, desktop: 70, desktopMedium: 60 }, + left: { laptop: 340, desktop: 350, desktopMedium: 350 }, + }, + }, + CenterChatContentGridProps: { + sx: { + overflow: 'scroll', + height: { laptop: '500px', desktop: '700px' }, + width: { + laptop: '50%', + desktop: '60%', + desktopMedium: '65%', + desktopLarge: '72%', + }, + left: { laptop: 200, desktop: 200 }, + }, + }, + starIconProps: { + item: true, + sx: { + position: 'relative', + top: { laptop: 62, desktop: 64 }, + left: { laptop: 95, desktop: 105 }, + }, + }, +}; +export default styles; diff --git a/frontend/templates/Chat/DiscoveryLibraryWindow/DiscoveryLibraryWindow.jsx b/frontend/templates/Chat/DiscoveryLibraryWindow/DiscoveryLibraryWindow.jsx new file mode 100644 index 000000000..1c436e826 --- /dev/null +++ b/frontend/templates/Chat/DiscoveryLibraryWindow/DiscoveryLibraryWindow.jsx @@ -0,0 +1,391 @@ +import { useEffect, useRef, useState } from 'react'; + +import { + ArrowDownwardOutlined, + InfoOutlined, + Settings, +} from '@mui/icons-material'; +import AddIcon from '@mui/icons-material/Add'; +import { + Button, + Fade, + Grid, + InputAdornment, + Slide, + TextField, + Typography, +} from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import { useRouter } from 'next/router'; + +import { useDispatch, useSelector } from 'react-redux'; + +import NavigationIcon from '@/assets/svg/Navigation.svg'; +import UnionPurpleIcon from '@/assets/svg/UnionPurple.svg'; + +import { MESSAGE_ROLE, MESSAGE_TYPES } from '@/constants/bots'; + +import ROUTES from '@/constants/routes'; + +import ChatSpinner from '../ChatSpinner'; +import DiscoveryLibrary from '../DiscoveryLibrary'; +import Message from '../Message'; +import QuickActions from '../QuickActions'; + +import TextMessage from '../TextMessage'; + +import styles from './styles'; + +import { + openInfoChat, + resetChat, + setActionType, + setChatSession, + setDisplayQuickActions, + setError, + setFullyScrolled, + setInput, + setMessages, + setMore, + setSessionLoaded, + setStreaming, + setStreamingDone, + setTyping, +} from '@/redux/slices/chatSlice'; + +import createChatSession from '@/services/chatbot/createChatSession'; +import sendMessage from '@/services/chatbot/sendMessage'; + +const DiscoveryLibraryWindow = (props) => { + const { isDiscoveryOpen } = props; + const dispatch = useDispatch(); + const { + more, + input, + typing, + chat, + sessionLoaded, + openSettingsChat, + infoChatOpened, + fullyScrolled, + streamingDone, + streaming, + error, + displayQuickActions, + actionType, + } = useSelector((state) => state.chat); + const { data: userData } = useSelector((state) => state.user); + const messagesContainerRef = useRef(); + const sessionId = localStorage.getItem('sessionId'); + + const currentSession = chat; + const chatMessages = currentSession?.messages; + const showNewMessageIndicator = !fullyScrolled && streamingDone; + const router = useRouter(); + const isDiscoveryPage = router.pathname === ROUTES.DISCOVERY; + const [selectedPrompt, setSelectedPrompt] = useState(null); + const startConversation = async (message) => { + // Optionally dispatch a temporary message for the user's input + dispatch( + setMessages({ + role: MESSAGE_ROLE.HUMAN, + message, + }) + ); + + dispatch(setTyping(true)); + + // Define the chat payload + const chatPayload = { + user: { + id: userData?.id, + fullName: userData?.fullName, + email: userData?.email, + }, + type: 'chat', + message, + }; + + // Send a chat session + const { status, data } = await createChatSession(chatPayload, dispatch); + + // Remove typing bubble + dispatch(setTyping(false)); + if (status === 'created') dispatch(setStreaming(true)); + + // Set chat session + dispatch(setChatSession(data)); + dispatch(setSessionLoaded(true)); + setSelectedPrompt(null); + // dispatch(fetchHistory(userData.id)); + }; + + const handleSendMessage = async () => { + if (!input) { + dispatch(setError('Please enter a message')); + setTimeout(() => { + dispatch(setError(null)); + }, 3000); + return; + } + + // BUG FIX: First checking whether the user has entered any text before setting streaming true amd then sending the message. + dispatch(setStreaming(true)); + + const message = { + role: MESSAGE_ROLE.HUMAN, + type: MESSAGE_TYPES.TEXT, + payload: { + text: input, + action: actionType, + }, + }; + + if (!chatMessages) { + // Start a new conversation if there are no existing messages + await startConversation(message); + return; + } + + // Add the user's message to the chat + dispatch( + setMessages({ + role: MESSAGE_ROLE.HUMAN, + message, + }) + ); + + dispatch(setTyping(true)); + + // Ensure the user’s message is displayed before sending the message + setTimeout(async () => { + await sendMessage({ message, id: sessionId }, dispatch); + }, 0); + dispatch(setActionType(null)); + }; + + const handleQuickReply = async (option) => { + dispatch(setInput(option)); + dispatch(setStreaming(true)); + + const message = { + role: MESSAGE_ROLE.HUMAN, + type: MESSAGE_TYPES.QUICK_REPLY, + payload: { + text: option, + action: actionType, + }, + }; + + dispatch( + setMessages({ + role: MESSAGE_ROLE.HUMAN, + }) + ); + dispatch(setTyping(true)); + + await sendMessage({ message, id: currentSession?.id }, dispatch); + + dispatch(setActionType(null)); + }; + + const handleOnScroll = () => { + const scrolled = + Math.abs( + messagesContainerRef.current.scrollHeight - + messagesContainerRef.current.clientHeight - + messagesContainerRef.current.scrollTop + ) <= 1; + + if (fullyScrolled !== scrolled) dispatch(setFullyScrolled(scrolled)); + }; + + const handleScrollToBottom = () => { + messagesContainerRef.current?.scrollTo( + 0, + messagesContainerRef.current?.scrollHeight, + { + behavior: 'smooth', + } + ); + + dispatch(setStreamingDone(false)); + }; + /* Push Enter */ + const keyDownHandler = async (e) => { + if (typing || !input || streaming) return; + if (e.keyCode === 13) handleSendMessage(); + }; + + const renderQuickAction = () => { + return ( + + dispatch(setDisplayQuickActions(!displayQuickActions))} + {...styles.quickActionButton} + > + + Actions + + + ); + }; + + const renderSendIcon = () => { + return ( + + + + + + ); + }; + + const renderCustomPrompt = () => { + if (selectedPrompt) { + return ( + dispatch(setMore({ role: 'shutdown' }))} + {...styles.centerChat.centerChatGridProps} + > + + + + {selectedPrompt.title} + + + + + + + + ); + } + return null; + }; + const renderStartChatMessage = () => { + return ( + + ); + }; + + const renderCenterChatContent = () => { + if (selectedPrompt) { + return renderCustomPrompt(); // Render custom prompt if selected + } + return ( + dispatch(setMore({ role: 'shutdown' }))} + {...styles.centerChat.centerChatGridProps} + > + + {/* Render the start chat message if there are no chat messages or if the info chat is not open. */} + {(chatMessages?.length === 0 || !chatMessages) && !infoChatOpened + ? renderStartChatMessage() + : null} + {chatMessages?.map( + (message, index) => + message?.role !== MESSAGE_ROLE.SYSTEM && ( + + ) + )} + {typing && } + + + ); + }; + const renderNewMessageIndicator = () => { + return ( + +