🎨 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:
Marco Beretta 2024-10-19 14:30:52 +02:00 committed by GitHub
parent 20fb7f05ae
commit 8f3de7d11f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 471 additions and 564 deletions

View file

@ -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>
);
};

View file

@ -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();
});
});

View file

@ -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>
);

View 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>
);
};

View file

@ -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'}
/>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};