mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
🎨 refactor: UI stlye (#4438)
* feat: Refactor ChatForm and StopButton components for improved styling and localization * feat: Refactor AudioRecorder, ChatForm, AttachFile, and SendButton components for improved styling and layout * feat: Add RevokeAllKeys component and update styling for buttons and inputs * feat: Refactor ClearChats component and update ClearConvos functionality for improved clarity and user experience * feat: Remove ClearConvos component and update related imports and functionality in Avatar and DeleteCacheButton components * feat: Rename DeleteCacheButton to DeleteCache and update related imports; enhance confirmation message in localization * feat: Update ChatForm layout for RTL support and improve component structure * feat: Adjust ChatForm layout for improved RTL support and alignment * feat: Refactor Bookmark components to use new UI elements and improve styling * feat: Update FileSearch and ShareAgent components for improved button styling and layout * feat: Update ChatForm and TextareaHeader styles for improved UI consistency * feat: Refactor Nav components for improved styling and layout adjustments * feat: Update button sizes and padding for improved UI consistency across chat components * feat: Remove ClearChatsButton test file as part of code cleanup
This commit is contained in:
parent
20fb7f05ae
commit
8f3de7d11f
38 changed files with 471 additions and 564 deletions
|
|
@ -1,29 +1,58 @@
|
|||
import type { TDangerButtonProps } from '~/common';
|
||||
import DangerButton from '../DangerButton';
|
||||
import React, { useState } from 'react';
|
||||
import { useClearConversationsMutation } from 'librechat-data-provider/react-query';
|
||||
import { Label, Button, OGDialog, OGDialogTrigger, Spinner } from '~/components';
|
||||
import { useConversation, useConversations, useLocalize } from '~/hooks';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
|
||||
export const ClearChats = () => {
|
||||
const localize = useLocalize();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { newConversation } = useConversation();
|
||||
const { refreshConversations } = useConversations();
|
||||
const clearConvosMutation = useClearConversationsMutation();
|
||||
|
||||
const clearConvos = () => {
|
||||
clearConvosMutation.mutate(
|
||||
{},
|
||||
{
|
||||
onSuccess: () => {
|
||||
newConversation();
|
||||
refreshConversations();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const ClearChatsButton = ({
|
||||
confirmClear,
|
||||
className = '',
|
||||
showText = true,
|
||||
mutation,
|
||||
onClick,
|
||||
}: Pick<
|
||||
TDangerButtonProps,
|
||||
'confirmClear' | 'mutation' | 'className' | 'showText' | 'onClick'
|
||||
>) => {
|
||||
return (
|
||||
<DangerButton
|
||||
id="clearConvosBtn"
|
||||
mutation={mutation}
|
||||
confirmClear={confirmClear}
|
||||
className={className}
|
||||
showText={showText}
|
||||
infoTextCode="com_nav_clear_all_chats"
|
||||
actionTextCode="com_ui_clear"
|
||||
confirmActionTextCode="com_nav_confirm_clear"
|
||||
dataTestIdInitial="clear-convos-initial"
|
||||
dataTestIdConfirm="clear-convos-confirm"
|
||||
onClick={onClick}
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="font-light">{localize('com_nav_clear_all_chats')}</Label>
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="flex items-center justify-center rounded-lg transition-colors duration-200"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{localize('com_ui_delete')}
|
||||
</Button>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_nav_confirm_clear')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_nav_clear_conversation_confirm_message')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: clearConvos,
|
||||
selectClasses:
|
||||
'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80',
|
||||
selectText: clearConvosMutation.isLoading ? <Spinner /> : localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<RecoilRoot>
|
||||
<ClearChatsButton confirmClear={false} showText={true} onClick={mockOnClick} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Clear all chats')).toBeInTheDocument();
|
||||
expect(getByText('Clear')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders confirm clear when confirmClear is true', () => {
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<ClearChatsButton confirmClear={true} showText={true} onClick={mockOnClick} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Confirm Clear')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when the button is clicked', () => {
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<ClearChatsButton confirmClear={false} showText={true} onClick={mockOnClick} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByText('Clear'));
|
||||
|
||||
expect(mockOnClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col gap-3 p-1 text-sm text-text-primary">
|
||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||
|
|
@ -43,18 +20,13 @@ function Data() {
|
|||
<SharedLinks />
|
||||
</div>
|
||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||
<RevokeKeysButton all={true} />
|
||||
<RevokeAllKeys />
|
||||
</div>
|
||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||
<DeleteCacheButton />
|
||||
<DeleteCache />
|
||||
</div>
|
||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||
<ClearChatsButton
|
||||
confirmClear={confirmClearConvos}
|
||||
onClick={clearConvos}
|
||||
showText={true}
|
||||
mutation={clearConvosMutation}
|
||||
/>
|
||||
<ClearChats />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
65
client/src/components/Nav/SettingsTabs/Data/DeleteCache.tsx
Normal file
65
client/src/components/Nav/SettingsTabs/Data/DeleteCache.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="font-light">{localize('com_nav_delete_cache_storage')}</Label>
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="flex items-center justify-center rounded-lg transition-colors duration-200"
|
||||
onClick={() => setOpen(true)}
|
||||
disabled={disabled || isCacheEmpty}
|
||||
>
|
||||
{localize('com_ui_delete')}
|
||||
</Button>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_nav_confirm_clear')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_nav_clear_cache_confirm_message')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: revokeAllUserKeys,
|
||||
selectClasses:
|
||||
'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80',
|
||||
selectText: isLoading ? <Spinner /> : localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<DangerButton
|
||||
ref={contentRef}
|
||||
showText={showText}
|
||||
onClick={revokeAllUserKeys}
|
||||
disabled={disabled || isCacheEmpty}
|
||||
confirmClear={confirmClear}
|
||||
id={'delete-cache'}
|
||||
actionTextCode={'com_ui_delete'}
|
||||
infoTextCode={'com_nav_delete_cache_storage'}
|
||||
infoDescriptionCode={'com_nav_info_delete_cache_storage'}
|
||||
dataTestIdInitial={'delete-cache-initial'}
|
||||
dataTestIdConfirm={'delete-cache-confirm'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="font-light">{localize('com_ui_revoke_info')}</Label>
|
||||
<RevokeKeysButton all={true} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<DangerButton
|
||||
ref={contentRef}
|
||||
showText={showText}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
confirmClear={confirmClear}
|
||||
id={'revoke-all-user-keys'}
|
||||
actionTextCode={'com_ui_revoke'}
|
||||
infoTextCode={'com_ui_revoke_info'}
|
||||
infoDescriptionCode={'com_nav_info_revoke'}
|
||||
dataTestIdInitial={'revoke-all-keys-initial'}
|
||||
dataTestIdConfirm={'revoke-all-keys-confirm'}
|
||||
mutation={all ? revokeKeysMutation : revokeKeyMutation}
|
||||
/>
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="flex items-center justify-center rounded-lg transition-colors duration-200"
|
||||
onClick={() => setOpen(true)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{localize('com_ui_revoke')}
|
||||
</Button>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={dialogTitle}
|
||||
className="max-w-[450px]"
|
||||
main={<Label className="text-left text-sm font-medium">{dialogMessage}</Label>}
|
||||
selection={{
|
||||
selectHandler: onClick,
|
||||
selectClasses:
|
||||
'bg-destructive text-white transition-all duration-200 hover:bg-destructive/80',
|
||||
selectText: isLoading ? <Spinner /> : localize('com_ui_revoke'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue