Download client/src/components/Chat/Input/ChatForm.tsx from Zhitomir/chat: direct link, hf CLI and curl.
- Browser
- Download file 7.79 kB
-
https://huggingface.co/spaces/Zhitomir/chat/resolve/main/client/src/components/Chat/Input/ChatForm.tsx
- Command line
-
hf download hf://spaces/Zhitomir/chat/client/src/components/Chat/Input/ChatForm.tsx
-
curl -L -o ChatForm.tsx https://huggingface.co/spaces/Zhitomir/chat/resolve/main/client/src/components/Chat/Input/ChatForm.tsx
7.79 kB
| import { memo, useRef, useMemo } from 'react'; | |
| import { useRecoilState, useRecoilValue } from 'recoil'; | |
| import { | |
| supportsFiles, | |
| mergeFileConfig, | |
| isAssistantsEndpoint, | |
| fileConfig as defaultFileConfig, | |
| } from 'librechat-data-provider'; | |
| import { | |
| useChatContext, | |
| useAddedChatContext, | |
| useAssistantsMapContext, | |
| useChatFormContext, | |
| } from '~/Providers'; | |
| import { | |
| useTextarea, | |
| useAutoSave, | |
| useRequiresKey, | |
| useHandleKeyUp, | |
| useSubmitMessage, | |
| } from '~/hooks'; | |
| import { TextareaAutosize } from '~/components/ui'; | |
| import { useGetFileConfig } from '~/data-provider'; | |
| import { cn, removeFocusRings } from '~/utils'; | |
| import TextareaHeader from './TextareaHeader'; | |
| import AttachFile from './Files/AttachFile'; | |
| import AudioRecorder from './AudioRecorder'; | |
| import { mainTextareaId } from '~/common'; | |
| import StreamAudio from './StreamAudio'; | |
| import StopButton from './StopButton'; | |
| import SendButton from './SendButton'; | |
| import FileRow from './Files/FileRow'; | |
| import Mention from './Mention'; | |
| import store from '~/store'; | |
| const ChatForm = ({ index = 0 }) => { | |
| const submitButtonRef = useRef<HTMLButtonElement>(null); | |
| const textAreaRef = useRef<HTMLTextAreaElement | null>(null); | |
| const SpeechToText = useRecoilValue(store.SpeechToText); | |
| const TextToSpeech = useRecoilValue(store.TextToSpeech); | |
| const automaticPlayback = useRecoilValue(store.automaticPlayback); | |
| const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); | |
| const [showPlusPopover, setShowPlusPopover] = useRecoilState(store.showPlusPopoverFamily(index)); | |
| const [showMentionPopover, setShowMentionPopover] = useRecoilState( | |
| store.showMentionPopoverFamily(index), | |
| ); | |
| const { requiresKey } = useRequiresKey(); | |
| const handleKeyUp = useHandleKeyUp({ textAreaRef, setShowPlusPopover, setShowMentionPopover }); | |
| const { handlePaste, handleKeyDown, handleCompositionStart, handleCompositionEnd } = useTextarea({ | |
| textAreaRef, | |
| submitButtonRef, | |
| disabled: !!requiresKey, | |
| }); | |
| const { | |
| files, | |
| setFiles, | |
| conversation, | |
| isSubmitting, | |
| filesLoading, | |
| setFilesLoading, | |
| newConversation, | |
| handleStopGenerating, | |
| } = useChatContext(); | |
| const methods = useChatFormContext(); | |
| const { | |
| addedIndex, | |
| generateConversation, | |
| conversation: addedConvo, | |
| setConversation: setAddedConvo, | |
| isSubmitting: isSubmittingAdded, | |
| } = useAddedChatContext(); | |
| const showStopAdded = useRecoilValue(store.showStopButtonByIndex(addedIndex)); | |
| const { clearDraft } = useAutoSave({ | |
| conversationId: useMemo(() => conversation?.conversationId, [conversation]), | |
| textAreaRef, | |
| files, | |
| setFiles, | |
| }); | |
| const assistantMap = useAssistantsMapContext(); | |
| const { submitMessage } = useSubmitMessage({ clearDraft }); | |
| const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null }; | |
| const endpoint = endpointType ?? _endpoint; | |
| const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ | |
| select: (data) => mergeFileConfig(data), | |
| }); | |
| const endpointFileConfig = fileConfig.endpoints[endpoint ?? '']; | |
| const invalidAssistant = useMemo( | |
| () => | |
| isAssistantsEndpoint(conversation?.endpoint) && | |
| (!conversation?.assistant_id || | |
| !assistantMap?.[conversation?.endpoint ?? '']?.[conversation?.assistant_id ?? '']), | |
| [conversation?.assistant_id, conversation?.endpoint, assistantMap], | |
| ); | |
| const disableInputs = useMemo( | |
| () => !!(requiresKey || invalidAssistant), | |
| [requiresKey, invalidAssistant], | |
| ); | |
| const { ref, ...registerProps } = methods.register('text', { | |
| required: true, | |
| onChange: (e) => { | |
| methods.setValue('text', e.target.value, { shouldValidate: true }); | |
| }, | |
| }); | |
| return ( | |
| <form | |
| onSubmit={methods.handleSubmit((data) => submitMessage(data))} | |
| className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:mx-4 md:last:mb-6 lg:mx-auto lg:max-w-2xl xl:max-w-3xl" | |
| > | |
| <div className="relative flex h-full flex-1 items-stretch md:flex-col"> | |
| <div className="flex w-full items-center"> | |
| {showPlusPopover && !isAssistantsEndpoint(endpoint) && ( | |
| <Mention | |
| setShowMentionPopover={setShowPlusPopover} | |
| newConversation={generateConversation} | |
| textAreaRef={textAreaRef} | |
| commandChar="+" | |
| placeholder="com_ui_add" | |
| includeAssistants={false} | |
| /> | |
| )} | |
| {showMentionPopover && ( | |
| <Mention | |
| setShowMentionPopover={setShowMentionPopover} | |
| newConversation={newConversation} | |
| textAreaRef={textAreaRef} | |
| /> | |
| )} | |
| <div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border dark:border-gray-600 dark:text-white [&:has(textarea:focus)]:border-gray-300 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)] dark:[&:has(textarea:focus)]:border-gray-500"> | |
| <TextareaHeader addedConvo={addedConvo} setAddedConvo={setAddedConvo} /> | |
| <FileRow | |
| files={files} | |
| setFiles={setFiles} | |
| setFilesLoading={setFilesLoading} | |
| Wrapper={({ children }) => ( | |
| <div className="mx-2 mt-2 flex flex-wrap gap-2 px-2.5 md:pl-0 md:pr-4"> | |
| {children} | |
| </div> | |
| )} | |
| /> | |
| {endpoint && ( | |
| <TextareaAutosize | |
| {...registerProps} | |
| autoFocus | |
| ref={(e) => { | |
| ref(e); | |
| textAreaRef.current = e; | |
| }} | |
| disabled={disableInputs} | |
| onPaste={handlePaste} | |
| onKeyDown={handleKeyDown} | |
| onKeyUp={handleKeyUp} | |
| onCompositionStart={handleCompositionStart} | |
| onCompositionEnd={handleCompositionEnd} | |
| id={mainTextareaId} | |
| tabIndex={0} | |
| data-testid="text-input" | |
| style={{ height: 44, overflowY: 'auto' }} | |
| rows={1} | |
| className={cn( | |
| supportsFiles[endpointType ?? endpoint ?? ''] && !endpointFileConfig?.disabled | |
| ? ' pl-10 md:pl-[55px]' | |
| : 'pl-3 md:pl-4', | |
| 'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ', | |
| SpeechToText ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12', | |
| 'max-h-[65vh] md:max-h-[75vh]', | |
| removeFocusRings, | |
| )} | |
| /> | |
| )} | |
| <AttachFile | |
| endpoint={_endpoint ?? ''} | |
| endpointType={endpointType} | |
| disabled={disableInputs} | |
| /> | |
| {(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? ( | |
| <StopButton stop={handleStopGenerating} setShowStopButton={setShowStopButton} /> | |
| ) : ( | |
| endpoint && ( | |
| <SendButton | |
| ref={submitButtonRef} | |
| control={methods.control} | |
| disabled={!!(filesLoading || isSubmitting || disableInputs)} | |
| /> | |
| ) | |
| )} | |
| {SpeechToText && ( | |
| <AudioRecorder | |
| disabled={!!disableInputs} | |
| textAreaRef={textAreaRef} | |
| ask={submitMessage} | |
| methods={methods} | |
| /> | |
| )} | |
| {TextToSpeech && automaticPlayback && <StreamAudio index={index} />} | |
| </div> | |
| </div> | |
| </div> | |
| </form> | |
| ); | |
| }; | |
| export default memo(ChatForm); | |