diff --git a/client/src/components/Bookmarks/BookmarkEditDialog.tsx b/client/src/components/Bookmarks/BookmarkEditDialog.tsx index 2764cf4b85..b166b92c24 100644 --- a/client/src/components/Bookmarks/BookmarkEditDialog.tsx +++ b/client/src/components/Bookmarks/BookmarkEditDialog.tsx @@ -2,10 +2,9 @@ import React, { useRef, Dispatch, SetStateAction } from 'react'; import { TConversationTag, TConversation } from 'librechat-data-provider'; import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; import { useConversationTagMutation } from '~/data-provider'; +import { OGDialog, Button, Spinner } from '~/components'; import { NotificationSeverity } from '~/common'; import { useToastContext } from '~/Providers'; -import { OGDialog } from '~/components/ui'; -import { Spinner } from '~/components/svg'; import BookmarkForm from './BookmarkForm'; import { useLocalize } from '~/hooks'; import { logger } from '~/utils'; @@ -75,6 +74,7 @@ const BookmarkEditDialog = ({ } buttons={ - + } /> diff --git a/client/src/components/Bookmarks/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm.tsx index ea35b423ca..85018d3cfb 100644 --- a/client/src/components/Bookmarks/BookmarkForm.tsx +++ b/client/src/components/Bookmarks/BookmarkForm.tsx @@ -8,7 +8,7 @@ import type { TConversationTagRequest, } from 'librechat-data-provider'; import { cn, removeFocusOutlines, defaultTextProps, logger } from '~/utils'; -import { Checkbox, Label, TextareaAutosize } from '~/components/ui'; +import { Checkbox, Label, TextareaAutosize, Input } from '~/components'; import { useBookmarkContext } from '~/Providers/BookmarkContext'; import { useConversationTagMutation } from '~/data-provider'; import { useToastContext } from '~/Providers'; @@ -100,7 +100,7 @@ const BookmarkForm = ({ - {errors.tag && {errors.tag.message}} -
+
@@ -143,8 +138,7 @@ const BookmarkForm = ({ id="bookmark-description" disabled={false} className={cn( - defaultTextProps, - 'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2', + 'flex h-10 max-h-[250px] min-h-[100px] w-full resize-none rounded-lg border border-input bg-transparent px-3 py-2 text-sm ring-offset-background focus-visible:outline-none', )} />
diff --git a/client/src/components/Chat/Input/AudioRecorder.tsx b/client/src/components/Chat/Input/AudioRecorder.tsx index 1c950b5180..a8754749ad 100644 --- a/client/src/components/Chat/Input/AudioRecorder.tsx +++ b/client/src/components/Chat/Input/AudioRecorder.tsx @@ -80,10 +80,8 @@ export default function AudioRecorder({ onClick={isListening ? handleStopRecording : handleStartRecording} disabled={disabled} className={cn( - 'absolute flex h-[30px] w-[30px] items-center justify-center rounded-lg p-0.5 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700', - isRTL - ? 'bottom-1.5 left-4 md:bottom-3 md:left-12' - : 'bottom-1.5 right-12 md:bottom-3 md:right-12', + 'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover', + isRTL ? 'bottom-2 left-2' : 'bottom-2 right-2', )} description={localize('com_ui_use_micrphone')} > diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index fafa17c34d..a4a0ca61af 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -156,7 +156,7 @@ const ChatForm = ({ index = 0 }) => { /> )} -
+
{endpoint && ( @@ -181,7 +181,7 @@ const ChatForm = ({ index = 0 }) => { endpointSupportsFiles && !isUploadDisabled ? 'pl-10 md:pl-[55px]' : 'pl-3 md:pl-4', - 'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ', + '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)]', SpeechToText && !isRTL ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12', 'max-h-[65vh] md:max-h-[75vh]', removeFocusRings, @@ -189,22 +189,6 @@ const ChatForm = ({ index = 0 }) => { /> )} - {(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? ( - - ) : ( - endpoint && ( - - ) - )} {SpeechToText && ( { )} {TextToSpeech && automaticPlayback && }
+
+ {(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? ( + + ) : ( + endpoint && ( + + ) + )} +
diff --git a/client/src/components/Chat/Input/Files/AttachFile.tsx b/client/src/components/Chat/Input/Files/AttachFile.tsx index 1344c2ba34..e02aeae092 100644 --- a/client/src/components/Chat/Input/Files/AttachFile.tsx +++ b/client/src/components/Chat/Input/Files/AttachFile.tsx @@ -17,29 +17,22 @@ const AttachFile = ({ const isUploadDisabled = disabled ?? false; return ( -
- - -
- -
-
-
-
+ + +
+ +
+
+
); }; diff --git a/client/src/components/Chat/Input/Files/FileFormWrapper.tsx b/client/src/components/Chat/Input/Files/FileFormWrapper.tsx index 81fd9f0258..230b303615 100644 --- a/client/src/components/Chat/Input/Files/FileFormWrapper.tsx +++ b/client/src/components/Chat/Input/Files/FileFormWrapper.tsx @@ -13,19 +13,17 @@ import AttachFile from './AttachFile'; import FileRow from './FileRow'; import store from '~/store'; -function FileFormWrapper({ children, disableInputs } : { +function FileFormWrapper({ + children, + disableInputs, +}: { disableInputs: boolean; children?: React.ReactNode; }) { const { handleFileChange, abortUpload } = useFileHandling(); const chatDirection = useRecoilValue(store.chatDirection).toLowerCase(); - const { - files, - setFiles, - conversation, - setFilesLoading, - } = useChatContext(); + const { files, setFiles, conversation, setFilesLoading } = useChatContext(); const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ select: (data) => mergeFileConfig(data), }); @@ -33,30 +31,30 @@ function FileFormWrapper({ children, disableInputs } : { const isRTL = chatDirection === 'rtl'; const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null }; - const endpointFileConfig = fileConfig.endpoints[_endpoint ?? ''] as EndpointFileConfig | undefined; + const endpointFileConfig = fileConfig.endpoints[_endpoint ?? ''] as + | EndpointFileConfig + | undefined; const endpointSupportsFiles: boolean = supportsFiles[endpointType ?? _endpoint ?? ''] ?? false; const isUploadDisabled = (disableInputs || endpointFileConfig?.disabled) ?? false; - return (<> - ( -
- {children} -
+ return ( + <> + ( +
{children}
+ )} + /> + {children} + {endpointSupportsFiles && !isUploadDisabled && ( + )} - /> - {children} - {endpointSupportsFiles && !isUploadDisabled && } - ); + + ); } -export default memo(FileFormWrapper); \ No newline at end of file +export default memo(FileFormWrapper); diff --git a/client/src/components/Chat/Input/SendButton.tsx b/client/src/components/Chat/Input/SendButton.tsx index 4b80f367ee..870d62d312 100644 --- a/client/src/components/Chat/Input/SendButton.tsx +++ b/client/src/components/Chat/Input/SendButton.tsx @@ -9,46 +9,40 @@ import { cn } from '~/utils'; type SendButtonProps = { disabled: boolean; control: Control<{ text: string }>; - isRTL: boolean; }; const SubmitButton = React.memo( - forwardRef( - (props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef) => { - const localize = useLocalize(); - return ( - - - - - - } - > - ); - }, - ), + forwardRef((props: { disabled: boolean }, ref: React.ForwardedRef) => { + const localize = useLocalize(); + return ( + + + + + + } + > + ); + }), ); const SendButton = React.memo( forwardRef((props: SendButtonProps, ref: React.ForwardedRef) => { const data = useWatch({ control: props.control }); - return ; + return ; }), ); diff --git a/client/src/components/Chat/Input/StopButton.tsx b/client/src/components/Chat/Input/StopButton.tsx index 28ac9bbff5..6c785811cd 100644 --- a/client/src/components/Chat/Input/StopButton.tsx +++ b/client/src/components/Chat/Input/StopButton.tsx @@ -1,36 +1,37 @@ +import { TooltipAnchor } from '~/components/ui'; +import { useLocalize } from '~/hooks'; import { cn } from '~/utils'; -export default function StopButton({ stop, setShowStopButton, isRTL }) { +export default function StopButton({ stop, setShowStopButton }) { + const localize = useLocalize(); + return ( -
- -
+ + + + + } + > ); } diff --git a/client/src/components/Chat/Input/TextareaHeader.tsx b/client/src/components/Chat/Input/TextareaHeader.tsx index e4df93c993..d4aa3f0cc7 100644 --- a/client/src/components/Chat/Input/TextareaHeader.tsx +++ b/client/src/components/Chat/Input/TextareaHeader.tsx @@ -13,7 +13,7 @@ export default function TextareaHeader({ return null; } return ( -
+
); diff --git a/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx b/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx index a13975b33b..9517f91737 100644 --- a/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx +++ b/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx @@ -29,6 +29,7 @@ const InputWithLabel: FC = forwardRef((props, ref) => { )}
+
= forwardRef((props, ref) => { onChange={onChange} ref={ref} placeholder={`${localize('com_endpoint_config_value')} ${label}`} - className={cn( - defaultTextProps, - 'flex h-10 max-h-10 w-full resize-none px-3 py-2', - removeFocusOutlines, - inputClassName, - )} + className={cn('flex h-10 max-h-10 w-full resize-none px-3 py-2')} /> ); diff --git a/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx b/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx index b5ecb3b7c7..fc371d73a3 100644 --- a/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx +++ b/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx @@ -3,9 +3,9 @@ import { useForm, FormProvider } from 'react-hook-form'; import { EModelEndpoint, alternateName, isAssistantsEndpoint } from 'librechat-data-provider'; import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; import type { TDialogProps } from '~/common'; -import DialogTemplate from '~/components/ui/DialogTemplate'; +import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; import { RevokeKeysButton } from '~/components/Nav'; -import { Dialog, Dropdown } from '~/components/ui'; +import { OGDialog, Dropdown } from '~/components/ui'; import { useUserKey, useLocalize } from '~/hooks'; import { useToastContext } from '~/Providers'; import CustomConfig from './CustomEndpoint'; @@ -160,10 +160,11 @@ const SetKeyDialog = ({ const config = endpointsConfig?.[endpoint]; return ( - - + @@ -172,7 +173,7 @@ const SetKeyDialog = ({ : `${localize('com_endpoint_config_key_encryption')} ${new Date( expiryTime ?? 0, ).toLocaleString()}`} - {' '} + option.label)} sizeClasses="w-[185px]" /> +
+ } /> -
+ ); }; diff --git a/client/src/components/Nav/AccountSettings.tsx b/client/src/components/Nav/AccountSettings.tsx index b6f09f65fd..50678c848e 100644 --- a/client/src/components/Nav/AccountSettings.tsx +++ b/client/src/components/Nav/AccountSettings.tsx @@ -30,7 +30,7 @@ function AccountSettings() {
diff --git a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx index d0fae61c46..452a97a7de 100644 --- a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx +++ b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx @@ -41,7 +41,7 @@ const BookmarkNav: FC = ({ tags, setTags, isSmallScreen }: Boo className={cn( 'mt-text-sm flex h-10 w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors duration-200 hover:bg-surface-hover', open ? 'bg-surface-hover' : '', - isSmallScreen ? 'h-14 rounded-2xl' : '', + isSmallScreen ? 'h-12' : '', )} data-testid="bookmark-menu" > diff --git a/client/src/components/Nav/ClearConvos.tsx b/client/src/components/Nav/ClearConvos.tsx deleted file mode 100644 index 310899f35c..0000000000 --- a/client/src/components/Nav/ClearConvos.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from 'react'; -import { useClearConversationsMutation } from 'librechat-data-provider/react-query'; -import { useLocalize, useConversation, useConversations } from '~/hooks'; -import DialogTemplate from '~/components/ui/DialogTemplate'; -import { ClearChatsButton } from './SettingsTabs'; -import { Dialog } from '~/components/ui'; - -const ClearConvos = ({ open, onOpenChange }) => { - const { newConversation } = useConversation(); - const { refreshConversations } = useConversations(); - const clearConvosMutation = useClearConversationsMutation(); - const [confirmClear, setConfirmClear] = useState(false); - const localize = useLocalize(); - - // Clear all conversations - const clearConvos = () => { - if (confirmClear) { - clearConvosMutation.mutate( - {}, - { - onSuccess: () => { - newConversation(); - refreshConversations(); - }, - }, - ); - setConfirmClear(false); - } else { - setConfirmClear(true); - } - }; - - return ( - - - } - /> - - ); -}; - -export default ClearConvos; diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index acf2284b39..ffc2bbadce 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -168,36 +168,22 @@ const Nav = ({ onMouseLeave={handleMouseLeave} ref={containerRef} > - {isSmallScreen == true ? ( -
- {isSearchEnabled === true && ( - - )} - {hasAccessToBookmarks === true && ( + + {isSearchEnabled === true && ( + + )} - )} -
- ) : ( - - {isSearchEnabled === true && ( - - )} - - - } - /> - )} + + } + /> { @@ -57,10 +58,12 @@ export default function NewChat({ index = 0, toggleNav, subHeaders, + isSmallScreen, }: { index?: number; toggleNav: () => void; subHeaders?: React.ReactNode; + isSmallScreen: boolean; }) { /** Note: this component needs an explicit index passed if using more than one */ const { newConversation: newConvo } = useNewConvo(index); @@ -86,7 +89,10 @@ export default function NewChat({ tabIndex={0} data-testid="nav-new-chat" onClick={clickHandler} - className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover" + className={cn( + 'group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover', + isSmallScreen ? 'h-14' : '', + )} aria-label={localize('com_ui_new_chat')} > diff --git a/client/src/components/Nav/Settings.tsx b/client/src/components/Nav/Settings.tsx index 4adfc8be5a..19f8fe13e7 100644 --- a/client/src/components/Nav/Settings.tsx +++ b/client/src/components/Nav/Settings.tsx @@ -76,11 +76,11 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {

diff --git a/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx index eb04d3f068..f18b7a88bc 100644 --- a/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx +++ b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx @@ -5,16 +5,17 @@ import AvatarEditor from 'react-avatar-editor'; import { fileConfig as defaultFileConfig, mergeFileConfig } from 'librechat-data-provider'; import type { TUser } from 'librechat-data-provider'; import { + Slider, + Button, + Spinner, OGDialog, OGDialogContent, OGDialogHeader, OGDialogTitle, OGDialogTrigger, - Slider, -} from '~/components/ui'; +} from '~/components'; import { useUploadAvatarMutation, useGetFileConfig } from '~/data-provider'; import { useToastContext } from '~/Providers'; -import { Spinner } from '~/components/svg'; import { cn, formatBytes } from '~/utils'; import { useLocalize } from '~/hooks'; import store from '~/store'; @@ -130,10 +131,7 @@ function Avatar() {

- + {image ? localize('com_ui_preview') : localize('com_ui_upload_image')} @@ -174,10 +172,10 @@ function Avatar() {
- + ) : (
- +

{localize('com_ui_drag_drop')}

- + { const localize = useLocalize(); @@ -15,14 +23,8 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea }); const [isDialogOpen, setDialogOpen] = useState(false); - const [deleteInput, setDeleteInput] = useState(''); - const [emailInput, setEmailInput] = useState(''); const [isLocked, setIsLocked] = useState(true); - const onClick = useCallback(() => { - setDialogOpen(true); - }, []); - const handleDeleteUser = () => { if (!isLocked) { deleteUser(undefined); @@ -30,47 +32,38 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea }; const handleInputChange = useCallback( - (newEmailInput: string, newDeleteInput: string) => { + (newEmailInput: string) => { const isEmailCorrect = - newEmailInput.trim().toLowerCase() === user?.email?.trim().toLowerCase(); - const isDeleteInputCorrect = newDeleteInput === 'DELETE'; - setIsLocked(!(isEmailCorrect && isDeleteInputCorrect)); + newEmailInput.trim().toLowerCase() === user?.email.trim().toLowerCase(); + setIsLocked(!isEmailCorrect); }, [user?.email], ); return ( <> -
- {localize('com_nav_delete_account')} - -
- setDialogOpen(false)}> - - - + +
+ {localize('com_nav_delete_account')} + + + +
+ + + {localize('com_nav_delete_account_confirm')} -
-
-
-
    + + +
    +
    • {localize('com_nav_delete_warning')}
    • {localize('com_nav_delete_data_info')}
    @@ -80,28 +73,14 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea {renderInput( localize('com_nav_delete_account_email_placeholder'), 'email-confirm-input', - user?.email || '', - (e) => { - setEmailInput(e.target.value); - handleInputChange(e.target.value, deleteInput); - }, - )} -
    -
    - {renderInput( - localize('com_nav_delete_account_confirm_placeholder'), - 'delete-confirm-input', - '', - (e) => { - setDeleteInput(e.target.value); - handleInputChange(emailInput, e.target.value); - }, + user?.email ?? '', + (e) => handleInputChange(e.target.value), )}
    {renderDeleteButton(handleDeleteUser, isDeleting, isLocked, localize)}
-
-
+ + ); }; @@ -113,17 +92,10 @@ const renderInput = ( onChange: (e: React.ChangeEvent) => void, ) => (
- - + +
); @@ -135,12 +107,8 @@ const renderDeleteButton = ( ) => ( + + + {localize('com_nav_clear_conversation_confirm_message')} + + } + selection={{ + selectHandler: clearConvos, + selectClasses: + 'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80', + selectText: clearConvosMutation.isLoading ? : localize('com_ui_delete'), + }} + /> + +
); }; diff --git a/client/src/components/Nav/SettingsTabs/Data/ClearChatsButton.spec.tsx b/client/src/components/Nav/SettingsTabs/Data/ClearChatsButton.spec.tsx deleted file mode 100644 index fbf82fd902..0000000000 --- a/client/src/components/Nav/SettingsTabs/Data/ClearChatsButton.spec.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import 'test/matchMedia.mock'; -import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { ClearChatsButton } from './ClearChats'; -import { RecoilRoot } from 'recoil'; - -describe('ClearChatsButton', () => { - let mockOnClick; - - beforeEach(() => { - mockOnClick = jest.fn(); - }); - - it('renders correctly', () => { - const { getByText } = render( - - - , - ); - - expect(getByText('Clear all chats')).toBeInTheDocument(); - expect(getByText('Clear')).toBeInTheDocument(); - }); - - it('renders confirm clear when confirmClear is true', () => { - const { getByText } = render( - - - , - ); - - expect(getByText('Confirm Clear')).toBeInTheDocument(); - }); - - it('calls onClick when the button is clicked', () => { - const { getByText } = render( - - - , - ); - - fireEvent.click(getByText('Clear')); - - expect(mockOnClick).toHaveBeenCalled(); - }); -}); diff --git a/client/src/components/Nav/SettingsTabs/Data/Data.tsx b/client/src/components/Nav/SettingsTabs/Data/Data.tsx index 6897a4a0b4..18c30d14a6 100644 --- a/client/src/components/Nav/SettingsTabs/Data/Data.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/Data.tsx @@ -1,10 +1,9 @@ import React, { useState, useRef } from 'react'; -import { useClearConversationsMutation } from 'librechat-data-provider/react-query'; -import { useConversation, useConversations, useOnClickOutside } from '~/hooks'; -import { RevokeKeysButton } from './RevokeKeysButton'; -import { DeleteCacheButton } from './DeleteCacheButton'; import ImportConversations from './ImportConversations'; -import { ClearChatsButton } from './ClearChats'; +import { RevokeAllKeys } from './RevokeAllKeys'; +import { DeleteCache } from './DeleteCache'; +import { useOnClickOutside } from '~/hooks'; +import { ClearChats } from './ClearChats'; import SharedLinks from './SharedLinks'; function Data() { @@ -12,28 +11,6 @@ function Data() { const [confirmClearConvos, setConfirmClearConvos] = useState(false); useOnClickOutside(dataTabRef, () => confirmClearConvos && setConfirmClearConvos(false), []); - const { newConversation } = useConversation(); - const { refreshConversations } = useConversations(); - const clearConvosMutation = useClearConversationsMutation(); - - const clearConvos = () => { - if (confirmClearConvos) { - console.log('Clearing conversations...'); - setConfirmClearConvos(false); - clearConvosMutation.mutate( - {}, - { - onSuccess: () => { - newConversation(); - refreshConversations(); - }, - }, - ); - } else { - setConfirmClearConvos(true); - } - }; - return (
@@ -43,18 +20,13 @@ function Data() {
- +
- +
- +
); diff --git a/client/src/components/Nav/SettingsTabs/Data/DeleteCache.tsx b/client/src/components/Nav/SettingsTabs/Data/DeleteCache.tsx new file mode 100644 index 0000000000..a2d36da586 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Data/DeleteCache.tsx @@ -0,0 +1,65 @@ +import React, { useState, useCallback, useRef, useEffect } from 'react'; +import { Label, Button, OGDialog, OGDialogTrigger, Spinner } from '~/components'; +import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; +import { useOnClickOutside, useLocalize } from '~/hooks'; + +export const DeleteCache = ({ disabled = false }: { disabled?: boolean }) => { + const localize = useLocalize(); + const [open, setOpen] = useState(false); + const [isCacheEmpty, setIsCacheEmpty] = useState(true); + const [confirmClear, setConfirmClear] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const contentRef = useRef(null); + useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []); + + const checkCache = useCallback(async () => { + const cache = await caches.open('tts-responses'); + const keys = await cache.keys(); + setIsCacheEmpty(keys.length === 0); + }, []); + + useEffect(() => { + checkCache(); + }, [checkCache]); + + const revokeAllUserKeys = useCallback(async () => { + setIsLoading(true); + const cache = await caches.open('tts-responses'); + await cache.keys().then((keys) => Promise.all(keys.map((key) => cache.delete(key)))); + setIsLoading(false); + }, []); + + return ( +
+ + + + + + + {localize('com_nav_clear_cache_confirm_message')} + + } + selection={{ + selectHandler: revokeAllUserKeys, + selectClasses: + 'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80', + selectText: isLoading ? : localize('com_ui_delete'), + }} + /> + +
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Data/DeleteCacheButton.tsx b/client/src/components/Nav/SettingsTabs/Data/DeleteCacheButton.tsx deleted file mode 100644 index 84e444f203..0000000000 --- a/client/src/components/Nav/SettingsTabs/Data/DeleteCacheButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useCallback, useRef, useEffect } from 'react'; -import { useOnClickOutside } from '~/hooks'; -import DangerButton from '../DangerButton'; - -export const DeleteCacheButton = ({ - showText = true, - disabled = false, -}: { - showText?: boolean; - disabled?: boolean; -}) => { - const [confirmClear, setConfirmClear] = useState(false); - const [isCacheEmpty, setIsCacheEmpty] = useState(true); - const contentRef = useRef(null); - useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []); - - const checkCache = useCallback(async () => { - const cache = await caches.open('tts-responses'); - const keys = await cache.keys(); - setIsCacheEmpty(keys.length === 0); - }, []); - - useEffect(() => { - checkCache(); - }, [confirmClear]); - - const revokeAllUserKeys = useCallback(async () => { - if (confirmClear) { - const cache = await caches.open('tts-responses'); - await cache.keys().then((keys) => Promise.all(keys.map((key) => cache.delete(key)))); - - setConfirmClear(false); - } else { - setConfirmClear(true); - } - }, [confirmClear]); - - return ( - - ); -}; diff --git a/client/src/components/Nav/SettingsTabs/Data/RevokeAllKeys.tsx b/client/src/components/Nav/SettingsTabs/Data/RevokeAllKeys.tsx new file mode 100644 index 0000000000..1c03bc58f5 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Data/RevokeAllKeys.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { RevokeKeysButton } from './RevokeKeysButton'; +import { Label } from '~/components/ui'; +import { useLocalize } from '~/hooks'; + +export const RevokeAllKeys = () => { + const localize = useLocalize(); + + return ( +
+ + +
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Data/RevokeKeysButton.tsx b/client/src/components/Nav/SettingsTabs/Data/RevokeKeysButton.tsx index 1d5dd82956..806ab8fbf4 100644 --- a/client/src/components/Nav/SettingsTabs/Data/RevokeKeysButton.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/RevokeKeysButton.tsx @@ -2,64 +2,77 @@ import { useRevokeAllUserKeysMutation, useRevokeUserKeyMutation, } from 'librechat-data-provider/react-query'; -import React, { useState, useCallback, useRef } from 'react'; -import { useOnClickOutside } from '~/hooks'; -import DangerButton from '../DangerButton'; +import React, { useState } from 'react'; +import { Button, Label, OGDialog, OGDialogTrigger, Spinner } from '~/components'; +import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; +import { useLocalize } from '~/hooks'; export const RevokeKeysButton = ({ - showText = true, endpoint = '', all = false, disabled = false, + setDialogOpen, }: { - showText?: boolean; endpoint?: string; all?: boolean; disabled?: boolean; + setDialogOpen?: (open: boolean) => void; }) => { - const [confirmClear, setConfirmClear] = useState(false); + const localize = useLocalize(); + const [open, setOpen] = useState(false); const revokeKeyMutation = useRevokeUserKeyMutation(endpoint); const revokeKeysMutation = useRevokeAllUserKeysMutation(); - const contentRef = useRef(null); - useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []); - - const revokeAllUserKeys = useCallback(() => { - if (confirmClear) { - revokeKeysMutation.mutate({}); - setConfirmClear(false); - } else { - setConfirmClear(true); - } - }, [confirmClear, revokeKeysMutation]); - - const revokeUserKey = useCallback(() => { - if (!endpoint) { + const handleSuccess = () => { + if (!setDialogOpen) { return; - } else if (confirmClear) { - revokeKeyMutation.mutate({}); - setConfirmClear(false); - } else { - setConfirmClear(true); } - }, [confirmClear, revokeKeyMutation, endpoint]); - const onClick = all ? revokeAllUserKeys : revokeUserKey; + setDialogOpen(false); + }; + + const onClick = () => { + if (all) { + revokeKeysMutation.mutate({}); + } else { + revokeKeyMutation.mutate({}, { onSuccess: handleSuccess }); + } + }; + + const dialogTitle = all + ? localize('com_ui_revoke_keys') + : localize('com_ui_revoke_key_endpoint', endpoint); + + const dialogMessage = all + ? localize('com_ui_revoke_keys_confirm') + : localize('com_ui_revoke_key_confirm'); + + const isLoading = revokeKeyMutation.isLoading || revokeKeysMutation.isLoading; return ( - + + + + + {dialogMessage}} + selection={{ + selectHandler: onClick, + selectClasses: + 'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80', + selectText: isLoading ? : localize('com_ui_revoke'), + }} + /> + ); }; diff --git a/client/src/components/Nav/index.ts b/client/src/components/Nav/index.ts index 5770793712..6ed5de4453 100644 --- a/client/src/components/Nav/index.ts +++ b/client/src/components/Nav/index.ts @@ -1,6 +1,5 @@ export * from './ExportConversation'; export * from './SettingsTabs/'; -export { default as ClearConvos } from './ClearConvos'; export { default as MobileNav } from './MobileNav'; export { default as Nav } from './Nav'; export { default as NavLink } from './NavLink'; diff --git a/client/src/components/SidePanel/Agents/FileSearch.tsx b/client/src/components/SidePanel/Agents/FileSearch.tsx index 84263f3566..9e4c061bd3 100644 --- a/client/src/components/SidePanel/Agents/FileSearch.tsx +++ b/client/src/components/SidePanel/Agents/FileSearch.tsx @@ -78,7 +78,7 @@ export default function FileSearch({ - + {localize( 'com_ui_share_var', @@ -255,13 +256,14 @@ export default function ShareAgent({
- +
diff --git a/client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx b/client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx index aebe235467..5d30859d08 100644 --- a/client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx +++ b/client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx @@ -18,8 +18,8 @@ const BookmarkPanel = () => {
-
diff --git a/client/src/components/ui/Button.tsx b/client/src/components/ui/Button.tsx index 64989f4d1a..9b6a6aaf5c 100644 --- a/client/src/components/ui/Button.tsx +++ b/client/src/components/ui/Button.tsx @@ -9,12 +9,13 @@ const buttonVariants = cva( variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', - destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/80', outline: 'text-text-primary border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', + submit: 'bg-surface-submit text-text-primary hover:bg-surface-submit/90', }, size: { default: 'h-10 px-4 py-2', diff --git a/client/src/components/ui/Input.tsx b/client/src/components/ui/Input.tsx index 042f6a5690..adc3b422c1 100644 --- a/client/src/components/ui/Input.tsx +++ b/client/src/components/ui/Input.tsx @@ -8,7 +8,7 @@ const Input = React.forwardRef(({ className, ...pr return ( void; selectClasses?: string; - selectText?: string; + selectText?: string | ReactNode; }; type DialogTemplateProps = { @@ -81,7 +81,7 @@ const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref {selectText} diff --git a/client/src/components/ui/OriginalDialog.tsx b/client/src/components/ui/OriginalDialog.tsx index f68746b9ff..b9abd9e9e4 100644 --- a/client/src/components/ui/OriginalDialog.tsx +++ b/client/src/components/ui/OriginalDialog.tsx @@ -41,7 +41,7 @@ const DialogContent = React.forwardRef< => { return request.get(`${endpoints.files()}/config`); }; -export const uploadImage = (data: FormData, signal?: AbortSignal | null): Promise => { +export const uploadImage = ( + data: FormData, + signal?: AbortSignal | null, +): Promise => { const requestConfig = signal ? { signal } : undefined; return request.postMultiPart(endpoints.images(), data, requestConfig); };