| import { useCallback, useMemo } from 'react'; |
| import { useSetRecoilState, useRecoilValue } from 'recoil'; |
| import { PermissionTypes, Permissions } from 'librechat-data-provider'; |
| import type { SetterOrUpdater } from 'recoil'; |
| import useHasAccess from '~/hooks/Roles/useHasAccess'; |
| import store from '~/store'; |
|
|
| |
| const invalidKeys = { |
| Escape: true, |
| Backspace: true, |
| Enter: true, |
| }; |
|
|
| |
| |
| |
| const shouldTriggerCommand = ( |
| textAreaRef: React.RefObject<HTMLTextAreaElement>, |
| commandChar: string, |
| ) => { |
| const text = textAreaRef.current?.value; |
| if (typeof text !== 'string' || text.length === 0 || text[0] !== commandChar) { |
| return false; |
| } |
|
|
| const startPos = textAreaRef.current?.selectionStart; |
| if (typeof startPos !== 'number') { |
| return false; |
| } |
|
|
| return startPos === 1; |
| }; |
|
|
| |
| |
| |
| const useHandleKeyUp = ({ |
| index, |
| textAreaRef, |
| setShowPlusPopover, |
| setShowMentionPopover, |
| }: { |
| index: number; |
| textAreaRef: React.RefObject<HTMLTextAreaElement>; |
| setShowPlusPopover: SetterOrUpdater<boolean>; |
| setShowMentionPopover: SetterOrUpdater<boolean>; |
| }) => { |
| const hasPromptsAccess = useHasAccess({ |
| permissionType: PermissionTypes.PROMPTS, |
| permission: Permissions.USE, |
| }); |
| const hasMultiConvoAccess = useHasAccess({ |
| permissionType: PermissionTypes.MULTI_CONVO, |
| permission: Permissions.USE, |
| }); |
| const latestMessage = useRecoilValue(store.latestMessageFamily(index)); |
| const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index)); |
|
|
| |
| const atCommandEnabled = useRecoilValue(store.atCommand); |
| const plusCommandEnabled = useRecoilValue(store.plusCommand); |
| const slashCommandEnabled = useRecoilValue(store.slashCommand); |
|
|
| const handleAtCommand = useCallback(() => { |
| if (atCommandEnabled && shouldTriggerCommand(textAreaRef, '@')) { |
| setShowMentionPopover(true); |
| } |
| }, [textAreaRef, setShowMentionPopover, atCommandEnabled]); |
|
|
| const handlePlusCommand = useCallback(() => { |
| if (!hasMultiConvoAccess || !plusCommandEnabled) { |
| return; |
| } |
| if (shouldTriggerCommand(textAreaRef, '+')) { |
| setShowPlusPopover(true); |
| } |
| }, [textAreaRef, setShowPlusPopover, plusCommandEnabled, hasMultiConvoAccess]); |
|
|
| const handlePromptsCommand = useCallback(() => { |
| if (!hasPromptsAccess || !slashCommandEnabled) { |
| return; |
| } |
| if (shouldTriggerCommand(textAreaRef, '/')) { |
| setShowPromptsPopover(true); |
| } |
| }, [textAreaRef, hasPromptsAccess, setShowPromptsPopover, slashCommandEnabled]); |
|
|
| const commandHandlers = useMemo( |
| () => ({ |
| '@': handleAtCommand, |
| '+': handlePlusCommand, |
| '/': handlePromptsCommand, |
| }), |
| [handleAtCommand, handlePlusCommand, handlePromptsCommand], |
| ); |
|
|
| const handleUpArrow = useCallback( |
| (event: React.KeyboardEvent<HTMLTextAreaElement>) => { |
| if (!latestMessage) { |
| return; |
| } |
|
|
| const element = document.getElementById(`edit-${latestMessage.parentMessageId}`); |
| if (!element) { |
| return; |
| } |
| event.preventDefault(); |
| element.click(); |
| }, |
| [latestMessage], |
| ); |
|
|
| |
| |
| |
| const handleKeyUp = useCallback( |
| (event: React.KeyboardEvent<HTMLTextAreaElement>) => { |
| const text = textAreaRef.current?.value; |
| if (event.key === 'ArrowUp' && text?.length === 0) { |
| handleUpArrow(event); |
| return; |
| } |
| if (typeof text !== 'string' || text.length === 0) { |
| return; |
| } |
|
|
| if (invalidKeys[event.key as keyof typeof invalidKeys]) { |
| return; |
| } |
|
|
| const firstChar = text[0]; |
| const handler = commandHandlers[firstChar as keyof typeof commandHandlers]; |
|
|
| if (typeof handler === 'function') { |
| handler(); |
| } |
| }, |
| [textAreaRef, commandHandlers, handleUpArrow], |
| ); |
|
|
| return handleKeyUp; |
| }; |
|
|
| export default useHandleKeyUp; |
|
|