chat / client /src /components /SidePanel /Agents /UninitializedMCPTool.tsx
helloya20's picture
Upload 2345 files
f0743f4 verified
import React, { useState } from 'react';
import { Label, OGDialog, TrashIcon, OGDialogTrigger, OGDialogTemplate } from '@librechat/client';
import type { MCPServerInfo } from '~/common';
import { useLocalize, useMCPServerManager, useRemoveMCPTool } from '~/hooks';
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
import { cn } from '~/utils';
export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
const localize = useLocalize();
const { removeTool } = useRemoveMCPTool();
const [isFocused, setIsFocused] = useState(false);
const [isHovering, setIsHovering] = useState(false);
const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } =
useMCPServerManager();
if (!serverInfo) {
return null;
}
const serverName = serverInfo.serverName;
const isServerInitializing = isInitializing(serverName);
const statusIconProps = getServerStatusIconProps(serverName);
const configDialogProps = getConfigDialogProps();
const statusIcon = statusIconProps && (
<div
onClick={(e) => {
e.stopPropagation();
}}
className="cursor-pointer rounded p-0.5 hover:bg-surface-secondary"
>
<MCPServerStatusIcon {...statusIconProps} />
</div>
);
return (
<OGDialog>
<div
className="group relative flex w-full items-center gap-1 rounded-lg p-1 text-sm hover:bg-surface-primary-alt"
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
onFocus={() => setIsFocused(true)}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
setIsFocused(false);
}
}}
>
<div
className="flex grow cursor-pointer items-center gap-1 rounded bg-transparent p-0 text-left transition-colors"
onClick={(e) => {
if ((e.target as HTMLElement).closest('[data-status-icon]')) {
return;
}
if (!isServerInitializing) {
initializeServer(serverName);
}
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (!isServerInitializing) {
initializeServer(serverName);
}
}
}}
aria-disabled={isServerInitializing}
>
{statusIcon && (
<div className="flex items-center" data-status-icon>
{statusIcon}
</div>
)}
{serverInfo.metadata.icon && (
<div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full">
<div
className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full bg-center bg-no-repeat dark:bg-white/20"
style={{
backgroundImage: `url(${serverInfo.metadata.icon})`,
backgroundSize: 'cover',
}}
/>
</div>
)}
<div
className="grow px-2 py-1.5"
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
>
{serverName}
{isServerInitializing && (
<span className="ml-2 text-xs text-text-secondary">
{localize('com_ui_initializing')}
</span>
)}
</div>
</div>
<OGDialogTrigger asChild>
<button
type="button"
className={cn(
'flex h-7 w-7 items-center justify-center rounded transition-all duration-200 hover:bg-surface-active-alt focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
isHovering || isFocused ? 'opacity-100' : 'pointer-events-none opacity-0',
)}
aria-label={`Delete ${serverName}`}
tabIndex={0}
onFocus={() => setIsFocused(true)}
>
<TrashIcon className="h-4 w-4" />
</button>
</OGDialogTrigger>
</div>
<OGDialogTemplate
showCloseButton={false}
title={localize('com_ui_delete_tool')}
mainClassName="px-0"
className="max-w-[450px]"
main={
<Label className="text-left text-sm font-medium">
{localize('com_ui_delete_tool_confirm')}
</Label>
}
selection={{
selectHandler: () => removeTool(serverName),
selectClasses:
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
selectText: localize('com_ui_delete'),
}}
/>
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}
</OGDialog>
);
}