mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10:14 +01:00
auth revamp
This commit is contained in:
parent
cbda3cb529
commit
c5cd9eb359
3 changed files with 237 additions and 47 deletions
|
|
@ -12,6 +12,7 @@ import Instructions from './Instructions';
|
||||||
import AgentAvatar from './AgentAvatar';
|
import AgentAvatar from './AgentAvatar';
|
||||||
import FileContext from './FileContext';
|
import FileContext from './FileContext';
|
||||||
import SearchForm from './Search/Form';
|
import SearchForm from './Search/Form';
|
||||||
|
import MCPSection from './MCPSection';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import FileSearch from './FileSearch';
|
import FileSearch from './FileSearch';
|
||||||
import Artifacts from './Artifacts';
|
import Artifacts from './Artifacts';
|
||||||
|
|
@ -355,7 +356,7 @@ export default function AgentConfig({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* MCP Section */}
|
{/* MCP Section */}
|
||||||
{/* <MCPSection /> */}
|
<MCPSection />
|
||||||
</div>
|
</div>
|
||||||
<ToolSelectDialog
|
<ToolSelectDialog
|
||||||
isOpen={showToolDialog}
|
isOpen={showToolDialog}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,229 @@
|
||||||
import { useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import ActionsAuth from '~/components/SidePanel/Builder/ActionsAuth';
|
import { Plus, Trash2, CirclePlus } from 'lucide-react';
|
||||||
|
import * as Menu from '@ariakit/react/menu';
|
||||||
import {
|
import {
|
||||||
AuthorizationTypeEnum,
|
Accordion,
|
||||||
TokenExchangeMethodEnum,
|
AccordionContent,
|
||||||
AuthTypeEnum,
|
AccordionItem,
|
||||||
} from 'librechat-data-provider';
|
AccordionTrigger,
|
||||||
|
} from '~/components/ui/Accordion';
|
||||||
|
import { DropdownPopup } from '~/components/ui';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
interface UserInfoPlaceholder {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfoPlaceholders: UserInfoPlaceholder[] = [
|
||||||
|
{ label: 'user-id', value: '{{LIBRECHAT_USER_ID}}', description: 'Current user ID' },
|
||||||
|
{ label: 'username', value: '{{LIBRECHAT_USER_USERNAME}}', description: 'Current username' },
|
||||||
|
{ label: 'email', value: '{{LIBRECHAT_USER_EMAIL}}', description: 'Current user email' },
|
||||||
|
{ label: 'name', value: '{{LIBRECHAT_USER_NAME}}', description: 'Current user name' },
|
||||||
|
{
|
||||||
|
label: 'provider',
|
||||||
|
value: '{{LIBRECHAT_USER_PROVIDER}}',
|
||||||
|
description: 'Authentication provider',
|
||||||
|
},
|
||||||
|
{ label: 'role', value: '{{LIBRECHAT_USER_ROLE}}', description: 'User role' },
|
||||||
|
];
|
||||||
|
|
||||||
export default function MCPAuth() {
|
export default function MCPAuth() {
|
||||||
// Create a separate form for auth
|
const localize = useLocalize();
|
||||||
const authMethods = useForm({
|
const { register, watch, setValue } = useFormContext();
|
||||||
defaultValues: {
|
const [isHeadersMenuOpen, setIsHeadersMenuOpen] = useState(false);
|
||||||
/* General */
|
|
||||||
type: AuthTypeEnum.None,
|
|
||||||
saved_auth_fields: false,
|
|
||||||
/* API key */
|
|
||||||
api_key: '',
|
|
||||||
authorization_type: AuthorizationTypeEnum.Basic,
|
|
||||||
custom_auth_header: '',
|
|
||||||
/* OAuth */
|
|
||||||
oauth_client_id: '',
|
|
||||||
oauth_client_secret: '',
|
|
||||||
authorization_url: '',
|
|
||||||
client_url: '',
|
|
||||||
scope: '',
|
|
||||||
token_exchange_method: TokenExchangeMethodEnum.DefaultPost,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { watch, setValue } = authMethods;
|
const customHeaders = watch('customHeaders') || [];
|
||||||
const type = watch('type');
|
const requestTimeout = watch('requestTimeout') || '';
|
||||||
|
const connectionTimeout = watch('connectionTimeout') || '';
|
||||||
|
|
||||||
// Sync form state when auth type changes
|
const addCustomHeader = () => {
|
||||||
useEffect(() => {
|
const newHeader = {
|
||||||
if (type === 'none') {
|
id: Date.now().toString(),
|
||||||
// Reset auth fields when type is none
|
name: '',
|
||||||
setValue('api_key', '');
|
value: '',
|
||||||
setValue('authorization_type', AuthorizationTypeEnum.Basic);
|
};
|
||||||
setValue('custom_auth_header', '');
|
setValue('customHeaders', [...customHeaders, newHeader]);
|
||||||
setValue('oauth_client_id', '');
|
};
|
||||||
setValue('oauth_client_secret', '');
|
|
||||||
setValue('authorization_url', '');
|
const removeCustomHeader = (id: string) => {
|
||||||
setValue('client_url', '');
|
setValue(
|
||||||
setValue('scope', '');
|
'customHeaders',
|
||||||
setValue('token_exchange_method', TokenExchangeMethodEnum.DefaultPost);
|
customHeaders.filter((header: any) => header.id !== id),
|
||||||
}
|
);
|
||||||
}, [type, setValue]);
|
};
|
||||||
|
|
||||||
|
const updateCustomHeader = (id: string, field: 'name' | 'value', value: string) => {
|
||||||
|
setValue(
|
||||||
|
'customHeaders',
|
||||||
|
customHeaders.map((header: any) =>
|
||||||
|
header.id === id ? { ...header, [field]: value } : header,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddPlaceholder = (placeholder: UserInfoPlaceholder) => {
|
||||||
|
const newHeader = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
name: placeholder.label,
|
||||||
|
value: placeholder.value,
|
||||||
|
};
|
||||||
|
setValue('customHeaders', [...customHeaders, newHeader]);
|
||||||
|
setIsHeadersMenuOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerMenuItems = [
|
||||||
|
...userInfoPlaceholders.map((placeholder) => ({
|
||||||
|
label: `${placeholder.label} - ${placeholder.description}`,
|
||||||
|
onClick: () => handleAddPlaceholder(placeholder),
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...authMethods}>
|
<div className="space-y-4">
|
||||||
<ActionsAuth />
|
{/* Authentication Accordion */}
|
||||||
</FormProvider>
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
<AccordionItem value="authentication" className="rounded-lg border border-border-medium">
|
||||||
|
<AccordionTrigger className="px-4 py-3 text-sm font-medium hover:no-underline">
|
||||||
|
{localize('com_ui_authentication')}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pb-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Custom Headers Section - Individual Inputs Version */}
|
||||||
|
<div>
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<label className="text-sm font-medium text-text-primary">
|
||||||
|
{localize('com_ui_mcp_custom_headers')}
|
||||||
|
</label>
|
||||||
|
<DropdownPopup
|
||||||
|
menuId="headers-menu"
|
||||||
|
items={headerMenuItems}
|
||||||
|
isOpen={isHeadersMenuOpen}
|
||||||
|
setIsOpen={setIsHeadersMenuOpen}
|
||||||
|
trigger={
|
||||||
|
<Menu.MenuButton
|
||||||
|
onClick={() => setIsHeadersMenuOpen(!isHeadersMenuOpen)}
|
||||||
|
className="flex h-7 items-center gap-1 rounded-md border border-border-medium bg-surface-secondary px-2 py-0 text-xs text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
|
||||||
|
>
|
||||||
|
<CirclePlus className="mr-1 h-3 w-3 text-text-secondary" />
|
||||||
|
{localize('com_ui_mcp_headers')}
|
||||||
|
</Menu.MenuButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{customHeaders.length === 0 ? (
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<p className="min-w-0 flex-1 text-sm text-text-secondary">
|
||||||
|
{localize('com_ui_mcp_no_custom_headers')}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addCustomHeader}
|
||||||
|
className="flex h-7 shrink-0 items-center gap-1 rounded-md border border-border-medium bg-surface-secondary px-2 py-0 text-xs text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
|
||||||
|
>
|
||||||
|
<Plus className="h-3 w-3" />
|
||||||
|
{localize('com_ui_mcp_add_header')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{customHeaders.map((header: any) => (
|
||||||
|
<div key={header.id} className="flex min-w-0 gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={localize('com_ui_mcp_header_name')}
|
||||||
|
value={header.name}
|
||||||
|
onChange={(e) => updateCustomHeader(header.id, 'name', e.target.value)}
|
||||||
|
className="min-w-0 flex-1 rounded-md border border-border-medium bg-surface-primary px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={localize('com_ui_mcp_header_value')}
|
||||||
|
value={header.value}
|
||||||
|
onChange={(e) => updateCustomHeader(header.id, 'value', e.target.value)}
|
||||||
|
className="min-w-0 flex-1 rounded-md border border-border-medium bg-surface-primary px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeCustomHeader(header.id)}
|
||||||
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-border-medium bg-surface-primary text-text-secondary hover:bg-surface-secondary hover:text-text-primary"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{/* Add New Header Button */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addCustomHeader}
|
||||||
|
className="flex h-7 shrink-0 items-center gap-1 rounded-md border border-border-medium bg-surface-secondary px-2 py-0 text-xs text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
|
||||||
|
>
|
||||||
|
<Plus className="h-3 w-3" />
|
||||||
|
{localize('com_ui_mcp_add_header')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
{/* Configuration Accordion */}
|
||||||
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
<AccordionItem value="configuration" className="rounded-lg border border-border-medium">
|
||||||
|
<AccordionTrigger className="px-4 py-3 text-sm font-medium hover:no-underline">
|
||||||
|
{localize('com_ui_mcp_configuration')}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pb-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Request Timeout */}
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-text-primary">
|
||||||
|
{localize('com_ui_mcp_request_timeout')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1000"
|
||||||
|
max="300000"
|
||||||
|
placeholder="10000"
|
||||||
|
{...register('requestTimeout')}
|
||||||
|
className="h-9 w-full rounded-md border border-border-medium bg-surface-primary px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-text-secondary">
|
||||||
|
{localize('com_ui_mcp_request_timeout_description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Connection Timeout */}
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-text-primary">
|
||||||
|
{localize('com_ui_mcp_connection_timeout')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1000"
|
||||||
|
max="60000"
|
||||||
|
placeholder="10000"
|
||||||
|
{...register('connectionTimeout')}
|
||||||
|
className="h-9 w-full rounded-md border border-border-medium bg-surface-primary px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-text-secondary">
|
||||||
|
{localize('com_ui_mcp_connection_timeout_description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -827,6 +827,21 @@
|
||||||
"com_ui_mcp_server_not_found": "Server not found.",
|
"com_ui_mcp_server_not_found": "Server not found.",
|
||||||
"com_ui_mcp_servers": "MCP Servers",
|
"com_ui_mcp_servers": "MCP Servers",
|
||||||
"com_ui_mcp_url": "MCP Server URL",
|
"com_ui_mcp_url": "MCP Server URL",
|
||||||
|
"com_ui_mcp_custom_headers": "Custom Headers",
|
||||||
|
"com_ui_mcp_headers": "Headers",
|
||||||
|
"com_ui_mcp_no_custom_headers": "No custom headers configured",
|
||||||
|
"com_ui_mcp_add_header": "Add Header",
|
||||||
|
"com_ui_mcp_header_name": "Header Name",
|
||||||
|
"com_ui_mcp_header_value": "Header Value",
|
||||||
|
"com_ui_mcp_configuration": "Configuration",
|
||||||
|
"com_ui_mcp_request_timeout": "Request Timeout (ms)",
|
||||||
|
"com_ui_mcp_request_timeout_description": "Maximum time in milliseconds to wait for a response from the MCP server",
|
||||||
|
"com_ui_mcp_connection_timeout": "Connection Timeout (ms)",
|
||||||
|
"com_ui_mcp_connection_timeout_description": "Maximum time in milliseconds to establish connection to the MCP server",
|
||||||
|
"com_ui_mcp_reset_timeout_on_progress": "Reset Timeout on Progress",
|
||||||
|
"com_ui_mcp_reset_timeout_on_progress_description": "Reset the request timeout when progress is received",
|
||||||
|
"com_ui_mcp_max_total_timeout": "Maximum Total Timeout (ms)",
|
||||||
|
"com_ui_mcp_max_total_timeout_description": "Maximum total time in milliseconds allowed for the entire request including retries",
|
||||||
"com_ui_memories": "Memories",
|
"com_ui_memories": "Memories",
|
||||||
"com_ui_memories_allow_create": "Allow creating Memories",
|
"com_ui_memories_allow_create": "Allow creating Memories",
|
||||||
"com_ui_memories_allow_opt_out": "Allow users to opt out of Memories",
|
"com_ui_memories_allow_opt_out": "Allow users to opt out of Memories",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue