mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
♿️ fix: Accessibility, UI consistency, dialog & avatar refactors (#9975)
* 🔧 refactor: Improve accessibility and styling in ChatGroupItem and FilterPrompts components * 🔧 fix: Add button type and keyboard accessibility to dropdown menu trigger in ChatGroupItem * 🔧 fix(757): Enhance accessibility by updating aria-labels and adding localization for prompt groups * 🔧 fix(618): Update version to 0.3.1 and enhance accessibility in InfoHoverCard component * 🔧 fix(618): Update aria-label in InfoHoverCard to use dynamic text prop for improved accessibility * 🔧 fix: Enhance accessibility by updating aria-labels and roles in Conversations components * 🔧 fix(620): Enhance accessibility by adding tabIndex to Tabs.Content components in ArtifactTabs, Settings, and Speech components * refactor: remove RevokeKeysButton component and update related components for accessibility - Deleted RevokeKeysButton component. - Updated SharedLinks and General components to use Label for accessibility. - Enhanced Personalization component with aria-labelledby and aria-describedby attributes. - Refactored ConversationModeSwitch to use ToggleSwitch for better state management. - Improved AutoSendTextSelector with local state management and accessibility attributes. - Replaced Switch components with ToggleSwitch in various Speech and TTS components for consistency. - Added aria-labelledby attributes to Dropdown components for better accessibility. - Updated translation.json to include new localization keys and improved existing ones. - Enhanced Slider component to support aria attributes for better accessibility. * 🔧 fix: Enhance user feedback for API key operations with success and error messages * 🔧 fix: Update aria-labels in Avatar component for improved localization and accessibility * 🔧 fix: Refactor handleFile and handleDrop functions for improved readability and maintainability
This commit is contained in:
parent
bcd97aad2f
commit
a5189052ec
56 changed files with 1158 additions and 857 deletions
|
|
@ -1,16 +1,33 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { OGDialogTemplate, OGDialog, Dropdown, useToastContext } from '@librechat/client';
|
||||
import {
|
||||
OGDialog,
|
||||
OGDialogContent,
|
||||
OGDialogHeader,
|
||||
OGDialogTitle,
|
||||
OGDialogFooter,
|
||||
Dropdown,
|
||||
useToastContext,
|
||||
Button,
|
||||
Label,
|
||||
OGDialogTrigger,
|
||||
Spinner,
|
||||
} from '@librechat/client';
|
||||
import { EModelEndpoint, alternateName, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
useRevokeAllUserKeysMutation,
|
||||
useRevokeUserKeyMutation,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { TDialogProps } from '~/common';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { RevokeKeysButton } from '~/components/Nav';
|
||||
import { useUserKey, useLocalize } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import CustomConfig from './CustomEndpoint';
|
||||
import GoogleConfig from './GoogleConfig';
|
||||
import OpenAIConfig from './OpenAIConfig';
|
||||
import OtherConfig from './OtherConfig';
|
||||
import HelpText from './HelpText';
|
||||
import { logger } from '~/utils';
|
||||
|
||||
const endpointComponents = {
|
||||
[EModelEndpoint.google]: GoogleConfig,
|
||||
|
|
@ -42,6 +59,94 @@ const EXPIRY = {
|
|||
NEVER: { label: 'never', value: 0 },
|
||||
};
|
||||
|
||||
const RevokeKeysButton = ({
|
||||
endpoint,
|
||||
disabled,
|
||||
setDialogOpen,
|
||||
}: {
|
||||
endpoint: string;
|
||||
disabled: boolean;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { showToast } = useToastContext();
|
||||
const revokeKeyMutation = useRevokeUserKeyMutation(endpoint);
|
||||
const revokeKeysMutation = useRevokeAllUserKeysMutation();
|
||||
|
||||
const handleSuccess = () => {
|
||||
showToast({
|
||||
message: localize('com_ui_revoke_key_success'),
|
||||
status: NotificationSeverity.SUCCESS,
|
||||
});
|
||||
|
||||
if (!setDialogOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
showToast({
|
||||
message: localize('com_ui_revoke_key_error'),
|
||||
status: NotificationSeverity.ERROR,
|
||||
});
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
revokeKeyMutation.mutate(
|
||||
{},
|
||||
{
|
||||
onSuccess: handleSuccess,
|
||||
onError: handleError,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const isLoading = revokeKeyMutation.isLoading || revokeKeysMutation.isLoading;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<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>
|
||||
<OGDialogContent className="max-w-[450px]">
|
||||
<OGDialogHeader>
|
||||
<OGDialogTitle>{localize('com_ui_revoke_key_endpoint', { 0: endpoint })}</OGDialogTitle>
|
||||
</OGDialogHeader>
|
||||
<div className="py-4">
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_revoke_key_confirm')}
|
||||
</Label>
|
||||
</div>
|
||||
<OGDialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
{localize('com_ui_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={onClick}
|
||||
disabled={isLoading}
|
||||
className="bg-destructive text-white transition-all duration-200 hover:bg-destructive/80"
|
||||
>
|
||||
{isLoading ? <Spinner /> : localize('com_ui_revoke')}
|
||||
</Button>
|
||||
</OGDialogFooter>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SetKeyDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
|
|
@ -83,7 +188,7 @@ const SetKeyDialog = ({
|
|||
|
||||
const submit = () => {
|
||||
const selectedOption = expirationOptions.find((option) => option.label === expiresAtLabel);
|
||||
let expiresAt;
|
||||
let expiresAt: number | null;
|
||||
|
||||
if (selectedOption?.value === 0) {
|
||||
expiresAt = null;
|
||||
|
|
@ -92,8 +197,20 @@ const SetKeyDialog = ({
|
|||
}
|
||||
|
||||
const saveKey = (key: string) => {
|
||||
saveUserKey(key, expiresAt);
|
||||
onOpenChange(false);
|
||||
try {
|
||||
saveUserKey(key, expiresAt);
|
||||
showToast({
|
||||
message: localize('com_ui_save_key_success'),
|
||||
status: NotificationSeverity.SUCCESS,
|
||||
});
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
logger.error('Error saving user key:', error);
|
||||
showToast({
|
||||
message: localize('com_ui_save_key_error'),
|
||||
status: NotificationSeverity.ERROR,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (formSet.has(endpoint) || formSet.has(endpointType ?? '')) {
|
||||
|
|
@ -148,6 +265,14 @@ const SetKeyDialog = ({
|
|||
return;
|
||||
}
|
||||
|
||||
if (!userKey.trim()) {
|
||||
showToast({
|
||||
message: localize('com_ui_key_required'),
|
||||
status: NotificationSeverity.ERROR,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
saveKey(userKey);
|
||||
setUserKey('');
|
||||
};
|
||||
|
|
@ -159,56 +284,54 @@ const SetKeyDialog = ({
|
|||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||
<OGDialogTemplate
|
||||
title={`${localize('com_endpoint_config_key_for')} ${alternateName[endpoint] ?? endpoint}`}
|
||||
className="w-11/12 max-w-2xl"
|
||||
showCancelButton={false}
|
||||
main={
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<small className="text-red-600">
|
||||
{expiryTime === 'never'
|
||||
? localize('com_endpoint_config_key_never_expires')
|
||||
: `${localize('com_endpoint_config_key_encryption')} ${new Date(
|
||||
expiryTime ?? 0,
|
||||
).toLocaleString()}`}
|
||||
</small>
|
||||
<Dropdown
|
||||
label="Expires "
|
||||
value={expiresAtLabel}
|
||||
onChange={handleExpirationChange}
|
||||
options={expirationOptions.map((option) => option.label)}
|
||||
sizeClasses="w-[185px]"
|
||||
portal={false}
|
||||
<OGDialogContent className="w-11/12 max-w-2xl">
|
||||
<OGDialogHeader>
|
||||
<OGDialogTitle>
|
||||
{`${localize('com_endpoint_config_key_for')} ${alternateName[endpoint] ?? endpoint}`}
|
||||
</OGDialogTitle>
|
||||
</OGDialogHeader>
|
||||
<div className="grid w-full items-center gap-2 py-4">
|
||||
<small className="text-red-600">
|
||||
{expiryTime === 'never'
|
||||
? localize('com_endpoint_config_key_never_expires')
|
||||
: `${localize('com_endpoint_config_key_encryption')} ${new Date(
|
||||
expiryTime ?? 0,
|
||||
).toLocaleString()}`}
|
||||
</small>
|
||||
<Dropdown
|
||||
label="Expires "
|
||||
value={expiresAtLabel}
|
||||
onChange={handleExpirationChange}
|
||||
options={expirationOptions.map((option) => option.label)}
|
||||
sizeClasses="w-[185px]"
|
||||
portal={false}
|
||||
/>
|
||||
<div className="mt-2" />
|
||||
<FormProvider {...methods}>
|
||||
<EndpointComponent
|
||||
userKey={userKey}
|
||||
setUserKey={setUserKey}
|
||||
endpoint={
|
||||
endpoint === EModelEndpoint.gptPlugins && (config?.azure ?? false)
|
||||
? EModelEndpoint.azureOpenAI
|
||||
: endpoint
|
||||
}
|
||||
userProvideURL={userProvideURL}
|
||||
/>
|
||||
<div className="mt-2" />
|
||||
<FormProvider {...methods}>
|
||||
<EndpointComponent
|
||||
userKey={userKey}
|
||||
setUserKey={setUserKey}
|
||||
endpoint={
|
||||
endpoint === EModelEndpoint.gptPlugins && (config?.azure ?? false)
|
||||
? EModelEndpoint.azureOpenAI
|
||||
: endpoint
|
||||
}
|
||||
userProvideURL={userProvideURL}
|
||||
/>
|
||||
</FormProvider>
|
||||
<HelpText endpoint={endpoint} />
|
||||
</div>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: submit,
|
||||
selectClasses: 'btn btn-primary',
|
||||
selectText: localize('com_ui_submit'),
|
||||
}}
|
||||
leftButtons={
|
||||
</FormProvider>
|
||||
<HelpText endpoint={endpoint} />
|
||||
</div>
|
||||
<OGDialogFooter>
|
||||
<RevokeKeysButton
|
||||
endpoint={endpoint}
|
||||
disabled={!(expiryTime ?? '')}
|
||||
setDialogOpen={onOpenChange}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button variant="submit" onClick={submit}>
|
||||
{localize('com_ui_submit')}
|
||||
</Button>
|
||||
</OGDialogFooter>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue