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) {
|
function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
const statusText = hasValue ? localize('com_ui_set') : localize('com_ui_unset');
|
||||||
|
|
||||||
const sanitizer = useMemo(() => {
|
const sanitizer = useMemo(() => {
|
||||||
const instance = DOMPurify();
|
const instance = DOMPurify();
|
||||||
|
|
@ -60,19 +61,21 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor={name} className="text-sm font-medium">
|
<Label htmlFor={name} className="text-sm font-medium">
|
||||||
{config.title}
|
{config.title} <span className="sr-only">({statusText})</span>
|
||||||
</Label>
|
</Label>
|
||||||
{hasValue ? (
|
<div aria-hidden="true">
|
||||||
<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">
|
{hasValue ? (
|
||||||
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
<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">
|
||||||
<span>{localize('com_ui_set')}</span>
|
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||||
</div>
|
<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" />
|
<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">
|
||||||
<span>{localize('com_ui_unset')}</span>
|
<div className="h-1.5 w-1.5 rounded-full border border-border-medium" />
|
||||||
</div>
|
<span>{localize('com_ui_unset')}</span>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { KeyRound, PlugZap, AlertTriangle } from 'lucide-react';
|
import { KeyRound, PlugZap, AlertTriangle } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Spinner,
|
Spinner,
|
||||||
|
|
@ -44,6 +44,33 @@ export default function MCPConfigDialog({
|
||||||
? localize('com_ui_configure_mcp_variables_for', { 0: serverName })
|
? localize('com_ui_configure_mcp_variables_for', { 0: serverName })
|
||||||
: `${serverName} MCP Server`;
|
: `${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
|
// Helper function to render status badge based on connection state
|
||||||
const renderStatusBadge = () => {
|
const renderStatusBadge = () => {
|
||||||
if (!serverStatus) {
|
if (!serverStatus) {
|
||||||
|
|
@ -102,7 +129,10 @@ export default function MCPConfigDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
|
<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>
|
<OGDialogHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<OGDialogTitle className="text-xl">
|
<OGDialogTitle className="text-xl">
|
||||||
|
|
|
||||||
|
|
@ -789,6 +789,7 @@
|
||||||
"com_ui_complete_setup": "Complete Setup",
|
"com_ui_complete_setup": "Complete Setup",
|
||||||
"com_ui_concise": "Concise",
|
"com_ui_concise": "Concise",
|
||||||
"com_ui_configure_mcp_variables_for": "Configure Variables for {{0}}",
|
"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": "Confirm",
|
||||||
"com_ui_confirm_action": "Confirm Action",
|
"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?",
|
"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(
|
className={cn(
|
||||||
'flex items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',
|
'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',
|
'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,
|
selectClassName,
|
||||||
selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName,
|
selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName,
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue