mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-10 20:48:54 +01:00
* refactor: useMCPSelect
- Add useGetMCPTools to use in useMCPSelect and elsewhere hooks for fetching MCP tools
- remove memoized key
- remove use of `useChatContext` and require conversationId as prop
* feat: Add MCPPanelContext and integrate conversationId as prop for useMCPSelect across components
- Introduced MCPPanelContext to manage conversationId state.
- Updated MCPSelect, MCPSubMenu, and MCPConfigDialog to accept conversationId as a prop.
- Modified ToolsDropdown and BadgeRow to pass conversationId to relevant components.
- Refactored MCPPanel to utilize MCPPanelProvider for context management.
* fix: remove nested ternary in ServerInitializationSection
- Replaced conditional operator with if-else statements for better readability in determining button text based on server initialization state and reinitialization status.
* refactor: wrap setValueWrap in useCallback for performance optimization
* refactor: streamline useMCPSelect by consolidating storageKey definition
* fix: prevent clearing selections on page refresh by tracking initial load completion
* refactor: simplify concern of useMCPSelect hook
* refactor: move ConfigFieldDetail interface to common types for better reusability, isolate usage of `useGetMCPTools`
* refactor: integrate mcpServerNames into BadgeRowContext and update ToolsDropdown and MCPSelect components
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { useForm, Controller } from 'react-hook-form';
|
|
import { Button, Input, Label, OGDialog, OGDialogTemplate } from '@librechat/client';
|
|
import type { ConfigFieldDetail } from '~/common';
|
|
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;
|
|
}
|
|
|
|
export default function MCPConfigDialog({
|
|
isOpen,
|
|
onOpenChange,
|
|
fieldsSchema,
|
|
initialValues,
|
|
onSave,
|
|
isSubmitting = false,
|
|
onRevoke,
|
|
serverName,
|
|
}: MCPConfigDialogProps) {
|
|
const localize = useLocalize();
|
|
const {
|
|
control,
|
|
handleSubmit,
|
|
reset,
|
|
formState: { errors },
|
|
} = useForm<Record<string, string>>({
|
|
defaultValues: initialValues,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
reset(initialValues);
|
|
}
|
|
}, [isOpen, initialValues, reset]);
|
|
|
|
const onFormSubmit = (data: Record<string, string>) => {
|
|
onSave(data);
|
|
};
|
|
|
|
const handleRevoke = () => {
|
|
if (onRevoke) {
|
|
onRevoke();
|
|
}
|
|
};
|
|
|
|
const dialogTitle = localize('com_ui_configure_mcp_variables_for', { 0: serverName });
|
|
|
|
return (
|
|
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
|
|
<OGDialogTemplate
|
|
className="sm:max-w-lg"
|
|
title={dialogTitle}
|
|
headerClassName="px-6 pt-6 pb-4"
|
|
main={
|
|
<form onSubmit={handleSubmit(onFormSubmit)} className="space-y-4 px-6 pb-2">
|
|
{Object.entries(fieldsSchema).map(([key, details]) => (
|
|
<div key={key} className="space-y-2">
|
|
<Label htmlFor={key} className="text-sm font-medium">
|
|
{details.title}
|
|
</Label>
|
|
<Controller
|
|
name={key}
|
|
control={control}
|
|
defaultValue={initialValues[key] || ''}
|
|
render={({ field }) => (
|
|
<Input
|
|
id={key}
|
|
type="text"
|
|
{...field}
|
|
placeholder={localize('com_ui_mcp_enter_var', { 0: details.title })}
|
|
className="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white sm:text-sm"
|
|
/>
|
|
)}
|
|
/>
|
|
{details.description && (
|
|
<p
|
|
className="text-xs text-text-secondary [&_a]:text-blue-500 [&_a]:hover:text-blue-600 dark:[&_a]:text-blue-400 dark:[&_a]:hover:text-blue-300"
|
|
dangerouslySetInnerHTML={{ __html: details.description }}
|
|
/>
|
|
)}
|
|
{errors[key] && <p className="text-xs text-red-500">{errors[key]?.message}</p>}
|
|
</div>
|
|
))}
|
|
</form>
|
|
}
|
|
selection={{
|
|
selectHandler: handleSubmit(onFormSubmit),
|
|
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
|
|
selectText: isSubmitting ? localize('com_ui_saving') : localize('com_ui_save'),
|
|
}}
|
|
buttons={
|
|
onRevoke && (
|
|
<Button
|
|
onClick={handleRevoke}
|
|
className="bg-red-600 text-white hover:bg-red-700 dark:hover:bg-red-800"
|
|
disabled={isSubmitting}
|
|
>
|
|
{localize('com_ui_revoke')}
|
|
</Button>
|
|
)
|
|
}
|
|
footerClassName="flex justify-end gap-2 px-6 pb-6 pt-2"
|
|
showCancelButton={true}
|
|
/>
|
|
</OGDialog>
|
|
);
|
|
}
|