import React, { useEffect, useContext, useRef } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil'; import SubmitButton from './SubmitButton'; import OptionsBar from './OptionsBar'; import { EndpointMenu } from './EndpointMenu'; import Footer from './Footer'; import { useMessageHandler, ThemeContext } from '~/hooks'; import { cn } from '~/utils'; import store from '~/store'; export default function TextChat({ isSearchView = false }) { const { ask, isSubmitting, handleStopGenerating, latestMessage, endpointsConfig } = useMessageHandler(); const conversation = useRecoilValue(store.conversation); const setShowBingToneSetting = useSetRecoilState(store.showBingToneSetting); const [text, setText] = useRecoilState(store.text); const { theme } = useContext(ThemeContext); const isComposing = useRef(false); const inputRef = useRef(null); // TODO: do we need this? const disabled = false; const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error; const { conversationId, jailbreak } = conversation || {}; // auto focus to input, when enter a conversation. useEffect(() => { if (!conversationId) { return; } // Prevents Settings from not showing on new conversation, also prevents showing toneStyle change without jailbreak if (conversationId === 'new' || !jailbreak) { setShowBingToneSetting(false); } if (conversationId !== 'search') { inputRef.current?.focus(); } // setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array // eslint-disable-next-line react-hooks/exhaustive-deps }, [conversationId, jailbreak]); useEffect(() => { const timeoutId = setTimeout(() => { inputRef.current?.focus(); }, 100); return () => clearTimeout(timeoutId); }, [isSubmitting]); const submitMessage = () => { ask({ text }); setText(''); }; const handleKeyDown = (e) => { if (e.key === 'Enter' && isSubmitting) { return; } if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); } if (e.key === 'Enter' && !e.shiftKey && !isComposing?.current) { submitMessage(); } }; const handleKeyUp = (e) => { if (e.keyCode === 8 && e.target.value.trim() === '') { setText(e.target.value); } if (e.key === 'Enter' && e.shiftKey) { return console.log('Enter + Shift'); } if (isSubmitting) { return; } }; const handleCompositionStart = () => { isComposing.current = true; }; const handleCompositionEnd = () => { isComposing.current = false; }; const changeHandler = (e) => { const { value } = e.target; setText(value); }; const getPlaceholderText = () => { if (isSearchView) { return 'Click a message title to open its conversation.'; } if (disabled) { return 'Choose another model or customize GPT again'; } if (isNotAppendable) { return 'Edit your message or Regenerate.'; } return ''; }; if (isSearchView) { return <>; } let isDark = theme === 'dark'; if (theme === 'system') { isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; } return ( <>
); }