mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40: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 FileContext from './FileContext';
|
||||
import SearchForm from './Search/Form';
|
||||
import MCPSection from './MCPSection';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import FileSearch from './FileSearch';
|
||||
import Artifacts from './Artifacts';
|
||||
|
|
@ -355,7 +356,7 @@ export default function AgentConfig({
|
|||
</div>
|
||||
</div>
|
||||
{/* MCP Section */}
|
||||
{/* <MCPSection /> */}
|
||||
<MCPSection />
|
||||
</div>
|
||||
<ToolSelectDialog
|
||||
isOpen={showToolDialog}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,229 @@
|
|||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import ActionsAuth from '~/components/SidePanel/Builder/ActionsAuth';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Plus, Trash2, CirclePlus } from 'lucide-react';
|
||||
import * as Menu from '@ariakit/react/menu';
|
||||
import {
|
||||
AuthorizationTypeEnum,
|
||||
TokenExchangeMethodEnum,
|
||||
AuthTypeEnum,
|
||||
} from 'librechat-data-provider';
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
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() {
|
||||
// Create a separate form for auth
|
||||
const authMethods = useForm({
|
||||
defaultValues: {
|
||||
/* 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 localize = useLocalize();
|
||||
const { register, watch, setValue } = useFormContext();
|
||||
const [isHeadersMenuOpen, setIsHeadersMenuOpen] = useState(false);
|
||||
|
||||
const { watch, setValue } = authMethods;
|
||||
const type = watch('type');
|
||||
const customHeaders = watch('customHeaders') || [];
|
||||
const requestTimeout = watch('requestTimeout') || '';
|
||||
const connectionTimeout = watch('connectionTimeout') || '';
|
||||
|
||||
// Sync form state when auth type changes
|
||||
useEffect(() => {
|
||||
if (type === 'none') {
|
||||
// Reset auth fields when type is none
|
||||
setValue('api_key', '');
|
||||
setValue('authorization_type', AuthorizationTypeEnum.Basic);
|
||||
setValue('custom_auth_header', '');
|
||||
setValue('oauth_client_id', '');
|
||||
setValue('oauth_client_secret', '');
|
||||
setValue('authorization_url', '');
|
||||
setValue('client_url', '');
|
||||
setValue('scope', '');
|
||||
setValue('token_exchange_method', TokenExchangeMethodEnum.DefaultPost);
|
||||
}
|
||||
}, [type, setValue]);
|
||||
const addCustomHeader = () => {
|
||||
const newHeader = {
|
||||
id: Date.now().toString(),
|
||||
name: '',
|
||||
value: '',
|
||||
};
|
||||
setValue('customHeaders', [...customHeaders, newHeader]);
|
||||
};
|
||||
|
||||
const removeCustomHeader = (id: string) => {
|
||||
setValue(
|
||||
'customHeaders',
|
||||
customHeaders.filter((header: any) => header.id !== id),
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<FormProvider {...authMethods}>
|
||||
<ActionsAuth />
|
||||
</FormProvider>
|
||||
<div className="space-y-4">
|
||||
{/* Authentication Accordion */}
|
||||
<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_servers": "MCP Servers",
|
||||
"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_allow_create": "Allow creating 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