mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-11 00:33:40 +00:00
refactor: dry out renderIcon to renderMCPIcon reusable component
This commit is contained in:
parent
3c142b64e9
commit
c8dffd3f07
6 changed files with 98 additions and 101 deletions
|
|
@ -1,16 +1,15 @@
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { Check } from 'lucide-react';
|
||||
import { MCPIcon } from '@librechat/client';
|
||||
import type { MCPServerDefinition } from '~/hooks/MCP/useMCPServerManager';
|
||||
import type { MCPServerStatusIconProps } from './MCPServerStatusIcon';
|
||||
import MCPServerStatusIcon from './MCPServerStatusIcon';
|
||||
import ClickHouseIcon from './ClickHouseIcon';
|
||||
import {
|
||||
getStatusColor,
|
||||
getStatusTextKey,
|
||||
shouldShowActionButton,
|
||||
type ConnectionStatusMap,
|
||||
} from './mcpServerUtils';
|
||||
import { renderMCPIcon } from './renderMCPIcon';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -41,28 +40,6 @@ export default function MCPServerMenuItem({
|
|||
// Include status in aria-label so screen readers announce it
|
||||
const accessibleLabel = `${displayName}, ${statusText}`;
|
||||
|
||||
const renderIcon = () => {
|
||||
if (server.config?.iconPath) {
|
||||
return (
|
||||
<img
|
||||
src={server.config.iconPath}
|
||||
className="h-8 w-8 rounded-lg object-cover"
|
||||
alt={displayName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (server.serverName.toLowerCase().includes('clickhouse')) {
|
||||
return <ClickHouseIcon className="h-8 w-8 rounded-lg object-cover" alt={displayName} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-surface-tertiary">
|
||||
<MCPIcon className="h-5 w-5 text-text-secondary" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Ariakit.MenuItemCheckbox
|
||||
hideOnClick={false}
|
||||
|
|
@ -81,7 +58,13 @@ export default function MCPServerMenuItem({
|
|||
>
|
||||
{/* Server Icon with Status Dot */}
|
||||
<div className="relative flex-shrink-0">
|
||||
{renderIcon()}
|
||||
{renderMCPIcon({
|
||||
iconPath: server.config?.iconPath,
|
||||
serverName: server.serverName,
|
||||
displayName,
|
||||
className: 'h-8 w-8 rounded-lg object-cover',
|
||||
wrapDefault: true,
|
||||
})}
|
||||
{/* Status dot - decorative, status is announced via aria-label on MenuItem */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||
import { MCPIcon } from '@librechat/client';
|
||||
import type { MCPServerDefinition } from '~/hooks/MCP/useMCPServerManager';
|
||||
import { getSelectedServerIcons } from './mcpServerUtils';
|
||||
import ClickHouseIcon from './ClickHouseIcon';
|
||||
import { renderMCPIcon } from './renderMCPIcon';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface StackedMCPIconsProps {
|
||||
|
|
@ -59,29 +59,6 @@ export default function StackedMCPIcons({
|
|||
const sizes = sizeConfig[iconSize];
|
||||
const colors = variantConfig[variant];
|
||||
|
||||
const renderIcon = (icon: { iconPath: string | null; displayName: string }) => {
|
||||
if (icon.iconPath === 'clickhouse') {
|
||||
return (
|
||||
<ClickHouseIcon
|
||||
className={cn('rounded-full object-cover', sizes.icon)}
|
||||
alt={icon.displayName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (icon.iconPath) {
|
||||
return (
|
||||
<img
|
||||
src={icon.iconPath}
|
||||
alt={icon.displayName}
|
||||
className={cn('rounded-full object-cover', sizes.icon)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <MCPIcon className={cn('text-text-primary', sizes.icon)} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{icons.map((icon, index) => (
|
||||
|
|
@ -97,7 +74,12 @@ export default function StackedMCPIcons({
|
|||
)}
|
||||
style={{ zIndex: icons.length - index }}
|
||||
>
|
||||
{renderIcon(icon)}
|
||||
{renderMCPIcon({
|
||||
iconPath: icon.iconPath,
|
||||
serverName: icon.serverName,
|
||||
displayName: icon.displayName,
|
||||
className: cn('rounded-full object-cover', sizes.icon),
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
{overflowCount > 0 && (
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function getSelectedServerIcons(
|
|||
clickhouseServers.push({
|
||||
key: server.serverName,
|
||||
serverName: server.serverName,
|
||||
iconPath: 'clickhouse',
|
||||
iconPath: null,
|
||||
displayName,
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
60
client/src/components/MCP/renderMCPIcon.tsx
Normal file
60
client/src/components/MCP/renderMCPIcon.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { MCPIcon } from '@librechat/client';
|
||||
import ClickHouseIcon from './ClickHouseIcon';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export interface RenderMCPIconOptions {
|
||||
iconPath?: string | null;
|
||||
serverName?: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
fallbackIcon?: ReactNode;
|
||||
wrapDefault?: boolean;
|
||||
}
|
||||
|
||||
export function renderMCPIcon({
|
||||
iconPath,
|
||||
serverName,
|
||||
name,
|
||||
displayName,
|
||||
className = 'h-8 w-8 rounded-lg object-cover',
|
||||
alt,
|
||||
fallbackIcon,
|
||||
wrapDefault = false,
|
||||
}: RenderMCPIconOptions): ReactNode {
|
||||
const altText = alt || displayName || name || serverName || '';
|
||||
const isClickHouse = serverName?.toLowerCase().includes('clickhouse') ?? false;
|
||||
|
||||
if (iconPath) {
|
||||
return <img src={iconPath} className={className} alt={altText} />;
|
||||
}
|
||||
|
||||
if (isClickHouse) {
|
||||
return <ClickHouseIcon className={className} alt={altText} />;
|
||||
}
|
||||
|
||||
if (fallbackIcon) {
|
||||
return fallbackIcon;
|
||||
}
|
||||
|
||||
const defaultIcon = <MCPIcon className={cn('text-text-secondary', className)} />;
|
||||
|
||||
if (wrapDefault) {
|
||||
return (
|
||||
<div
|
||||
className={cn('flex items-center justify-center rounded-lg bg-surface-tertiary', className)}
|
||||
>
|
||||
<MCPIcon
|
||||
className={cn(
|
||||
'h-5 w-5 text-text-secondary',
|
||||
className.includes('size-8') ? 'size-5' : '',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return defaultIcon;
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { useState, useRef } from 'react';
|
||||
import { MCPIcon } from '@librechat/client';
|
||||
import { PermissionBits, hasPermissions } from 'librechat-data-provider';
|
||||
import type { MCPServerStatusIconProps } from '~/components/MCP/MCPServerStatusIcon';
|
||||
import ClickHouseIcon from '~/components/MCP/ClickHouseIcon';
|
||||
import { renderMCPIcon } from '~/components/MCP/renderMCPIcon';
|
||||
import type { MCPServerDefinition } from '~/hooks';
|
||||
import { getStatusDotColor } from './MCPStatusBadge';
|
||||
import MCPServerDialog from './MCPServerDialog';
|
||||
|
|
@ -71,29 +70,6 @@ export default function MCPServerCard({
|
|||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const renderIcon = () => {
|
||||
if (server.config?.iconPath) {
|
||||
return (
|
||||
<img
|
||||
src={server.config.iconPath}
|
||||
className="size-8 rounded-lg object-cover"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (server.serverName.toLowerCase().includes('clickhouse')) {
|
||||
return <ClickHouseIcon className="size-8 rounded-lg object-cover" alt="" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-8 items-center justify-center rounded-lg bg-surface-tertiary">
|
||||
<MCPIcon className="size-5 text-text-secondary" aria-hidden="true" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Determine status text for accessibility
|
||||
const getStatusText = () => {
|
||||
if (isInitializing) return localize('com_nav_mcp_status_initializing');
|
||||
|
|
@ -121,7 +97,14 @@ export default function MCPServerCard({
|
|||
>
|
||||
{/* Server Icon with Status Dot */}
|
||||
<div className="relative flex-shrink-0">
|
||||
{renderIcon()}
|
||||
{renderMCPIcon({
|
||||
iconPath: server.config?.iconPath,
|
||||
serverName: server.serverName,
|
||||
displayName,
|
||||
className: 'size-8 rounded-lg object-cover',
|
||||
alt: '',
|
||||
wrapDefault: true,
|
||||
})}
|
||||
{/* Status dot - color indicates connection state */}
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { XCircle, PlusCircleIcon, Wrench } from 'lucide-react';
|
||||
import type { AgentToolType } from 'librechat-data-provider';
|
||||
import ClickHouseIcon from '~/components/MCP/ClickHouseIcon';
|
||||
import { renderMCPIcon } from '~/components/MCP/renderMCPIcon';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type MCPToolItemProps = {
|
||||
|
|
@ -72,34 +72,23 @@ function MCPToolItem({
|
|||
|
||||
const buttonState = getButtonState();
|
||||
|
||||
const renderIcon = () => {
|
||||
if (icon) {
|
||||
return (
|
||||
<img
|
||||
src={icon}
|
||||
alt={localize('com_ui_logo', { 0: name })}
|
||||
className="h-full w-full rounded-[5px] bg-white"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name.toLowerCase().includes('clickhouse')) {
|
||||
return <ClickHouseIcon className="h-full w-full rounded-[5px] object-cover" alt={name} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-[5px] border border-border-medium bg-transparent">
|
||||
<Wrench className="h-8 w-8 text-text-secondary" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded border border-border-medium bg-transparent p-6">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[70px] w-[70px] shrink-0">
|
||||
<div className="relative h-full w-full">
|
||||
{renderIcon()}
|
||||
{renderMCPIcon({
|
||||
iconPath: icon,
|
||||
serverName: name,
|
||||
displayName: name,
|
||||
className: 'h-full w-full rounded-[5px] object-cover',
|
||||
alt: localize('com_ui_logo', { 0: name }),
|
||||
fallbackIcon: (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-[5px] border border-border-medium bg-transparent">
|
||||
<Wrench className="h-8 w-8 text-text-secondary" />
|
||||
</div>
|
||||
),
|
||||
})}
|
||||
<div className="absolute inset-0 rounded-[5px] ring-1 ring-inset ring-black/10"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue