mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🧰 feat: Accessible MCP Tool Lists (#10695)
* feat: add aria-label for expansion chevron in Agent Builder's MCP tool list dropdown * fix: remove duplicate tool info button in MCPTool so it doesn't get picked up via keyboard nav (still exists on mouse hover as it should to provide tooltip description of tool) * feat: use InfoHoverCard rather than Ariakit components for tool descriptions * chore: remove unused i18n keys
This commit is contained in:
parent
d971486418
commit
c81bdeecb7
2 changed files with 20 additions and 57 deletions
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import * as Ariakit from '@ariakit/react';
|
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { Constants } from 'librechat-data-provider';
|
import { Constants } from 'librechat-data-provider';
|
||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import {
|
import {
|
||||||
Label,
|
Label,
|
||||||
|
ESide,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
OGDialog,
|
OGDialog,
|
||||||
Accordion,
|
Accordion,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
InfoHoverCard,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
CircleHelpIcon,
|
|
||||||
OGDialogTrigger,
|
OGDialogTrigger,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
OGDialogTemplate,
|
OGDialogTemplate,
|
||||||
|
|
@ -31,7 +31,6 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
const [accordionValue, setAccordionValue] = useState<string>('');
|
const [accordionValue, setAccordionValue] = useState<string>('');
|
||||||
const [hoveredToolId, setHoveredToolId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
if (!serverInfo) {
|
if (!serverInfo) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -183,7 +182,16 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
|
||||||
'flex h-7 w-7 items-center justify-center rounded transition-colors duration-200 hover:bg-surface-active-alt focus:translate-x-0 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
'flex h-7 w-7 items-center justify-center rounded transition-colors duration-200 hover:bg-surface-active-alt focus:translate-x-0 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||||
isExpanded && 'bg-surface-active-alt',
|
isExpanded && 'bg-surface-active-alt',
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-label={
|
||||||
|
isExpanded
|
||||||
|
? localize('com_ui_tool_list_collapse', {
|
||||||
|
serverName: currentServerName,
|
||||||
|
})
|
||||||
|
: localize('com_ui_tool_list_expand', {
|
||||||
|
serverName: currentServerName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
>
|
>
|
||||||
|
|
@ -227,15 +235,13 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
|
||||||
key={subTool.tool_id}
|
key={subTool.tool_id}
|
||||||
htmlFor={subTool.tool_id}
|
htmlFor={subTool.tool_id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-token-border-light hover:bg-token-surface-secondary flex cursor-pointer items-center rounded-lg border p-2',
|
'group/item border-token-border-light hover:bg-token-surface-secondary flex cursor-pointer items-center rounded-lg border p-2',
|
||||||
'ml-2 mr-1 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
|
'ml-2 mr-1 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
|
||||||
)}
|
)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredToolId(subTool.tool_id)}
|
|
||||||
onMouseLeave={() => setHoveredToolId(null)}
|
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={subTool.tool_id}
|
id={subTool.tool_id}
|
||||||
|
|
@ -264,54 +270,9 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
|
||||||
{subTool.metadata.name}
|
{subTool.metadata.name}
|
||||||
</span>
|
</span>
|
||||||
{subTool.metadata.description && (
|
{subTool.metadata.description && (
|
||||||
<Ariakit.HovercardProvider placement="left-start">
|
<div className="ml-auto flex items-center opacity-0 transition-opacity duration-200 group-focus-within/item:opacity-100 group-hover/item:opacity-100">
|
||||||
<div className="ml-auto flex h-6 w-6 items-center justify-center">
|
<InfoHoverCard side={ESide.Left} text={subTool.metadata.description} />
|
||||||
<Ariakit.HovercardAnchor
|
</div>
|
||||||
render={
|
|
||||||
<Ariakit.Button
|
|
||||||
className={cn(
|
|
||||||
'flex h-5 w-5 cursor-help items-center rounded-full text-text-secondary transition-opacity duration-200',
|
|
||||||
hoveredToolId === subTool.tool_id ? 'opacity-100' : 'opacity-0',
|
|
||||||
)}
|
|
||||||
aria-label={localize('com_ui_tool_info')}
|
|
||||||
>
|
|
||||||
<CircleHelpIcon className="h-4 w-4" />
|
|
||||||
<Ariakit.VisuallyHidden>
|
|
||||||
{localize('com_ui_tool_info')}
|
|
||||||
</Ariakit.VisuallyHidden>
|
|
||||||
</Ariakit.Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Ariakit.HovercardDisclosure
|
|
||||||
className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring"
|
|
||||||
aria-label={localize('com_ui_tool_more_info')}
|
|
||||||
aria-expanded={hoveredToolId === subTool.tool_id}
|
|
||||||
aria-controls={`tool-description-${subTool.tool_id}`}
|
|
||||||
>
|
|
||||||
<Ariakit.VisuallyHidden>
|
|
||||||
{localize('com_ui_tool_more_info')}
|
|
||||||
</Ariakit.VisuallyHidden>
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
</Ariakit.HovercardDisclosure>
|
|
||||||
</div>
|
|
||||||
<Ariakit.Hovercard
|
|
||||||
id={`tool-description-${subTool.tool_id}`}
|
|
||||||
gutter={14}
|
|
||||||
shift={40}
|
|
||||||
flip={false}
|
|
||||||
className="z-[999] w-80 scale-95 rounded-2xl border border-border-medium bg-surface-secondary p-4 text-text-primary opacity-0 shadow-md transition-all duration-200 data-[enter]:scale-100 data-[leave]:scale-95 data-[enter]:opacity-100 data-[leave]:opacity-0"
|
|
||||||
portal={true}
|
|
||||||
unmountOnHide={true}
|
|
||||||
role="tooltip"
|
|
||||||
aria-label={subTool.metadata.description}
|
|
||||||
>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-sm text-text-secondary">
|
|
||||||
{subTool.metadata.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Ariakit.Hovercard>
|
|
||||||
</Ariakit.HovercardProvider>
|
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -782,6 +782,7 @@
|
||||||
"com_ui_close_settings": "Close Settings",
|
"com_ui_close_settings": "Close Settings",
|
||||||
"com_ui_close_window": "Close Window",
|
"com_ui_close_window": "Close Window",
|
||||||
"com_ui_code": "Code",
|
"com_ui_code": "Code",
|
||||||
|
"com_ui_collapse": "Collapse",
|
||||||
"com_ui_collapse_chat": "Collapse Chat",
|
"com_ui_collapse_chat": "Collapse Chat",
|
||||||
"com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used",
|
"com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used",
|
||||||
"com_ui_command_usage_placeholder": "Select a Prompt by command or name",
|
"com_ui_command_usage_placeholder": "Select a Prompt by command or name",
|
||||||
|
|
@ -918,6 +919,7 @@
|
||||||
"com_ui_error_updating_preferences": "Error updating preferences",
|
"com_ui_error_updating_preferences": "Error updating preferences",
|
||||||
"com_ui_everyone_permission_level": "Everyone's permission level",
|
"com_ui_everyone_permission_level": "Everyone's permission level",
|
||||||
"com_ui_examples": "Examples",
|
"com_ui_examples": "Examples",
|
||||||
|
"com_ui_expand": "Expand",
|
||||||
"com_ui_expand_chat": "Expand Chat",
|
"com_ui_expand_chat": "Expand Chat",
|
||||||
"com_ui_export_convo_modal": "Export Conversation Modal",
|
"com_ui_export_convo_modal": "Export Conversation Modal",
|
||||||
"com_ui_feedback_more": "More...",
|
"com_ui_feedback_more": "More...",
|
||||||
|
|
@ -1282,8 +1284,8 @@
|
||||||
"com_ui_token_url": "Token URL",
|
"com_ui_token_url": "Token URL",
|
||||||
"com_ui_tokens": "tokens",
|
"com_ui_tokens": "tokens",
|
||||||
"com_ui_tool_collection_prefix": "A collection of tools from",
|
"com_ui_tool_collection_prefix": "A collection of tools from",
|
||||||
"com_ui_tool_info": "Tool Information",
|
"com_ui_tool_list_collapse": "Collapse {{serverName}} tool list",
|
||||||
"com_ui_tool_more_info": "More information about this tool",
|
"com_ui_tool_list_expand": "Expand {{serverName}} tool list",
|
||||||
"com_ui_tools": "Tools",
|
"com_ui_tools": "Tools",
|
||||||
"com_ui_tools_and_actions": "Tools and Actions",
|
"com_ui_tools_and_actions": "Tools and Actions",
|
||||||
"com_ui_transferred_to": "Transferred to",
|
"com_ui_transferred_to": "Transferred to",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue