diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 51e9cb5c28..69e4819a99 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -29,14 +29,11 @@ const ChatForm = ({ index = 0 }) => { defaultValues: { text: '' }, }); - const { handlePaste, handleKeyUp, handleKeyDown, handleCompositionStart, handleCompositionEnd } = - useTextarea({ - textAreaRef, - submitButtonRef, - disabled: !!requiresKey, - setValue: methods.setValue, - getValues: methods.getValues, - }); + const { handlePaste, handleKeyDown, handleCompositionStart, handleCompositionEnd } = useTextarea({ + textAreaRef, + submitButtonRef, + disabled: !!requiresKey, + }); const { ask, @@ -58,9 +55,6 @@ const ChatForm = ({ index = 0 }) => { } ask({ text: data.text }); methods.reset(); - if (textAreaRef.current) { - textAreaRef.current.value = ''; - } }, [ask, methods], ); @@ -84,6 +78,13 @@ const ChatForm = ({ index = 0 }) => { [requiresKey, invalidAssistant], ); + const { ref, ...registerProps } = methods.register('text', { + required: true, + onChange: (e) => { + methods.setValue('text', e.target.value); + }, + }); + return (
submitMessage(data))} @@ -104,19 +105,14 @@ const ChatForm = ({ index = 0 }) => { /> {endpoint && ( { - methods.setValue('text', e.target.value); - }, - })} + {...registerProps} autoFocus ref={(e) => { + ref(e); textAreaRef.current = e; }} disabled={disableInputs} onPaste={handlePaste} - onKeyUp={handleKeyUp} onKeyDown={handleKeyDown} onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} diff --git a/client/src/hooks/Input/useTextarea.ts b/client/src/hooks/Input/useTextarea.ts index 58dd01ad42..4f8e6f7a93 100644 --- a/client/src/hooks/Input/useTextarea.ts +++ b/client/src/hooks/Input/useTextarea.ts @@ -3,9 +3,8 @@ import { useRecoilValue } from 'recoil'; import { EModelEndpoint } from 'librechat-data-provider'; import React, { useEffect, useRef, useCallback } from 'react'; import type { TEndpointOption } from 'librechat-data-provider'; -import type { UseFormSetValue } from 'react-hook-form'; import type { KeyboardEvent } from 'react'; -import { forceResize, insertTextAtCursor, trimUndoneRange, getAssistantName } from '~/utils'; +import { forceResize, insertTextAtCursor, getAssistantName } from '~/utils'; import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext'; import useGetSender from '~/hooks/Conversations/useGetSender'; import useFileHandling from '~/hooks/Files/useFileHandling'; @@ -18,14 +17,10 @@ type KeyEvent = KeyboardEvent; export default function useTextarea({ textAreaRef, submitButtonRef, - setValue, - getValues, disabled = false, }: { textAreaRef: React.RefObject; submitButtonRef: React.RefObject; - setValue: UseFormSetValue<{ text: string }>; - getValues: (field: string) => string; disabled?: boolean; }) { const assistantMap = useAssistantsMapContext(); @@ -166,33 +161,6 @@ export default function useTextarea({ [isSubmitting, filesLoading, enterToSend, textAreaRef, submitButtonRef], ); - const handleKeyUp = (e: KeyEvent) => { - const target = e.target as HTMLTextAreaElement; - - const isUndo = e.key === 'z' && (e.ctrlKey || e.metaKey); - if (isUndo && target.value.trim() === '') { - textAreaRef.current?.setRangeText('', 0, textAreaRef.current?.value?.length, 'end'); - setValue('text', '', { shouldValidate: true }); - forceResize(textAreaRef); - } else if (isUndo) { - trimUndoneRange(textAreaRef); - setValue('text', '', { shouldValidate: true }); - forceResize(textAreaRef); - } - - if ((e.keyCode === 8 || e.key === 'Backspace') && target.value.trim() === '') { - textAreaRef.current?.setRangeText('', 0, textAreaRef.current?.value?.length, 'end'); - } - - if (e.key === 'Enter' && e.shiftKey) { - return console.log('Enter + Shift'); - } - - if (isSubmitting) { - return; - } - }; - const handleCompositionStart = () => { isComposing.current = true; }; @@ -201,36 +169,31 @@ export default function useTextarea({ isComposing.current = false; }; - /** Necessary handler to update form state when paste doesn't fire textArea input event */ - const setPastedValue = useCallback( - (textArea: HTMLTextAreaElement, pastedData: string) => { - const currentTextValue = getValues('text') || ''; - const { selectionStart, selectionEnd } = textArea; - const newValue = - currentTextValue.substring(0, selectionStart) + - pastedData + - currentTextValue.substring(selectionEnd); - - setValue('text', newValue, { shouldValidate: true }); - }, - [getValues, setValue], - ); - const handlePaste = useCallback( (e: React.ClipboardEvent) => { - e.preventDefault(); const textArea = textAreaRef.current; if (!textArea) { return; } - const pastedData = e.clipboardData.getData('text/plain'); - setPastedValue(textArea, pastedData); - insertTextAtCursor(textArea, pastedData); - forceResize(textAreaRef); + if (!e.clipboardData) { + return; + } - if (e.clipboardData && e.clipboardData.files.length > 0) { + let includedText = ''; + const { types } = e.clipboardData; + + if (types.indexOf('text/rtf') !== -1 || types.indexOf('Files') !== -1) { e.preventDefault(); + includedText = e.clipboardData.getData('text/plain'); + } + + if (includedText && e.clipboardData.files.length > 0) { + insertTextAtCursor(textAreaRef.current, includedText); + forceResize(textAreaRef); + } + + if (e.clipboardData.files.length > 0) { setFilesLoading(true); const timestampedFiles: File[] = []; for (const file of e.clipboardData.files) { @@ -242,14 +205,13 @@ export default function useTextarea({ handleFiles(timestampedFiles); } }, - [handleFiles, setFilesLoading, setPastedValue, textAreaRef], + [handleFiles, setFilesLoading, textAreaRef], ); return { textAreaRef, - handleKeyDown, - handleKeyUp, handlePaste, + handleKeyDown, handleCompositionStart, handleCompositionEnd, };