import { Conversation, ErrorMessage, KeyValuePair, Message, OpenAIModel, } from '@/types'; import { FC, MutableRefObject, useCallback, useEffect, useRef, useState, } from 'react'; import { useTranslation } from 'next-i18next'; import { ChatInput } from './ChatInput'; import { ChatLoader } from './ChatLoader'; import { ChatMessage } from './ChatMessage'; import { ErrorMessageDiv } from './ErrorMessageDiv'; import { ModelSelect } from './ModelSelect'; import { SystemPrompt } from './SystemPrompt'; import { IconSettings } from "@tabler/icons-react"; interface Props { conversation: Conversation; models: OpenAIModel[]; apiKey: string; serverSideApiKeyIsSet: boolean; messageIsStreaming: boolean; modelError: ErrorMessage | null; messageError: boolean; loading: boolean; onSend: (message: Message, deleteCount?: number) => void; onUpdateConversation: ( conversation: Conversation, data: KeyValuePair, ) => void; onEditMessage: (message: Message, messageIndex: number) => void; stopConversationRef: MutableRefObject; } export const Chat: FC = ({ conversation, models, apiKey, serverSideApiKeyIsSet, messageIsStreaming, modelError, messageError, loading, onSend, onUpdateConversation, onEditMessage, stopConversationRef, }) => { const { t } = useTranslation('chat'); const [currentMessage, setCurrentMessage] = useState(); const [autoScrollEnabled, setAutoScrollEnabled] = useState(true); const [showSettings, setShowSettings] = useState(false); const messagesEndRef = useRef(null); const chatContainerRef = useRef(null); const textareaRef = useRef(null); const scrollToBottom = useCallback(() => { if (autoScrollEnabled) { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); textareaRef.current?.focus(); } }, [autoScrollEnabled]); const handleScroll = () => { if (chatContainerRef.current) { const { scrollTop, scrollHeight, clientHeight } = chatContainerRef.current; const bottomTolerance = 5; if (scrollTop + clientHeight < scrollHeight - bottomTolerance) { setAutoScrollEnabled(false); } else { setAutoScrollEnabled(true); } } }; const handleSettings = () => { setShowSettings(!showSettings); }; useEffect(() => { scrollToBottom(); setCurrentMessage(conversation.messages[conversation.messages.length - 2]); }, [conversation.messages, scrollToBottom]); useEffect(() => { const chatContainer = chatContainerRef.current; if (chatContainer) { chatContainer.addEventListener('scroll', handleScroll); return () => { chatContainer.removeEventListener('scroll', handleScroll); }; } }, []); return (
{!(apiKey || serverSideApiKeyIsSet) ? (
{t('OpenAI API Key Required')}
{t( 'Please set your OpenAI API key in the bottom left of the sidebar.', )}
) : modelError ? ( ) : ( <>
{conversation.messages.length === 0 ? ( <>
{models.length === 0 ? t('Loading...') : 'Chatbot UI'}
{models.length > 0 && (
onUpdateConversation(conversation, { key: 'model', value: model, }) } /> onUpdateConversation(conversation, { key: 'prompt', value: prompt, }) } />
)}
) : ( <>
{t('Model')}: {conversation.model.name}
{showSettings && (
onUpdateConversation(conversation, { key: "model", value: model })} />
)} {conversation.messages.map((message, index) => ( ))} {loading && }
)}
{ setCurrentMessage(message); onSend(message); }} onRegenerate={() => { if (currentMessage) { onSend(currentMessage, 2); } }} /> )}
); };