mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🗣 feat: MCP Status Accessibility Improvements (#10738)
* feat: make MultiSelect highlight same opacity as other focus highlights in app * feat: add better screenreader announcements for mcp server and variable states * feat: memoize fullTitle calculation
This commit is contained in:
parent
ad6ba4b6d1
commit
1a38e2a081
4 changed files with 49 additions and 15 deletions
|
|
@ -27,6 +27,7 @@ interface AuthFieldProps {
|
|||
|
||||
function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) {
|
||||
const localize = useLocalize();
|
||||
const statusText = hasValue ? localize('com_ui_set') : localize('com_ui_unset');
|
||||
|
||||
const sanitizer = useMemo(() => {
|
||||
const instance = DOMPurify();
|
||||
|
|
@ -60,19 +61,21 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
|
|||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor={name} className="text-sm font-medium">
|
||||
{config.title}
|
||||
{config.title} <span className="sr-only">({statusText})</span>
|
||||
</Label>
|
||||
{hasValue ? (
|
||||
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||
<span>{localize('com_ui_set')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-border-medium" />
|
||||
<span>{localize('com_ui_unset')}</span>
|
||||
</div>
|
||||
)}
|
||||
<div aria-hidden="true">
|
||||
{hasValue ? (
|
||||
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||
<span>{localize('com_ui_set')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-border-medium" />
|
||||
<span>{localize('com_ui_unset')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Controller
|
||||
name={name}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { KeyRound, PlugZap, AlertTriangle } from 'lucide-react';
|
||||
import {
|
||||
Spinner,
|
||||
|
|
@ -44,6 +44,33 @@ export default function MCPConfigDialog({
|
|||
? localize('com_ui_configure_mcp_variables_for', { 0: serverName })
|
||||
: `${serverName} MCP Server`;
|
||||
|
||||
const fullTitle = useMemo(() => {
|
||||
if (!serverStatus) {
|
||||
return localize('com_ui_mcp_dialog_title', {
|
||||
serverName,
|
||||
status: '',
|
||||
});
|
||||
}
|
||||
|
||||
const { connectionState, requiresOAuth } = serverStatus;
|
||||
let statusText = '';
|
||||
|
||||
if (connectionState === 'connecting') {
|
||||
statusText = localize('com_ui_connecting');
|
||||
} else if (connectionState === 'error') {
|
||||
statusText = localize('com_ui_error');
|
||||
} else if (connectionState === 'connected') {
|
||||
statusText = localize('com_ui_active');
|
||||
} else if (connectionState === 'disconnected') {
|
||||
statusText = requiresOAuth ? localize('com_ui_oauth') : localize('com_ui_offline');
|
||||
}
|
||||
|
||||
return localize('com_ui_mcp_dialog_title', {
|
||||
serverName,
|
||||
status: statusText,
|
||||
});
|
||||
}, [serverStatus, serverName, localize]);
|
||||
|
||||
// Helper function to render status badge based on connection state
|
||||
const renderStatusBadge = () => {
|
||||
if (!serverStatus) {
|
||||
|
|
@ -102,7 +129,10 @@ export default function MCPConfigDialog({
|
|||
|
||||
return (
|
||||
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<OGDialogContent className="flex max-h-screen w-11/12 max-w-lg flex-col space-y-2">
|
||||
<OGDialogContent
|
||||
className="flex max-h-screen w-11/12 max-w-lg flex-col space-y-2"
|
||||
title={fullTitle}
|
||||
>
|
||||
<OGDialogHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<OGDialogTitle className="text-xl">
|
||||
|
|
|
|||
|
|
@ -789,6 +789,7 @@
|
|||
"com_ui_complete_setup": "Complete Setup",
|
||||
"com_ui_concise": "Concise",
|
||||
"com_ui_configure_mcp_variables_for": "Configure Variables for {{0}}",
|
||||
"com_ui_mcp_dialog_title": "Configure Variables for {{serverName}}. Server Status: {{status}}",
|
||||
"com_ui_confirm": "Confirm",
|
||||
"com_ui_confirm_action": "Confirm Action",
|
||||
"com_ui_confirm_admin_use_change": "Changing this setting will block access for admins, including yourself. Are you sure you want to proceed?",
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export default function MultiSelect<T extends string>({
|
|||
className={cn(
|
||||
'flex items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',
|
||||
'bg-surface-tertiary text-text-primary shadow-sm hover:cursor-pointer hover:bg-surface-hover',
|
||||
'outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75',
|
||||
'outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white',
|
||||
selectClassName,
|
||||
selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName,
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue