mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
🛠️ fix: Merge Textarea Ref with Form for Simplified Handling (#2456)
* share ref correctly * chore: remove extraneous textarea handlers and add excel text data
This commit is contained in:
parent
26ea990045
commit
692ce3b346
2 changed files with 33 additions and 75 deletions
|
|
@ -29,14 +29,11 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
defaultValues: { text: '' },
|
defaultValues: { text: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { handlePaste, handleKeyUp, handleKeyDown, handleCompositionStart, handleCompositionEnd } =
|
const { handlePaste, handleKeyDown, handleCompositionStart, handleCompositionEnd } = useTextarea({
|
||||||
useTextarea({
|
textAreaRef,
|
||||||
textAreaRef,
|
submitButtonRef,
|
||||||
submitButtonRef,
|
disabled: !!requiresKey,
|
||||||
disabled: !!requiresKey,
|
});
|
||||||
setValue: methods.setValue,
|
|
||||||
getValues: methods.getValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ask,
|
ask,
|
||||||
|
|
@ -58,9 +55,6 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
}
|
}
|
||||||
ask({ text: data.text });
|
ask({ text: data.text });
|
||||||
methods.reset();
|
methods.reset();
|
||||||
if (textAreaRef.current) {
|
|
||||||
textAreaRef.current.value = '';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[ask, methods],
|
[ask, methods],
|
||||||
);
|
);
|
||||||
|
|
@ -84,6 +78,13 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
[requiresKey, invalidAssistant],
|
[requiresKey, invalidAssistant],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { ref, ...registerProps } = methods.register('text', {
|
||||||
|
required: true,
|
||||||
|
onChange: (e) => {
|
||||||
|
methods.setValue('text', e.target.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={methods.handleSubmit((data) => submitMessage(data))}
|
onSubmit={methods.handleSubmit((data) => submitMessage(data))}
|
||||||
|
|
@ -104,19 +105,14 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
/>
|
/>
|
||||||
{endpoint && (
|
{endpoint && (
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
{...methods.register('text', {
|
{...registerProps}
|
||||||
required: true,
|
|
||||||
onChange: (e) => {
|
|
||||||
methods.setValue('text', e.target.value);
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
ref={(e) => {
|
ref={(e) => {
|
||||||
|
ref(e);
|
||||||
textAreaRef.current = e;
|
textAreaRef.current = e;
|
||||||
}}
|
}}
|
||||||
disabled={disableInputs}
|
disabled={disableInputs}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onCompositionStart={handleCompositionStart}
|
onCompositionStart={handleCompositionStart}
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ import { useRecoilValue } from 'recoil';
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import React, { useEffect, useRef, useCallback } from 'react';
|
import React, { useEffect, useRef, useCallback } from 'react';
|
||||||
import type { TEndpointOption } from 'librechat-data-provider';
|
import type { TEndpointOption } from 'librechat-data-provider';
|
||||||
import type { UseFormSetValue } from 'react-hook-form';
|
|
||||||
import type { KeyboardEvent } from 'react';
|
import type { KeyboardEvent } from 'react';
|
||||||
import { forceResize, insertTextAtCursor, trimUndoneRange, getAssistantName } from '~/utils';
|
import { forceResize, insertTextAtCursor, getAssistantName } from '~/utils';
|
||||||
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
|
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
|
||||||
import useGetSender from '~/hooks/Conversations/useGetSender';
|
import useGetSender from '~/hooks/Conversations/useGetSender';
|
||||||
import useFileHandling from '~/hooks/Files/useFileHandling';
|
import useFileHandling from '~/hooks/Files/useFileHandling';
|
||||||
|
|
@ -18,14 +17,10 @@ type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
|
||||||
export default function useTextarea({
|
export default function useTextarea({
|
||||||
textAreaRef,
|
textAreaRef,
|
||||||
submitButtonRef,
|
submitButtonRef,
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: {
|
}: {
|
||||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
submitButtonRef: React.RefObject<HTMLButtonElement>;
|
submitButtonRef: React.RefObject<HTMLButtonElement>;
|
||||||
setValue: UseFormSetValue<{ text: string }>;
|
|
||||||
getValues: (field: string) => string;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const assistantMap = useAssistantsMapContext();
|
const assistantMap = useAssistantsMapContext();
|
||||||
|
|
@ -166,33 +161,6 @@ export default function useTextarea({
|
||||||
[isSubmitting, filesLoading, enterToSend, textAreaRef, submitButtonRef],
|
[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 = () => {
|
const handleCompositionStart = () => {
|
||||||
isComposing.current = true;
|
isComposing.current = true;
|
||||||
};
|
};
|
||||||
|
|
@ -201,36 +169,31 @@ export default function useTextarea({
|
||||||
isComposing.current = false;
|
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(
|
const handlePaste = useCallback(
|
||||||
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
e.preventDefault();
|
|
||||||
const textArea = textAreaRef.current;
|
const textArea = textAreaRef.current;
|
||||||
if (!textArea) {
|
if (!textArea) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pastedData = e.clipboardData.getData('text/plain');
|
if (!e.clipboardData) {
|
||||||
setPastedValue(textArea, pastedData);
|
return;
|
||||||
insertTextAtCursor(textArea, pastedData);
|
}
|
||||||
forceResize(textAreaRef);
|
|
||||||
|
|
||||||
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();
|
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);
|
setFilesLoading(true);
|
||||||
const timestampedFiles: File[] = [];
|
const timestampedFiles: File[] = [];
|
||||||
for (const file of e.clipboardData.files) {
|
for (const file of e.clipboardData.files) {
|
||||||
|
|
@ -242,14 +205,13 @@ export default function useTextarea({
|
||||||
handleFiles(timestampedFiles);
|
handleFiles(timestampedFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleFiles, setFilesLoading, setPastedValue, textAreaRef],
|
[handleFiles, setFilesLoading, textAreaRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
textAreaRef,
|
textAreaRef,
|
||||||
handleKeyDown,
|
|
||||||
handleKeyUp,
|
|
||||||
handlePaste,
|
handlePaste,
|
||||||
|
handleKeyDown,
|
||||||
handleCompositionStart,
|
handleCompositionStart,
|
||||||
handleCompositionEnd,
|
handleCompositionEnd,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue