From 70e410f38bced020e30bbb1775a7db5259d693d4 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 7 Feb 2025 02:15:38 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=AC=20fix:=20Temporary=20Chat=20PR's?= =?UTF-8?q?=20broken=20components=20and=20improved=20UI=20(#5705)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 💬 fix: Temporary Chat PR's broken components and improved UI * 💬 fix: bring back hover effect on AudioRecorder button * style: adjust position of Mention component popover * refactor: PromptsCommand typing and style position * refactor: virtualize mention UI --------- Co-authored-by: Danny Avila --- .../components/Chat/Input/AudioRecorder.tsx | 8 +- client/src/components/Chat/Input/ChatForm.tsx | 20 ++-- client/src/components/Chat/Input/Mention.tsx | 73 ++++++++++----- .../src/components/Chat/Input/MentionItem.tsx | 14 ++- .../components/Chat/Input/PromptsCommand.tsx | 93 +++++++++++++------ .../components/Chat/Input/TemporaryChat.tsx | 38 ++++++++ .../Input/ModelSelect/TemporaryChat.tsx | 16 ++-- client/src/localization/languages/Eng.ts | 1 + client/src/store/temporary.ts | 7 +- package-lock.json | 11 +++ package.json | 1 + 11 files changed, 196 insertions(+), 86 deletions(-) create mode 100644 client/src/components/Chat/Input/TemporaryChat.tsx diff --git a/client/src/components/Chat/Input/AudioRecorder.tsx b/client/src/components/Chat/Input/AudioRecorder.tsx index a16d8870ae..96e29ec502 100644 --- a/client/src/components/Chat/Input/AudioRecorder.tsx +++ b/client/src/components/Chat/Input/AudioRecorder.tsx @@ -13,7 +13,6 @@ export default function AudioRecorder({ methods, textAreaRef, isSubmitting, - isTemporary = false, }: { isRTL: boolean; disabled: boolean; @@ -21,7 +20,6 @@ export default function AudioRecorder({ methods: ReturnType; textAreaRef: React.RefObject; isSubmitting: boolean; - isTemporary?: boolean; }) { const { setValue, reset } = methods; const localize = useLocalize(); @@ -78,11 +76,7 @@ export default function AudioRecorder({ if (isLoading === true) { return ; } - return ( - - ); + return ; }; return ( diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index acfef0ad5e..442d32a45e 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -24,6 +24,7 @@ import { cn, removeFocusRings, checkIfScrollable } from '~/utils'; import FileFormWrapper from './Files/FileFormWrapper'; import { TextareaAutosize } from '~/components/ui'; import { useGetFileConfig } from '~/data-provider'; +import { TemporaryChat } from './TemporaryChat'; import TextareaHeader from './TextareaHeader'; import PromptsCommand from './PromptsCommand'; import AudioRecorder from './AudioRecorder'; @@ -47,7 +48,7 @@ const ChatForm = ({ index = 0 }) => { const TextToSpeech = useRecoilValue(store.textToSpeech); const automaticPlayback = useRecoilValue(store.automaticPlayback); const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); - const isTemporary = useRecoilValue(store.isTemporary); + const [isTemporaryChat, setIsTemporaryChat] = useRecoilState(store.isTemporary); const isSearching = useRecoilValue(store.isSearching); const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); @@ -145,11 +146,8 @@ const ChatForm = ({ index = 0 }) => { const isUploadDisabled: boolean = endpointFileConfig?.disabled ?? false; const baseClasses = cn( - 'md:py-3.5 m-0 w-full resize-none bg-surface-tertiary py-[13px] placeholder-black/50 dark:placeholder-white/50 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]', + 'md:py-3.5 m-0 w-full resize-none py-[13px] bg-surface-tertiary placeholder-black/50 dark:placeholder-white/50 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]', isCollapsed ? 'max-h-[52px]' : 'max-h-[65vh] md:max-h-[75vh]', - isTemporary - ? 'bg-gray-600 text-white placeholder-white/20' - : 'bg-surface-tertiary placeholder-black/50 dark:placeholder-white/50', ); const uploadActive = endpointSupportsFiles && !isUploadDisabled; @@ -185,12 +183,11 @@ const ChatForm = ({ index = 0 }) => { /> )} -
+
+ {endpoint && ( @@ -243,7 +240,6 @@ const ChatForm = ({ index = 0 }) => { textAreaRef={textAreaRef} disabled={!!disableInputs} isSubmitting={isSubmitting} - isTemporary={isTemporary} /> )} {TextToSpeech && automaticPlayback && } diff --git a/client/src/components/Chat/Input/Mention.tsx b/client/src/components/Chat/Input/Mention.tsx index e268bba00f..c09c23b2bd 100644 --- a/client/src/components/Chat/Input/Mention.tsx +++ b/client/src/components/Chat/Input/Mention.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect } from 'react'; +import { AutoSizer, List } from 'react-virtualized'; import { EModelEndpoint } from 'librechat-data-provider'; import type { SetterOrUpdater } from 'recoil'; import type { MentionOption, ConvoGenerator } from '~/common'; @@ -9,6 +10,8 @@ import { useLocalize, useCombobox } from '~/hooks'; import { removeCharIfLast } from '~/utils'; import MentionItem from './MentionItem'; +const ROW_HEIGHT = 40; + export default function Mention({ setShowMentionPopover, newConversation, @@ -121,8 +124,41 @@ export default function Mention({ currentActiveItem?.scrollIntoView({ behavior: 'instant', block: 'nearest' }); }, [type, activeIndex]); + const rowRenderer = ({ + index, + key, + style, + }: { + index: number; + key: string; + style: React.CSSProperties; + }) => { + const mention = matches[index] as MentionOption; + return ( + { + e.preventDefault(); + e.stopPropagation(); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = null; + handleSelect(mention); + }} + name={mention.label ?? ''} + icon={mention.icon} + description={mention.description} + isActive={index === activeIndex} + /> + ); + }; + return ( -
+
{open && ( -
- {(matches as MentionOption[]).map((mention, index) => ( - { - e.preventDefault(); - e.stopPropagation(); - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = null; - handleSelect(mention); - }} - name={mention.label ?? ''} - icon={mention.icon} - description={mention.description} - isActive={index === activeIndex} - /> - ))} +
+ + {({ width }) => ( + + )} +
)}
diff --git a/client/src/components/Chat/Input/MentionItem.tsx b/client/src/components/Chat/Input/MentionItem.tsx index 7cdaa127c5..fcfb22c312 100644 --- a/client/src/components/Chat/Input/MentionItem.tsx +++ b/client/src/components/Chat/Input/MentionItem.tsx @@ -10,6 +10,7 @@ export interface MentionItemProps { icon?: React.ReactNode; isActive?: boolean; description?: string; + style?: React.CSSProperties; } export default function MentionItem({ @@ -19,21 +20,28 @@ export default function MentionItem({ icon, isActive, description, + style, type = 'mention', }: MentionItemProps) { return ( - +
+
+ ); +}; diff --git a/client/src/components/Input/ModelSelect/TemporaryChat.tsx b/client/src/components/Input/ModelSelect/TemporaryChat.tsx index d8b8308689..1f4168d953 100644 --- a/client/src/components/Input/ModelSelect/TemporaryChat.tsx +++ b/client/src/components/Input/ModelSelect/TemporaryChat.tsx @@ -4,15 +4,16 @@ import { MessageCircleDashed } from 'lucide-react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { Constants, getConfigDefaults } from 'librechat-data-provider'; import { useGetStartupConfig } from '~/data-provider'; -import temporaryStore from '~/store/temporary'; import { Switch } from '~/components/ui'; +import { useLocalize } from '~/hooks'; import { cn } from '~/utils'; import store from '~/store'; export const TemporaryChat = () => { + const localize = useLocalize(); const { data: startupConfig } = useGetStartupConfig(); const defaultInterface = getConfigDefaults().interface; - const [isTemporary, setIsTemporary] = useRecoilState(temporaryStore.isTemporary); + const [isTemporary, setIsTemporary] = useRecoilState(store.isTemporary); const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; const conversationId = conversation?.conversationId ?? ''; const interfaceConfig = useMemo( @@ -20,7 +21,7 @@ export const TemporaryChat = () => { [startupConfig], ); - if (!interfaceConfig.temporaryChat) { + if (interfaceConfig.temporaryChat === false) { return null; } @@ -39,20 +40,21 @@ export const TemporaryChat = () => { }; return ( -
+
- Temporary Chat + {localize('com_ui_temporary_chat')}
diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index 338609101a..358d8fb595 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -463,6 +463,7 @@ export default { com_ui_shared_link_delete_success: 'Successfully deleted shared link', com_ui_shared_link_bulk_delete_success: 'Successfully deleted shared links', com_ui_search: 'Search', + com_ui_temporary_chat: 'Temporary Chat', com_auth_error_login: 'Unable to login with the information provided. Please check your credentials and try again.', com_auth_error_login_rl: diff --git a/client/src/store/temporary.ts b/client/src/store/temporary.ts index b82bb789c3..cddea06155 100644 --- a/client/src/store/temporary.ts +++ b/client/src/store/temporary.ts @@ -1,9 +1,6 @@ -import { atom } from 'recoil'; +import { atomWithLocalStorage } from '~/store/utils'; -const isTemporary = atom({ - key: 'isTemporary', - default: false, -}); +const isTemporary = atomWithLocalStorage('isTemporary', false); export default { isTemporary, diff --git a/package-lock.json b/package-lock.json index f05d0d85b4..4d75979ea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "devDependencies": { "@axe-core/playwright": "^4.9.1", "@playwright/test": "^1.38.1", + "@types/react-virtualized": "^9.22.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "cross-env": "^7.0.3", @@ -15126,6 +15127,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-virtualized": { + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.22.0.tgz", + "integrity": "sha512-JL/YCCFZ123za//cj10Apk54F0UGFMrjOE0QHTuXt1KBMFrzLOGv9/x6Uc/pZ0Gaf4o6w61Fostvlw0DwuPXig==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", diff --git a/package.json b/package.json index 9e43048f91..02e05722d5 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "devDependencies": { "@axe-core/playwright": "^4.9.1", "@playwright/test": "^1.38.1", + "@types/react-virtualized": "^9.22.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "cross-env": "^7.0.3",