From f307488dd47534615e9db79f7853c993f665cdf1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 11 Mar 2024 09:18:10 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=8D=EF=B8=8F=20refactor(Textarea):=20Opti?= =?UTF-8?q?mize=20Text=20Input=20&=20Enhance=20UX=20(#2058)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(useDebouncedInput): make object as input arg and accept setter * refactor(ChatForm/Textarea): consolidate textarea/form logic to one component, use react-hook-form, programmatically click send button instead of passing submitMessage, forwardRef and memoize SendButton * refactor(Textarea): use Controller field value to avoid manual update of ref * chore: remove forms provider * chore: memoize AttachFile * refactor(ChatForm/SendButton): only re-render SendButton when there is text input * chore: make iconURL bigger * chore: optimize Root/Nav * refactor(SendButton): memoize disabled prop based on text * chore: memoize Nav and ChatForm * chore: remove textarea ref text on submission * feat(EditMessage): Make Esc exit the edit mode and dismiss changes when editing a message * style(MenuItem): Display the ☑️ icon only on the selected model --- client/src/components/Chat/Input/ChatForm.tsx | 102 +++++++++++++----- .../Chat/Input/Files/AttachFile.tsx | 9 +- .../src/components/Chat/Input/SendButton.tsx | 75 ++++++++----- client/src/components/Chat/Input/Textarea.tsx | 59 ---------- .../Chat/Menus/Presets/EditPresetDialog.tsx | 6 +- .../Chat/Menus/Presets/PresetItems.tsx | 1 - .../src/components/Chat/Menus/UI/MenuItem.tsx | 45 -------- .../Chat/Messages/Content/EditMessage.tsx | 13 ++- client/src/components/Endpoints/Icon.tsx | 2 +- .../Endpoints/Settings/Assistants.tsx | 16 +-- client/src/components/Nav/MobileNav.tsx | 7 +- client/src/components/Nav/Nav.tsx | 13 ++- client/src/components/Nav/NewChat.tsx | 15 +-- .../hooks/Conversations/useDebouncedInput.ts | 42 +++++--- client/src/hooks/Input/useTextarea.ts | 60 +++++++---- client/src/routes/Root.tsx | 4 - 16 files changed, 244 insertions(+), 225 deletions(-) delete mode 100644 client/src/components/Chat/Input/Textarea.tsx diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 3c7ba42457..69a62b7846 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -1,17 +1,30 @@ import { useRecoilState } from 'recoil'; -import type { ChangeEvent } from 'react'; +import { memo, useCallback, useRef } from 'react'; +import TextareaAutosize from 'react-textarea-autosize'; +import { useForm } from 'react-hook-form'; +import { + supportsFiles, + mergeFileConfig, + fileConfig as defaultFileConfig, +} from 'librechat-data-provider'; +import { useRequiresKey, useTextarea } from '~/hooks'; +import { useGetFileConfig } from '~/data-provider'; +import { cn, removeFocusOutlines } from '~/utils'; import { useChatContext } from '~/Providers'; -import { useRequiresKey } from '~/hooks'; import AttachFile from './Files/AttachFile'; import StopButton from './StopButton'; import SendButton from './SendButton'; import FileRow from './Files/FileRow'; -import Textarea from './Textarea'; import store from '~/store'; -export default function ChatForm({ index = 0 }) { - const [text, setText] = useRecoilState(store.textByIndex(index)); +const ChatForm = ({ index = 0 }) => { + const submitButtonRef = useRef(null); + const textAreaRef = useRef(null); const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); + const { requiresKey } = useRequiresKey(); + + const { handlePaste, handleKeyUp, handleKeyDown, handleCompositionStart, handleCompositionEnd } = + useTextarea({ textAreaRef, submitButtonRef, disabled: !!requiresKey }); const { ask, @@ -24,21 +37,34 @@ export default function ChatForm({ index = 0 }) { setFilesLoading, } = useChatContext(); - const submitMessage = () => { - ask({ text }); - setText(''); - }; + const methods = useForm<{ text: string }>({ + defaultValues: { text: '' }, + }); + + const submitMessage = useCallback( + (data?: { text: string }) => { + if (!data) { + return console.warn('No data provided to submitMessage'); + } + ask({ text: data.text }); + methods.reset(); + textAreaRef.current?.setRangeText('', 0, data.text.length, 'end'); + }, + [ask, methods], + ); - const { requiresKey } = useRequiresKey(); 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 ?? '']; + return (
{ - e.preventDefault(); - submitMessage(); - }} + 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" >
@@ -55,14 +81,36 @@ export default function ChatForm({ index = 0 }) { )} /> {endpoint && ( -