📂 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:
Danny Avila 2025-11-10 19:05:30 -05:00 committed by GitHub
parent 06c060b983
commit 2524d33362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 2352 additions and 290 deletions

View file

@ -1,5 +1,6 @@
import { useState, useMemo, useCallback, useRef } from 'react';
import { useDrop } from 'react-dnd';
import { useToastContext } from '@librechat/client';
import { NativeTypes } from 'react-dnd-html5-backend';
import { useQueryClient } from '@tanstack/react-query';
import { useRecoilValue, useSetRecoilState } from 'recoil';
@ -7,10 +8,12 @@ import {
Tools,
QueryKeys,
Constants,
EModelEndpoint,
EToolResources,
EModelEndpoint,
mergeFileConfig,
AgentCapabilities,
isAssistantsEndpoint,
getEndpointFileConfig,
defaultAgentCapabilities,
} from 'librechat-data-provider';
import type { DropTargetMonitor } from 'react-dnd';
@ -18,9 +21,12 @@ import type * as t from 'librechat-data-provider';
import store, { ephemeralAgentByConvoId } from '~/store';
import useFileHandling from './useFileHandling';
import { isEphemeralAgent } from '~/common';
import useLocalize from '../useLocalize';
export default function useDragHelpers() {
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const localize = useLocalize();
const [showModal, setShowModal] = useState(false);
const [draggedFiles, setDraggedFiles] = useState<File[]>([]);
const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined;
@ -33,9 +39,7 @@ export default function useDragHelpers() {
[conversation?.endpoint],
);
const { handleFiles } = useFileHandling({
overrideEndpoint: isAssistants ? undefined : EModelEndpoint.agents,
});
const { handleFiles } = useFileHandling();
const handleOptionSelect = useCallback(
(toolResource: EToolResources | undefined) => {
@ -62,6 +66,26 @@ export default function useDragHelpers() {
const handleDrop = useCallback(
(item: { files: File[] }) => {
/** Early block: leverage endpoint file config to prevent drag/drop on disabled endpoints */
const currentEndpoint = conversationRef.current?.endpoint ?? 'default';
const currentEndpointType = conversationRef.current?.endpointType ?? undefined;
const cfg = queryClient.getQueryData<t.FileConfig>([QueryKeys.fileConfig]);
if (cfg) {
const mergedCfg = mergeFileConfig(cfg);
const endpointCfg = getEndpointFileConfig({
fileConfig: mergedCfg,
endpoint: currentEndpoint,
endpointType: currentEndpointType,
});
if (endpointCfg?.disabled === true) {
showToast({
message: localize('com_ui_attach_error_disabled'),
status: 'error',
});
return;
}
}
if (isAssistants) {
handleFilesRef.current(item.files);
return;
@ -110,7 +134,7 @@ export default function useDragHelpers() {
setDraggedFiles(item.files);
setShowModal(true);
},
[isAssistants, queryClient],
[isAssistants, queryClient, showToast, localize],
);
const [{ canDrop, isOver }, drop] = useDrop(

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react';
import { v4 } from 'uuid';
import { useSetRecoilState } from 'recoil';
import { useToastContext } from '@librechat/client';
@ -6,16 +6,14 @@ import { useQueryClient } from '@tanstack/react-query';
import {
QueryKeys,
Constants,
EModelEndpoint,
EToolResources,
mergeFileConfig,
isAgentsEndpoint,
isAssistantsEndpoint,
getEndpointFileConfig,
defaultAssistantsVersion,
fileConfig as defaultFileConfig,
} from 'librechat-data-provider';
import debounce from 'lodash/debounce';
import type { EndpointFileConfig, TEndpointsConfig, TError } from 'librechat-data-provider';
import type { TEndpointsConfig, TError } from 'librechat-data-provider';
import type { ExtendedFile, FileSetter } from '~/common';
import { useGetFileConfig, useUploadFileMutation } from '~/data-provider';
import useLocalize, { TranslationKeys } from '~/hooks/useLocalize';
@ -29,9 +27,7 @@ import useUpdateFiles from './useUpdateFiles';
type UseFileHandling = {
fileSetter?: FileSetter;
overrideEndpoint?: EModelEndpoint;
fileFilter?: (file: File) => boolean;
overrideEndpointFileConfig?: EndpointFileConfig;
additionalMetadata?: Record<string, string | undefined>;
};
@ -54,17 +50,13 @@ const useFileHandling = (params?: UseFileHandling) => {
const agent_id = params?.additionalMetadata?.agent_id ?? '';
const assistant_id = params?.additionalMetadata?.assistant_id ?? '';
const endpointType = useMemo(() => conversation?.endpointType, [conversation?.endpointType]);
const endpoint = useMemo(() => conversation?.endpoint ?? 'default', [conversation?.endpoint]);
const { data: fileConfig = null } = useGetFileConfig({
select: (data) => mergeFileConfig(data),
});
const endpoint = useMemo(
() =>
params?.overrideEndpoint ?? conversation?.endpointType ?? conversation?.endpoint ?? 'default',
[params?.overrideEndpoint, conversation?.endpointType, conversation?.endpoint],
);
const displayToast = useCallback(() => {
if (errors.length > 1) {
// TODO: this should not be a dynamic localize input!!
@ -169,10 +161,7 @@ const useFileHandling = (params?: UseFileHandling) => {
const formData = new FormData();
formData.append('endpoint', endpoint);
formData.append(
'original_endpoint',
conversation?.endpointType || conversation?.endpoint || '',
);
formData.append('endpointType', endpointType ?? '');
formData.append('file', extendedFile.file as File, encodeURIComponent(filename));
formData.append('file_id', extendedFile.file_id);
@ -194,7 +183,7 @@ const useFileHandling = (params?: UseFileHandling) => {
}
}
if (isAgentsEndpoint(endpoint)) {
if (!isAssistantsEndpoint(endpointType ?? endpoint)) {
if (!agent_id) {
formData.append('message_file', 'true');
}
@ -205,9 +194,7 @@ const useFileHandling = (params?: UseFileHandling) => {
if (conversation?.agent_id != null && formData.get('agent_id') == null) {
formData.append('agent_id', conversation.agent_id);
}
}
if (!isAssistantsEndpoint(endpoint)) {
uploadFile.mutate(formData);
return;
}
@ -264,18 +251,19 @@ const useFileHandling = (params?: UseFileHandling) => {
/* Validate files */
let filesAreValid: boolean;
try {
const endpointFileConfig = getEndpointFileConfig({
endpoint,
fileConfig,
endpointType,
});
filesAreValid = validateFiles({
files,
fileList,
setError,
endpointFileConfig:
params?.overrideEndpointFileConfig ??
fileConfig?.endpoints?.[endpoint] ??
fileConfig?.endpoints?.default ??
defaultFileConfig.endpoints[endpoint] ??
defaultFileConfig.endpoints.default,
fileConfig,
endpointFileConfig,
toolResource: _toolResource,
fileConfig: fileConfig,
});
} catch (error) {
console.error('file validation error', error);

View file

@ -5,11 +5,9 @@ import type { SharePointFile } from '~/data-provider/Files/sharepoint';
interface UseSharePointFileHandlingProps {
fileSetter?: any;
toolResource?: string;
fileFilter?: (file: File) => boolean;
additionalMetadata?: Record<string, string | undefined>;
overrideEndpoint?: any;
overrideEndpointFileConfig?: any;
toolResource?: string;
}
interface UseSharePointFileHandlingReturn {