️ 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:
Marco Beretta 2025-10-07 20:12:49 +02:00 committed by GitHub
parent bcd97aad2f
commit a5189052ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 1158 additions and 857 deletions

View file

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