import { useEffect, useRef, useState } from 'react'; import { useAppContext } from '../utils/app.context'; import StorageUtils from '../utils/storage'; import { useNavigate } from 'react-router'; import ChatMessage from './ChatMessage'; import { PendingMessage } from '../utils/types'; export default function ChatScreen() { const { viewingConversation, sendMessage, isGenerating, stopGenerating, pendingMessages, } = useAppContext(); const [inputMsg, setInputMsg] = useState(''); const containerRef = useRef(null); const navigate = useNavigate(); const currConvId = viewingConversation?.id ?? ''; const pendingMsg: PendingMessage | undefined = pendingMessages[currConvId]; const scrollToBottom = (requiresNearBottom: boolean) => { if (!containerRef.current) return; const msgListElem = containerRef.current; const spaceToBottom = msgListElem.scrollHeight - msgListElem.scrollTop - msgListElem.clientHeight; if (!requiresNearBottom || spaceToBottom < 50) { setTimeout( () => msgListElem.scrollTo({ top: msgListElem.scrollHeight }), 1 ); } }; // scroll to bottom when conversation changes useEffect(() => { scrollToBottom(false); }, [viewingConversation?.id]); const sendNewMessage = async () => { if (inputMsg.trim().length === 0 || isGenerating(currConvId)) return; const convId = viewingConversation?.id ?? StorageUtils.getNewConvId(); const lastInpMsg = inputMsg; setInputMsg(''); if (!viewingConversation) { // if user is creating a new conversation, redirect to the new conversation navigate(`/chat/${convId}`); } scrollToBottom(false); // auto scroll as message is being generated const onChunk = () => scrollToBottom(true); if (!(await sendMessage(convId, inputMsg, onChunk))) { // restore the input message if failed setInputMsg(lastInpMsg); } }; return ( <> {/* chat messages */}
{/* placeholder to shift the message to the bottom */} {viewingConversation ? '' : 'Send a message to start'}
{viewingConversation?.messages.map((msg) => ( ))} {pendingMsg && ( )}
{/* chat input */}
{isGenerating(currConvId) ? ( ) : ( )}
); }