mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-17 07:55:32 +01:00
184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
||
|
|
import { useFormContext } from 'react-hook-form';
|
||
|
|
import { Constants } from 'librechat-data-provider';
|
||
|
|
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||
|
|
import {
|
||
|
|
Label,
|
||
|
|
OGDialog,
|
||
|
|
TrashIcon,
|
||
|
|
OGDialogTrigger,
|
||
|
|
useToastContext,
|
||
|
|
OGDialogTemplate,
|
||
|
|
} from '@librechat/client';
|
||
|
|
import type { AgentForm, MCPServerInfo } from '~/common';
|
||
|
|
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
|
||
|
|
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
|
||
|
|
import { useLocalize, useMCPServerManager } from '~/hooks';
|
||
|
|
import { cn } from '~/utils';
|
||
|
|
|
||
|
|
export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
|
||
|
|
const [isFocused, setIsFocused] = useState(false);
|
||
|
|
const [isHovering, setIsHovering] = useState(false);
|
||
|
|
|
||
|
|
const localize = useLocalize();
|
||
|
|
const { showToast } = useToastContext();
|
||
|
|
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||
|
|
const { getValues, setValue } = useFormContext<AgentForm>();
|
||
|
|
const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } =
|
||
|
|
useMCPServerManager();
|
||
|
|
|
||
|
|
if (!serverInfo) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const removeTool = (serverName: string) => {
|
||
|
|
if (!serverName) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
updateUserPlugins.mutate(
|
||
|
|
{
|
||
|
|
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||
|
|
action: 'uninstall',
|
||
|
|
auth: {},
|
||
|
|
isEntityTool: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
onError: (error: unknown) => {
|
||
|
|
showToast({
|
||
|
|
message: localize('com_ui_delete_tool_error', { error: String(error) }),
|
||
|
|
status: 'error',
|
||
|
|
});
|
||
|
|
},
|
||
|
|
onSuccess: () => {
|
||
|
|
const currentTools = getValues('tools');
|
||
|
|
const remainingToolIds =
|
||
|
|
currentTools?.filter(
|
||
|
|
(currentToolId) =>
|
||
|
|
currentToolId !== serverName &&
|
||
|
|
!currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||
|
|
) || [];
|
||
|
|
setValue('tools', remainingToolIds);
|
||
|
|
showToast({ message: localize('com_ui_delete_tool_success'), status: 'success' });
|
||
|
|
},
|
||
|
|
},
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
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>
|
||
|
|
);
|
||
|
|
}
|