mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10: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
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';
|
||||
19
client/src/hooks/Input/useDebounce.ts
Normal file
19
client/src/hooks/Input/useDebounce.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
function useDebounce(value: string, delay: number) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
export default useDebounce;
|
||||
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 };
|
||||
}
|
||||
115
client/src/hooks/Input/useTextarea.ts
Normal file
115
client/src/hooks/Input/useTextarea.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
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 '~/hooks/useFileHandling';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
|
||||
type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
|
||||
|
||||
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 || {};
|
||||
|
||||
// auto focus to input, when enter a conversation.
|
||||
useEffect(() => {
|
||||
if (!conversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevents Settings from not showing on new conversation, also prevents showing toneStyle change without jailbreak
|
||||
if (conversationId === 'new' || !jailbreak) {
|
||||
setShowBingToneSetting(false);
|
||||
}
|
||||
|
||||
if (conversationId !== 'search') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [conversationId, jailbreak]);
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const handleKeyDown = (e: KeyEvent) => {
|
||||
if (e.key === 'Enter' && isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isComposing?.current) {
|
||||
submitMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: KeyEvent) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
|
||||
if (e.keyCode === 8 && target.value.trim() === '') {
|
||||
setText(target.value);
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.shiftKey) {
|
||||
return console.log('Enter + Shift');
|
||||
}
|
||||
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompositionStart = () => {
|
||||
isComposing.current = true;
|
||||
};
|
||||
|
||||
const handleCompositionEnd = () => {
|
||||
isComposing.current = false;
|
||||
};
|
||||
|
||||
const getPlaceholderText = () => {
|
||||
if (disabled) {
|
||||
return localize('com_endpoint_config_placeholder');
|
||||
}
|
||||
if (isNotAppendable) {
|
||||
return localize('com_endpoint_message_not_appendable');
|
||||
}
|
||||
|
||||
const sender = getResponseSender(conversation as TEndpointOption);
|
||||
|
||||
return `${localize('com_endpoint_message')} ${sender ? sender : 'ChatGPT'}…`;
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
||||
e.preventDefault();
|
||||
setFilesLoading(true);
|
||||
handleFiles(e.clipboardData.files);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
inputRef,
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handlePaste,
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
placeholder: getPlaceholderText(),
|
||||
};
|
||||
}
|
||||
60
client/src/hooks/Input/useUserKey.ts
Normal file
60
client/src/hooks/Input/useUserKey.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { useMemo, useCallback } from 'react';
|
||||
import {
|
||||
useUpdateUserKeysMutation,
|
||||
useUserKeyQuery,
|
||||
useGetEndpointsQuery,
|
||||
} from 'librechat-data-provider';
|
||||
|
||||
const useUserKey = (endpoint: string) => {
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const config = endpointsConfig?.[endpoint];
|
||||
|
||||
const { azure } = config ?? {};
|
||||
let keyEndpoint = endpoint;
|
||||
|
||||
if (azure) {
|
||||
keyEndpoint = 'azureOpenAI';
|
||||
} else if (keyEndpoint === 'gptPlugins') {
|
||||
keyEndpoint = 'openAI';
|
||||
}
|
||||
|
||||
const updateKey = useUpdateUserKeysMutation();
|
||||
const checkUserKey = useUserKeyQuery(keyEndpoint);
|
||||
const getExpiry = useCallback(() => {
|
||||
if (checkUserKey.data) {
|
||||
return checkUserKey.data.expiresAt;
|
||||
}
|
||||
}, [checkUserKey.data]);
|
||||
|
||||
const checkExpiry = useCallback(() => {
|
||||
const expiresAt = getExpiry();
|
||||
if (!expiresAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expiresAtDate = new Date(expiresAt);
|
||||
if (expiresAtDate < new Date()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, [getExpiry]);
|
||||
|
||||
const saveUserKey = useCallback(
|
||||
(value: string, expiresAt: number) => {
|
||||
const dateStr = new Date(expiresAt).toISOString();
|
||||
updateKey.mutate({
|
||||
name: keyEndpoint,
|
||||
value,
|
||||
expiresAt: dateStr,
|
||||
});
|
||||
},
|
||||
[updateKey, keyEndpoint],
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({ getExpiry, checkExpiry, saveUserKey }),
|
||||
[getExpiry, checkExpiry, saveUserKey],
|
||||
);
|
||||
};
|
||||
|
||||
export default useUserKey;
|
||||
Loading…
Add table
Add a link
Reference in a new issue