mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-26 04:06:12 +01:00
* 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
164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { KeyRound, PlugZap, AlertTriangle } from 'lucide-react';
|
|
import {
|
|
Spinner,
|
|
OGDialog,
|
|
OGDialogTitle,
|
|
OGDialogHeader,
|
|
OGDialogContent,
|
|
} from '@librechat/client';
|
|
import type { MCPServerStatus } from 'librechat-data-provider';
|
|
import type { ConfigFieldDetail } from '~/common';
|
|
import ServerInitializationSection from './ServerInitializationSection';
|
|
import CustomUserVarsSection from './CustomUserVarsSection';
|
|
import { useLocalize } from '~/hooks';
|
|
|
|
interface MCPConfigDialogProps {
|
|
isOpen: boolean;
|
|
onOpenChange: (isOpen: boolean) => void;
|
|
fieldsSchema: Record<string, ConfigFieldDetail>;
|
|
initialValues: Record<string, string>;
|
|
onSave: (updatedValues: Record<string, string>) => void;
|
|
isSubmitting?: boolean;
|
|
onRevoke?: () => void;
|
|
serverName: string;
|
|
serverStatus?: MCPServerStatus;
|
|
conversationId?: string | null;
|
|
}
|
|
|
|
export default function MCPConfigDialog({
|
|
isOpen,
|
|
onOpenChange,
|
|
fieldsSchema,
|
|
onSave,
|
|
isSubmitting = false,
|
|
onRevoke,
|
|
serverName,
|
|
serverStatus,
|
|
conversationId,
|
|
}: MCPConfigDialogProps) {
|
|
const localize = useLocalize();
|
|
|
|
const hasFields = Object.keys(fieldsSchema).length > 0;
|
|
const dialogTitle = hasFields
|
|
? localize('com_ui_configure_mcp_variables_for', { 0: serverName })
|
|
: `${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
|
|
const renderStatusBadge = () => {
|
|
if (!serverStatus) {
|
|
return null;
|
|
}
|
|
|
|
const { connectionState, requiresOAuth } = serverStatus;
|
|
|
|
if (connectionState === 'connecting') {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-full bg-blue-50 px-2 py-0.5 text-xs font-medium text-blue-600 dark:bg-blue-950 dark:text-blue-400">
|
|
<Spinner className="h-3 w-3" />
|
|
<span>{localize('com_ui_connecting')}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (connectionState === 'disconnected') {
|
|
if (requiresOAuth) {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-600 dark:bg-amber-950 dark:text-amber-400">
|
|
<KeyRound className="h-3 w-3" aria-hidden="true" />
|
|
<span>{localize('com_ui_oauth')}</span>
|
|
</div>
|
|
);
|
|
} else {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-full bg-orange-50 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-950 dark:text-orange-400">
|
|
<PlugZap className="h-3 w-3" aria-hidden="true" />
|
|
<span>{localize('com_ui_offline')}</span>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (connectionState === 'error') {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-full bg-red-50 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-950 dark:text-red-400">
|
|
<AlertTriangle className="h-3 w-3" aria-hidden="true" />
|
|
<span>{localize('com_ui_error')}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (connectionState === 'connected') {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700 dark:bg-green-900 dark:text-green-300">
|
|
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
|
<span>{localize('com_ui_active')}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
return (
|
|
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
|
|
<OGDialogContent
|
|
className="flex max-h-screen w-11/12 max-w-lg flex-col space-y-2"
|
|
title={fullTitle}
|
|
>
|
|
<OGDialogHeader>
|
|
<div className="flex items-center gap-3">
|
|
<OGDialogTitle className="text-xl">
|
|
{dialogTitle.charAt(0).toUpperCase() + dialogTitle.slice(1)}
|
|
</OGDialogTitle>
|
|
{renderStatusBadge()}
|
|
</div>
|
|
</OGDialogHeader>
|
|
|
|
{/* Custom User Variables Section */}
|
|
<CustomUserVarsSection
|
|
serverName={serverName}
|
|
fields={fieldsSchema}
|
|
onSave={onSave}
|
|
onRevoke={onRevoke || (() => {})}
|
|
isSubmitting={isSubmitting}
|
|
/>
|
|
|
|
{/* Server Initialization Section */}
|
|
<ServerInitializationSection
|
|
serverName={serverName}
|
|
conversationId={conversationId}
|
|
requiresOAuth={serverStatus?.requiresOAuth || false}
|
|
hasCustomUserVars={fieldsSchema && Object.keys(fieldsSchema).length > 0}
|
|
/>
|
|
</OGDialogContent>
|
|
</OGDialog>
|
|
);
|
|
}
|