mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
fix: Ensure Message Send Requires Key 🔑 (#1281)
* fix: only allow message send when key is provided when required - create useRequiresKey hook - pass same disabled prop to Textarea, AttachFile, and SendButton - EndpointItem: add localization, stopPropagation, and remove commented code - separate some hooks to new Input dir - completely remove textareaHeight recoil state as is not needed - update imports for moved hooks - pass disabled prop to useTextarea * feat: add localization to textarea placeholders
This commit is contained in:
parent
f6118879e5
commit
00b6af8c74
14 changed files with 54 additions and 50 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useRequiresKey } from '~/hooks';
|
||||
import AttachFile from './Files/AttachFile';
|
||||
import StopButton from './StopButton';
|
||||
import SendButton from './SendButton';
|
||||
|
|
@ -28,6 +29,8 @@ export default function ChatForm({ index = 0 }) {
|
|||
setText('');
|
||||
};
|
||||
|
||||
const { requiresKey } = useRequiresKey();
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
|
|
@ -42,16 +45,17 @@ export default function ChatForm({ index = 0 }) {
|
|||
<Images files={files} setFiles={setFiles} setFilesLoading={setFilesLoading} />
|
||||
<Textarea
|
||||
value={text}
|
||||
disabled={requiresKey}
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setText(e.target.value)}
|
||||
setText={setText}
|
||||
submitMessage={submitMessage}
|
||||
endpoint={conversation?.endpoint}
|
||||
/>
|
||||
<AttachFile endpoint={conversation?.endpoint ?? ''} />
|
||||
<AttachFile endpoint={conversation?.endpoint ?? ''} disabled={requiresKey} />
|
||||
{isSubmitting && showStopButton ? (
|
||||
<StopButton stop={handleStopGenerating} setShowStopButton={setShowStopButton} />
|
||||
) : (
|
||||
<SendButton text={text} disabled={filesLoading || isSubmitting} />
|
||||
<SendButton text={text} disabled={filesLoading || isSubmitting || requiresKey} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ import { AttachmentIcon } from '~/components/svg';
|
|||
import { FileUpload } from '~/components/ui';
|
||||
import { useFileHandling } from '~/hooks';
|
||||
|
||||
export default function AttachFile({ endpoint }: { endpoint: EModelEndpoint | '' }) {
|
||||
export default function AttachFile({
|
||||
endpoint,
|
||||
disabled = false,
|
||||
}: {
|
||||
endpoint: EModelEndpoint | '';
|
||||
disabled?: boolean | null;
|
||||
}) {
|
||||
const { handleFileChange } = useFileHandling();
|
||||
if (!supportsFiles[endpoint]) {
|
||||
return null;
|
||||
|
|
@ -13,6 +19,7 @@ export default function AttachFile({ endpoint }: { endpoint: EModelEndpoint | ''
|
|||
<div className="absolute bottom-2 left-2 md:bottom-3 md:left-4">
|
||||
<FileUpload handleFileChange={handleFileChange} className="flex">
|
||||
<button
|
||||
disabled={!!disabled}
|
||||
type="button"
|
||||
className="btn relative p-0 text-black dark:text-white"
|
||||
aria-label="Attach files"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { supportsFiles } from 'librechat-data-provider';
|
|||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { useTextarea } from '~/hooks';
|
||||
|
||||
export default function Textarea({ value, onChange, setText, submitMessage, endpoint }) {
|
||||
export default function Textarea({ value, disabled, onChange, setText, submitMessage, endpoint }) {
|
||||
const {
|
||||
inputRef,
|
||||
handlePaste,
|
||||
|
|
@ -11,22 +11,21 @@ export default function Textarea({ value, onChange, setText, submitMessage, endp
|
|||
handleKeyDown,
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
onHeightChange,
|
||||
placeholder,
|
||||
} = useTextarea({ setText, submitMessage });
|
||||
} = useTextarea({ setText, submitMessage, disabled });
|
||||
|
||||
return (
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
autoFocus
|
||||
value={value}
|
||||
disabled={!!disabled}
|
||||
onChange={onChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onHeightChange={onHeightChange}
|
||||
id="prompt-textarea"
|
||||
tabIndex={0}
|
||||
data-testid="text-input"
|
||||
|
|
|
|||
|
|
@ -57,10 +57,6 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
{<Icon size={18} className="icon-md shrink-0 dark:text-white" />}
|
||||
{/* <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="icon-md shrink-0">
|
||||
<path d="M19.3975 1.35498C19.3746 1.15293 19.2037 1.00021 19.0004 1C18.7971 0.999793 18.6259 1.15217 18.6026 1.35417C18.4798 2.41894 18.1627 3.15692 17.6598 3.65983C17.1569 4.16274 16.4189 4.47983 15.3542 4.60264C15.1522 4.62593 14.9998 4.79707 15 5.00041C15.0002 5.20375 15.1529 5.37457 15.355 5.39746C16.4019 5.51605 17.1562 5.83304 17.6716 6.33906C18.1845 6.84269 18.5078 7.57998 18.6016 8.63539C18.6199 8.84195 18.7931 9.00023 19.0005 9C19.2078 8.99977 19.3806 8.84109 19.3985 8.6345C19.4883 7.59673 19.8114 6.84328 20.3273 6.32735C20.8433 5.81142 21.5967 5.48834 22.6345 5.39851C22.8411 5.38063 22.9998 5.20782 23 5.00045C23.0002 4.79308 22.842 4.61992 22.6354 4.60157C21.58 4.50782 20.8427 4.18447 20.3391 3.67157C19.833 3.15623 19.516 2.40192 19.3975 1.35498Z" fill="currentColor"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M11 3C11.4833 3 11.8974 3.34562 11.9839 3.82111C12.4637 6.46043 13.279 8.23983 14.5196 9.48039C15.7602 10.721 17.5396 11.5363 20.1789 12.0161C20.6544 12.1026 21 12.5167 21 13C21 13.4833 20.6544 13.8974 20.1789 13.9839C17.5396 14.4637 15.7602 15.279 14.5196 16.5196C13.279 17.7602 12.4637 19.5396 11.9839 22.1789C11.8974 22.6544 11.4833 23 11 23C10.5167 23 10.1026 22.6544 10.0161 22.1789C9.53625 19.5396 8.72096 17.7602 7.48039 16.5196C6.23983 15.279 4.46043 14.4637 1.82111 13.9839C1.34562 13.8974 1 13.4833 1 13C1 12.5167 1.34562 12.1026 1.82111 12.0161C4.46043 11.5363 6.23983 10.721 7.48039 9.48039C8.72096 8.23983 9.53625 6.46043 10.0161 3.82111C10.1026 3.34562 10.5167 3 11 3ZM5.66618 13C6.9247 13.5226 7.99788 14.2087 8.89461 15.1054C9.79134 16.0021 10.4774 17.0753 11 18.3338C11.5226 17.0753 12.2087 16.0021 13.1054 15.1054C14.0021 14.2087 15.0753 13.5226 16.3338 13C15.0753 12.4774 14.0021 11.7913 13.1054 10.8946C12.2087 9.99788 11.5226 8.9247 11 7.66618C10.4774 8.9247 9.79134 9.99788 8.89461 10.8946C7.99788 11.7913 6.9247 12.4774 5.66618 13Z" fill="currentColor"/>
|
||||
</svg> */}
|
||||
<div>
|
||||
{title}
|
||||
<div className="text-token-text-tertiary">{description}</div>
|
||||
|
|
@ -80,6 +76,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -109,7 +106,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
)}
|
||||
{(!userProvidesKey || expiryTime) && (
|
||||
<div className="text-token-text-primary hidden gap-x-1 group-hover:flex ">
|
||||
{!userProvidesKey && <div className="">New Chat</div>}
|
||||
{!userProvidesKey && <div className="">{localize('com_ui_new_chat')}</div>}
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
|
|
|
|||
4
client/src/hooks/Input/index.ts
Normal file
4
client/src/hooks/Input/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { default as useUserKey } from './useUserKey';
|
||||
export { default as useDebounce } from './useDebounce';
|
||||
export { default as useTextarea } from './useTextarea';
|
||||
export { default as useRequiresKey } from './useRequiresKey';
|
||||
14
client/src/hooks/Input/useRequiresKey.ts
Normal file
14
client/src/hooks/Input/useRequiresKey.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useGetEndpointsQuery } from 'librechat-data-provider';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import useUserKey from './useUserKey';
|
||||
|
||||
export default function useRequiresKey() {
|
||||
const { conversation } = useChatContext();
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const { endpoint } = conversation || {};
|
||||
const userProvidesKey = endpointsConfig?.[endpoint ?? '']?.userProvide;
|
||||
const { getExpiry } = useUserKey(endpoint ?? '');
|
||||
const expiryTime = getExpiry();
|
||||
const requiresKey = !expiryTime && userProvidesKey;
|
||||
return { requiresKey };
|
||||
}
|
||||
|
|
@ -2,23 +2,18 @@ import { useEffect, useRef } from 'react';
|
|||
import { TEndpointOption, getResponseSender } from 'librechat-data-provider';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import useFileHandling from './useFileHandling';
|
||||
import useFileHandling from '~/hooks/useFileHandling';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
|
||||
type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
|
||||
|
||||
export default function useTextarea({ setText, submitMessage }) {
|
||||
const {
|
||||
conversation,
|
||||
isSubmitting,
|
||||
latestMessage,
|
||||
setShowBingToneSetting,
|
||||
textareaHeight,
|
||||
setTextareaHeight,
|
||||
setFilesLoading,
|
||||
} = useChatContext();
|
||||
export default function useTextarea({ setText, submitMessage, disabled = false }) {
|
||||
const { conversation, isSubmitting, latestMessage, setShowBingToneSetting, setFilesLoading } =
|
||||
useChatContext();
|
||||
const isComposing = useRef(false);
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const { handleFiles } = useFileHandling();
|
||||
const localize = useLocalize();
|
||||
|
||||
const isNotAppendable = (latestMessage?.unfinished && !isSubmitting) || latestMessage?.error;
|
||||
const { conversationId, jailbreak } = conversation || {};
|
||||
|
|
@ -88,23 +83,16 @@ export default function useTextarea({ setText, submitMessage }) {
|
|||
};
|
||||
|
||||
const getPlaceholderText = () => {
|
||||
if (disabled) {
|
||||
return localize('com_endpoint_config_placeholder');
|
||||
}
|
||||
if (isNotAppendable) {
|
||||
return 'Edit your message or Regenerate.';
|
||||
return localize('com_endpoint_message_not_appendable');
|
||||
}
|
||||
|
||||
const sender = getResponseSender(conversation as TEndpointOption);
|
||||
|
||||
return `Message ${sender ? sender : 'ChatGPT'}…`;
|
||||
};
|
||||
|
||||
const onHeightChange = (height: number) => {
|
||||
if (height > 208 && textareaHeight < 208) {
|
||||
setTextareaHeight(Math.min(height, 208));
|
||||
} else if (height > 208) {
|
||||
return;
|
||||
} else {
|
||||
setTextareaHeight(height);
|
||||
}
|
||||
return `${localize('com_endpoint_message')} ${sender ? sender : 'ChatGPT'}…`;
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
|
|
@ -123,6 +111,5 @@ export default function useTextarea({ setText, submitMessage }) {
|
|||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
placeholder: getPlaceholderText(),
|
||||
onHeightChange,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export * from './Messages';
|
||||
export * from './Input';
|
||||
|
||||
export * from './AuthContext';
|
||||
export * from './ThemeContext';
|
||||
|
|
@ -7,10 +8,7 @@ export * from './ApiErrorBoundaryContext';
|
|||
export { default as useSSE } from './useSSE';
|
||||
export { default as useToast } from './useToast';
|
||||
export { default as useTimeout } from './useTimeout';
|
||||
export { default as useUserKey } from './useUserKey';
|
||||
export { default as useNewConvo } from './useNewConvo';
|
||||
export { default as useDebounce } from './useDebounce';
|
||||
export { default as useTextarea } from './useTextarea';
|
||||
export { default as useLocalize } from './useLocalize';
|
||||
export { default as useMediaQuery } from './useMediaQuery';
|
||||
export { default as useSetOptions } from './useSetOptions';
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import type {
|
|||
import type { TAskFunction } from '~/common';
|
||||
import useSetFilesToDelete from './useSetFilesToDelete';
|
||||
import { useAuthContext } from './AuthContext';
|
||||
import useUserKey from './Input/useUserKey';
|
||||
import useNewConvo from './useNewConvo';
|
||||
import useUserKey from './useUserKey';
|
||||
import store from '~/store';
|
||||
|
||||
// this to be set somewhere else
|
||||
|
|
@ -324,7 +324,6 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) {
|
|||
const [showPopover, setShowPopover] = useRecoilState(store.showPopoverFamily(index));
|
||||
const [abortScroll, setAbortScroll] = useRecoilState(store.abortScrollFamily(index));
|
||||
const [preset, setPreset] = useRecoilState(store.presetByIndex(index));
|
||||
const [textareaHeight, setTextareaHeight] = useRecoilState(store.textareaHeightFamily(index));
|
||||
const [optionSettings, setOptionSettings] = useRecoilState(store.optionSettingsFamily(index));
|
||||
const [showAgentSettings, setShowAgentSettings] = useRecoilState(
|
||||
store.showAgentSettingsFamily(index),
|
||||
|
|
@ -364,8 +363,6 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) {
|
|||
setOptionSettings,
|
||||
showAgentSettings,
|
||||
setShowAgentSettings,
|
||||
textareaHeight,
|
||||
setTextareaHeight,
|
||||
files,
|
||||
setFiles,
|
||||
invalidateConvos,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
|||
import { parseConvo, getResponseSender, useGetEndpointsQuery } from 'librechat-data-provider';
|
||||
import type { TMessage, TSubmission, TEndpointOption } from 'librechat-data-provider';
|
||||
import type { TAskFunction } from '~/common';
|
||||
import useUserKey from './useUserKey';
|
||||
import useUserKey from './Input/useUserKey';
|
||||
import store from '~/store';
|
||||
|
||||
const useMessageHandler = () => {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ export default {
|
|||
com_endpoint_bing_system_message_placeholder:
|
||||
'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.',
|
||||
com_endpoint_system_message: 'System Message',
|
||||
com_endpoint_message: 'Message',
|
||||
com_endpoint_message_not_appendable: 'Edit your message or Regenerate.',
|
||||
com_endpoint_default_blank: 'default: blank',
|
||||
com_endpoint_default_false: 'default: false',
|
||||
com_endpoint_default_creative: 'default: creative',
|
||||
|
|
@ -205,6 +207,7 @@ export default {
|
|||
com_endpoint_skip_hover:
|
||||
'Enable skipping the completion step, which reviews the final answer and generated steps',
|
||||
com_endpoint_config_key: 'Set API Key',
|
||||
com_endpoint_config_placeholder: 'Set your Key in the Header menu to chat.',
|
||||
com_endpoint_config_key_for: 'Set API Key for',
|
||||
com_endpoint_config_key_name: 'Key',
|
||||
com_endpoint_config_value: 'Enter value for',
|
||||
|
|
|
|||
|
|
@ -83,11 +83,6 @@ const latestMessageFamily = atomFamily<TMessage | null, string | number | null>(
|
|||
default: null,
|
||||
});
|
||||
|
||||
const textareaHeightFamily = atomFamily<number, string | number>({
|
||||
key: 'textareaHeightByIndex',
|
||||
default: 56,
|
||||
});
|
||||
|
||||
function useCreateConversationAtom(key: string | number) {
|
||||
const [keys, setKeys] = useRecoilState(conversationKeysAtom);
|
||||
const setConversation = useSetRecoilState(conversationByIndex(key));
|
||||
|
|
@ -115,7 +110,6 @@ export default {
|
|||
showBingToneSettingFamily,
|
||||
showPopoverFamily,
|
||||
latestMessageFamily,
|
||||
textareaHeightFamily,
|
||||
allConversationsSelector,
|
||||
useCreateConversationAtom,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue