⚙️ fix: File Config Handling (#4664)

* chore: typing

* refactor: create file filter from custom fileConfig, if provided

* refactor: use logger utility to avoid overly verbose axios error logs when using RAG_API

* fix(useFileHandling): use memoization/callbacks to make sure the appropriate fileConfig is used; refactor: move endpoint to first field applied to formdata

* chore: update librechat-data-provider version to 0.7.54

* chore: revert type change
This commit is contained in:
Danny Avila 2024-11-07 11:11:20 -05:00 committed by GitHub
parent d60a0af878
commit 49ee88b6e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 123 additions and 82 deletions

View file

@ -30,21 +30,41 @@ const importFileFilter = (req, file, cb) => {
} }
}; };
const fileFilter = (req, file, cb) => { /**
*
* @param {import('librechat-data-provider').FileConfig | undefined} customFileConfig
*/
const createFileFilter = (customFileConfig) => {
/**
* @param {ServerRequest} req
* @param {Express.Multer.File}
* @param {import('multer').FileFilterCallback} cb
*/
const fileFilter = (req, file, cb) => {
if (!file) { if (!file) {
return cb(new Error('No file provided'), false); return cb(new Error('No file provided'), false);
} }
if (!defaultFileConfig.checkType(file.mimetype)) { const endpoint = req.body.endpoint;
const supportedTypes =
customFileConfig?.endpoints?.[endpoint]?.supportedMimeTypes ??
customFileConfig?.endpoints?.default.supportedMimeTypes ??
defaultFileConfig?.endpoints?.[endpoint]?.supportedMimeTypes;
if (!defaultFileConfig.checkType(file.mimetype, supportedTypes)) {
return cb(new Error('Unsupported file type: ' + file.mimetype), false); return cb(new Error('Unsupported file type: ' + file.mimetype), false);
} }
cb(null, true); cb(null, true);
};
return fileFilter;
}; };
const createMulterInstance = async () => { const createMulterInstance = async () => {
const customConfig = await getCustomConfig(); const customConfig = await getCustomConfig();
const fileConfig = mergeFileConfig(customConfig?.fileConfig); const fileConfig = mergeFileConfig(customConfig?.fileConfig);
const fileFilter = createFileFilter(fileConfig);
return multer({ return multer({
storage, storage,
fileFilter, fileFilter,

View file

@ -2,6 +2,7 @@ const fs = require('fs');
const axios = require('axios'); const axios = require('axios');
const FormData = require('form-data'); const FormData = require('form-data');
const { FileSources } = require('librechat-data-provider'); const { FileSources } = require('librechat-data-provider');
const { logAxiosError } = require('~/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');
/** /**
@ -32,7 +33,10 @@ const deleteVectors = async (req, file) => {
data: [file.file_id], data: [file.file_id],
}); });
} catch (error) { } catch (error) {
logger.error('Error deleting vectors', error); logAxiosError({
error,
message: 'Error deleting vectors',
});
throw new Error(error.message || 'An error occurred during file deletion.'); throw new Error(error.message || 'An error occurred during file deletion.');
} }
}; };
@ -91,7 +95,10 @@ async function uploadVectors({ req, file, file_id }) {
embedded: Boolean(responseData.known_type), embedded: Boolean(responseData.known_type),
}; };
} catch (error) { } catch (error) {
logger.error('Error embedding file', error); logAxiosError({
error,
message: 'Error uploading vectors',
});
throw new Error(error.message || 'An error occurred during file upload.'); throw new Error(error.message || 'An error occurred during file upload.');
} }
} }

View file

@ -1,7 +1,7 @@
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { import {
megabyte, megabyte,
QueryKeys, QueryKeys,
@ -47,14 +47,24 @@ const useFileHandling = (params?: UseFileHandling) => {
const agent_id = params?.additionalMetadata?.agent_id ?? ''; const agent_id = params?.additionalMetadata?.agent_id ?? '';
const assistant_id = params?.additionalMetadata?.assistant_id ?? ''; const assistant_id = params?.additionalMetadata?.assistant_id ?? '';
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ const { data: fileConfig = null } = useGetFileConfig({
select: (data) => mergeFileConfig(data), select: (data) => mergeFileConfig(data),
}); });
const endpoint =
params?.overrideEndpoint ?? conversation?.endpointType ?? conversation?.endpoint ?? 'default';
const { fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes } = const endpoint = useMemo(
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default; () =>
params?.overrideEndpoint ?? conversation?.endpointType ?? conversation?.endpoint ?? 'default',
[params?.overrideEndpoint, conversation?.endpointType, conversation?.endpoint],
);
const { fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes } = useMemo(
() =>
fileConfig?.endpoints[endpoint] ??
fileConfig?.endpoints.default ??
defaultFileConfig.endpoints[endpoint] ??
defaultFileConfig.endpoints.default,
[fileConfig, endpoint],
);
const displayToast = useCallback(() => { const displayToast = useCallback(() => {
if (errors.length > 1) { if (errors.length > 1) {
@ -146,6 +156,7 @@ const useFileHandling = (params?: UseFileHandling) => {
startUploadTimer(extendedFile.file_id, filename, extendedFile.size); startUploadTimer(extendedFile.file_id, filename, extendedFile.size);
const formData = new FormData(); const formData = new FormData();
formData.append('endpoint', endpoint);
formData.append('file', extendedFile.file as File, encodeURIComponent(filename)); formData.append('file', extendedFile.file as File, encodeURIComponent(filename));
formData.append('file_id', extendedFile.file_id); formData.append('file_id', extendedFile.file_id);
@ -167,8 +178,6 @@ const useFileHandling = (params?: UseFileHandling) => {
} }
} }
formData.append('endpoint', endpoint);
if (!isAssistantsEndpoint(endpoint)) { if (!isAssistantsEndpoint(endpoint)) {
uploadFile.mutate(formData); uploadFile.mutate(formData);
return; return;
@ -203,7 +212,8 @@ const useFileHandling = (params?: UseFileHandling) => {
uploadFile.mutate(formData); uploadFile.mutate(formData);
}; };
const validateFiles = (fileList: File[]) => { const validateFiles = useCallback(
(fileList: File[]) => {
const existingFiles = Array.from(files.values()); const existingFiles = Array.from(files.values());
const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0); const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0);
if (incomingTotalSize === 0) { if (incomingTotalSize === 0) {
@ -261,7 +271,9 @@ const useFileHandling = (params?: UseFileHandling) => {
const combinedFilesInfo = [ const combinedFilesInfo = [
...existingFiles.map( ...existingFiles.map(
(file) => (file) =>
`${file.file?.name ?? file.filename}-${file.size}-${file.type?.split('/')[0] ?? 'file'}`, `${file.file?.name ?? file.filename}-${file.size}-${
file.type?.split('/')[0] ?? 'file'
}`,
), ),
...fileList.map( ...fileList.map(
(file: File | undefined) => (file: File | undefined) =>
@ -277,7 +289,9 @@ const useFileHandling = (params?: UseFileHandling) => {
} }
return true; return true;
}; },
[files, fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes],
);
const loadImage = (extendedFile: ExtendedFile, preview: string) => { const loadImage = (extendedFile: ExtendedFile, preview: string) => {
const img = new Image(); const img = new Image();

2
package-lock.json generated
View file

@ -36283,7 +36283,7 @@
}, },
"packages/data-provider": { "packages/data-provider": {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.7.53", "version": "0.7.54",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.7.53", "version": "0.7.54",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",