mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-24 11:24:10 +01:00
📂 refactor: Cleanup File Filtering Logic, Improve Validation (#10414)
* feat: add filterFilesByEndpointConfig to filter disabled file processing by provider * chore: explicit define of endpointFileConfig for better debugging * refactor: move `normalizeEndpointName` to data-provider as used app-wide * chore: remove overrideEndpoint from useFileHandling * refactor: improve endpoint file config selection * refactor: update filterFilesByEndpointConfig to accept structured parameters and improve endpoint file config handling * refactor: replace defaultFileConfig with getEndpointFileConfig for improved file configuration handling across components * test: add comprehensive unit tests for getEndpointFileConfig to validate endpoint configuration handling * refactor: streamline agent endpoint assignment and improve file filtering logic * feat: add error handling for disabled file uploads in endpoint configuration * refactor: update encodeAndFormat functions to accept structured parameters for provider and endpoint * refactor: streamline requestFiles handling in initializeAgent function * fix: getEndpointFileConfig partial config merging scenarios * refactor: enhance mergeWithDefault function to support document-supported providers with comprehensive MIME types * refactor: user-configured default file config in getEndpointFileConfig * fix: prevent file handling when endpoint is disabled and file is dragged to chat * refactor: move `getEndpointField` to `data-provider` and update usage across components and hooks * fix: prioritize endpointType based on agent.endpoint in file filtering logic * fix: prioritize agent.endpoint in file filtering logic and remove unnecessary endpointType defaulting
This commit is contained in:
parent
06c060b983
commit
2524d33362
62 changed files with 2352 additions and 290 deletions
|
|
@ -1,12 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
import type { ZodError } from 'zod';
|
||||
import type { TModelsConfig } from './types';
|
||||
import type { TEndpointsConfig, TModelsConfig, TConfig } from './types';
|
||||
import { EModelEndpoint, eModelEndpointSchema } from './schemas';
|
||||
import { specsConfigSchema, TSpecsConfig } from './models';
|
||||
import { fileConfigSchema } from './file-config';
|
||||
import { apiBaseUrl } from './api-endpoints';
|
||||
import { FileSources } from './types/files';
|
||||
import { MCPServersSchema } from './mcp';
|
||||
import { apiBaseUrl } from './api-endpoints';
|
||||
|
||||
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord', 'saml'];
|
||||
|
||||
|
|
@ -1737,3 +1737,24 @@ export const specialVariables = {
|
|||
};
|
||||
|
||||
export type TSpecialVarLabel = `com_ui_special_var_${keyof typeof specialVariables}`;
|
||||
|
||||
/**
|
||||
* Retrieves a specific field from the endpoints configuration for a given endpoint key.
|
||||
* Does not infer or default any endpoint type when absent.
|
||||
*/
|
||||
export function getEndpointField<
|
||||
K extends TConfig[keyof TConfig] extends never ? never : keyof TConfig,
|
||||
>(
|
||||
endpointsConfig: TEndpointsConfig | undefined | null,
|
||||
endpoint: EModelEndpoint | string | null | undefined,
|
||||
property: K,
|
||||
): TConfig[K] | undefined {
|
||||
if (!endpointsConfig || endpoint === null || endpoint === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const config = endpointsConfig[endpoint];
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
return config[property];
|
||||
}
|
||||
|
|
|
|||
1097
packages/data-provider/src/file-config.spec.ts
Normal file
1097
packages/data-provider/src/file-config.spec.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import { EModelEndpoint } from './schemas';
|
||||
import type { EndpointFileConfig, FileConfig } from './types/files';
|
||||
import { EModelEndpoint, isAgentsEndpoint, isDocumentSupportedProvider } from './schemas';
|
||||
import { normalizeEndpointName } from './utils';
|
||||
|
||||
export const supportsFiles = {
|
||||
[EModelEndpoint.openAI]: true,
|
||||
|
|
@ -331,9 +332,146 @@ export const convertStringsToRegex = (patterns: string[]): RegExp[] =>
|
|||
return acc;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Gets the appropriate endpoint file configuration with standardized lookup logic.
|
||||
*
|
||||
* @param params - Object containing fileConfig, endpoint, and optional conversationEndpoint
|
||||
* @param params.fileConfig - The merged file configuration
|
||||
* @param params.endpoint - The endpoint name to look up
|
||||
* @param params.conversationEndpoint - Optional conversation endpoint for additional context
|
||||
* @returns The endpoint file configuration or undefined
|
||||
*/
|
||||
/**
|
||||
* Merges an endpoint config with the default config to ensure all fields are populated.
|
||||
* For document-supported providers, uses the comprehensive MIME type list (includes videos/audio).
|
||||
*/
|
||||
function mergeWithDefault(
|
||||
endpointConfig: EndpointFileConfig,
|
||||
defaultConfig: EndpointFileConfig,
|
||||
endpoint?: string | null,
|
||||
): EndpointFileConfig {
|
||||
/** Use comprehensive MIME types for document-supported providers */
|
||||
const defaultMimeTypes = isDocumentSupportedProvider(endpoint)
|
||||
? supportedMimeTypes
|
||||
: defaultConfig.supportedMimeTypes;
|
||||
|
||||
return {
|
||||
disabled: endpointConfig.disabled ?? defaultConfig.disabled,
|
||||
fileLimit: endpointConfig.fileLimit ?? defaultConfig.fileLimit,
|
||||
fileSizeLimit: endpointConfig.fileSizeLimit ?? defaultConfig.fileSizeLimit,
|
||||
totalSizeLimit: endpointConfig.totalSizeLimit ?? defaultConfig.totalSizeLimit,
|
||||
supportedMimeTypes: endpointConfig.supportedMimeTypes ?? defaultMimeTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export function getEndpointFileConfig(params: {
|
||||
fileConfig?: FileConfig | null;
|
||||
endpoint?: string | null;
|
||||
endpointType?: string | null;
|
||||
}): EndpointFileConfig {
|
||||
const { fileConfig: mergedFileConfig, endpoint, endpointType } = params;
|
||||
|
||||
if (!mergedFileConfig?.endpoints) {
|
||||
return fileConfig.endpoints.default;
|
||||
}
|
||||
|
||||
/** Compute an effective default by merging user-configured default over the base default */
|
||||
const baseDefaultConfig = fileConfig.endpoints.default;
|
||||
const userDefaultConfig = mergedFileConfig.endpoints.default;
|
||||
const defaultConfig = userDefaultConfig
|
||||
? mergeWithDefault(userDefaultConfig, baseDefaultConfig, 'default')
|
||||
: baseDefaultConfig;
|
||||
|
||||
const normalizedEndpoint = normalizeEndpointName(endpoint ?? '');
|
||||
const standardEndpoints = new Set([
|
||||
'default',
|
||||
EModelEndpoint.agents,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.azureAssistants,
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.bedrock,
|
||||
]);
|
||||
|
||||
const normalizedEndpointType = normalizeEndpointName(endpointType ?? '');
|
||||
const isCustomEndpoint =
|
||||
endpointType === EModelEndpoint.custom ||
|
||||
(!standardEndpoints.has(normalizedEndpointType) &&
|
||||
normalizedEndpoint &&
|
||||
!standardEndpoints.has(normalizedEndpoint));
|
||||
|
||||
if (isCustomEndpoint) {
|
||||
/** 1. Check direct endpoint lookup (could be normalized or not) */
|
||||
if (endpoint && mergedFileConfig.endpoints[endpoint]) {
|
||||
return mergeWithDefault(mergedFileConfig.endpoints[endpoint], defaultConfig, endpoint);
|
||||
}
|
||||
/** 2. Check normalized endpoint lookup (skip standard endpoint keys) */
|
||||
for (const key in mergedFileConfig.endpoints) {
|
||||
if (!standardEndpoints.has(key) && normalizeEndpointName(key) === normalizedEndpoint) {
|
||||
return mergeWithDefault(mergedFileConfig.endpoints[key], defaultConfig, key);
|
||||
}
|
||||
}
|
||||
/** 3. Fallback to generic 'custom' config if any */
|
||||
if (mergedFileConfig.endpoints[EModelEndpoint.custom]) {
|
||||
return mergeWithDefault(
|
||||
mergedFileConfig.endpoints[EModelEndpoint.custom],
|
||||
defaultConfig,
|
||||
endpoint,
|
||||
);
|
||||
}
|
||||
/** 4. Fallback to 'agents' (all custom endpoints are non-assistants) */
|
||||
if (mergedFileConfig.endpoints[EModelEndpoint.agents]) {
|
||||
return mergeWithDefault(
|
||||
mergedFileConfig.endpoints[EModelEndpoint.agents],
|
||||
defaultConfig,
|
||||
endpoint,
|
||||
);
|
||||
}
|
||||
/** 5. Fallback to default */
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
/** Check endpointType first (most reliable for standard endpoints) */
|
||||
if (endpointType && mergedFileConfig.endpoints[endpointType]) {
|
||||
return mergeWithDefault(mergedFileConfig.endpoints[endpointType], defaultConfig, endpointType);
|
||||
}
|
||||
|
||||
/** Check direct endpoint lookup */
|
||||
if (endpoint && mergedFileConfig.endpoints[endpoint]) {
|
||||
return mergeWithDefault(mergedFileConfig.endpoints[endpoint], defaultConfig, endpoint);
|
||||
}
|
||||
|
||||
/** Check normalized endpoint */
|
||||
if (normalizedEndpoint && mergedFileConfig.endpoints[normalizedEndpoint]) {
|
||||
return mergeWithDefault(
|
||||
mergedFileConfig.endpoints[normalizedEndpoint],
|
||||
defaultConfig,
|
||||
normalizedEndpoint,
|
||||
);
|
||||
}
|
||||
|
||||
/** Fallback to agents if endpoint is explicitly agents */
|
||||
const isAgents = isAgentsEndpoint(normalizedEndpointType || normalizedEndpoint);
|
||||
if (isAgents && mergedFileConfig.endpoints[EModelEndpoint.agents]) {
|
||||
return mergeWithDefault(
|
||||
mergedFileConfig.endpoints[EModelEndpoint.agents],
|
||||
defaultConfig,
|
||||
EModelEndpoint.agents,
|
||||
);
|
||||
}
|
||||
|
||||
/** Return default config */
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | undefined): FileConfig {
|
||||
const mergedConfig: FileConfig = {
|
||||
...fileConfig,
|
||||
endpoints: {
|
||||
...fileConfig.endpoints,
|
||||
},
|
||||
ocr: {
|
||||
...fileConfig.ocr,
|
||||
supportedMimeTypes: fileConfig.ocr?.supportedMimeTypes || [],
|
||||
|
|
@ -398,8 +536,11 @@ export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | unde
|
|||
for (const key in dynamic.endpoints) {
|
||||
const dynamicEndpoint = (dynamic.endpoints as Record<string, EndpointFileConfig>)[key];
|
||||
|
||||
/** Deep copy the base endpoint config if it exists to prevent mutation */
|
||||
if (!mergedConfig.endpoints[key]) {
|
||||
mergedConfig.endpoints[key] = {};
|
||||
} else {
|
||||
mergedConfig.endpoints[key] = { ...mergedConfig.endpoints[key] };
|
||||
}
|
||||
|
||||
const mergedEndpoint = mergedConfig.endpoints[key];
|
||||
|
|
@ -428,6 +569,10 @@ export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | unde
|
|||
}
|
||||
});
|
||||
|
||||
if (dynamicEndpoint.disabled !== undefined) {
|
||||
mergedEndpoint.disabled = dynamicEndpoint.disabled;
|
||||
}
|
||||
|
||||
if (dynamicEndpoint.supportedMimeTypes) {
|
||||
mergedEndpoint.supportedMimeTypes = convertStringsToRegex(
|
||||
dynamicEndpoint.supportedMimeTypes as unknown as string[],
|
||||
|
|
|
|||
|
|
@ -52,3 +52,11 @@ export function extractEnvVariable(value: string) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the endpoint name to system-expected value.
|
||||
* @param name
|
||||
*/
|
||||
export function normalizeEndpointName(name = ''): string {
|
||||
return name.toLowerCase() === 'ollama' ? 'ollama' : name;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue