| import { |
| TextPaths, |
| FilePaths, |
| CodePaths, |
| AudioPaths, |
| VideoPaths, |
| SheetPaths, |
| } from '@librechat/client'; |
| import { |
| megabyte, |
| QueryKeys, |
| inferMimeType, |
| excelMimeTypes, |
| EToolResources, |
| fileConfig as defaultFileConfig, |
| } from 'librechat-data-provider'; |
| import type { TFile, EndpointFileConfig, FileConfig } from 'librechat-data-provider'; |
| import type { QueryClient } from '@tanstack/react-query'; |
| import type { ExtendedFile } from '~/common'; |
|
|
| export const partialTypes = ['text/x-']; |
|
|
| const textDocument = { |
| paths: TextPaths, |
| fill: '#FF5588', |
| title: 'Document', |
| }; |
|
|
| const spreadsheet = { |
| paths: SheetPaths, |
| fill: '#10A37F', |
| title: 'Spreadsheet', |
| }; |
|
|
| const codeFile = { |
| paths: CodePaths, |
| fill: '#FF6E3C', |
| |
| title: 'Code', |
| }; |
|
|
| const artifact = { |
| paths: CodePaths, |
| fill: '#2D305C', |
| title: 'Code', |
| }; |
|
|
| const audioFile = { |
| paths: AudioPaths, |
| fill: '#FF6B35', |
| title: 'Audio', |
| }; |
|
|
| const videoFile = { |
| paths: VideoPaths, |
| fill: '#8B5CF6', |
| title: 'Video', |
| }; |
|
|
| export const fileTypes = { |
| |
| file: { |
| paths: FilePaths, |
| fill: '#0000FF', |
| title: 'File', |
| }, |
| text: textDocument, |
| txt: textDocument, |
| audio: audioFile, |
| video: videoFile, |
| |
|
|
| |
| csv: spreadsheet, |
| 'application/pdf': textDocument, |
| pdf: textDocument, |
| 'text/x-': codeFile, |
| artifact: artifact, |
|
|
| |
| |
| |
| |
| |
| }; |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| export const getFileType = ( |
| type = '', |
| ): { |
| paths: React.FC; |
| fill: string; |
| title: string; |
| } => { |
| |
| if (fileTypes[type]) { |
| return fileTypes[type]; |
| } |
|
|
| if (excelMimeTypes.test(type)) { |
| return spreadsheet; |
| } |
|
|
| |
| const partialMatch = partialTypes.find((partial) => type.includes(partial)); |
| if (partialMatch && fileTypes[partialMatch]) { |
| return fileTypes[partialMatch]; |
| } |
|
|
| |
| const category = type.split('/')[0] || 'text'; |
| if (fileTypes[category]) { |
| return fileTypes[category]; |
| } |
|
|
| |
| return fileTypes.file; |
| }; |
|
|
| |
| |
| |
| |
| |
| export function formatDate(dateString: string, isSmallScreen = false) { |
| if (!dateString) { |
| return ''; |
| } |
|
|
| const date = new Date(dateString); |
|
|
| if (isSmallScreen) { |
| return date.toLocaleDateString('en-US', { |
| month: 'numeric', |
| day: 'numeric', |
| year: '2-digit', |
| }); |
| } |
|
|
| const months = [ |
| 'Jan', |
| 'Feb', |
| 'Mar', |
| 'Apr', |
| 'May', |
| 'Jun', |
| 'Jul', |
| 'Aug', |
| 'Sep', |
| 'Oct', |
| 'Nov', |
| 'Dec', |
| ]; |
|
|
| const day = date.getDate(); |
| const month = months[date.getMonth()]; |
| const year = date.getFullYear(); |
|
|
| return `${day} ${month} ${year}`; |
| } |
|
|
| |
| |
| |
| export function addFileToCache(queryClient: QueryClient, newfile: TFile) { |
| const currentFiles = queryClient.getQueryData<TFile[]>([QueryKeys.files]); |
|
|
| if (!currentFiles) { |
| console.warn('No current files found in cache, skipped updating file query cache'); |
| return; |
| } |
|
|
| const fileIndex = currentFiles.findIndex((file) => file.file_id === newfile.file_id); |
|
|
| if (fileIndex > -1) { |
| console.warn('File already exists in cache, skipped updating file query cache'); |
| return; |
| } |
|
|
| queryClient.setQueryData<TFile[]>( |
| [QueryKeys.files], |
| [ |
| { |
| ...newfile, |
| }, |
| ...currentFiles, |
| ], |
| ); |
| } |
|
|
| export function formatBytes(bytes: number, decimals = 2) { |
| if (bytes === 0) { |
| return 0; |
| } |
| const k = 1024; |
| const dm = decimals < 0 ? 0 : decimals; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)); |
| } |
|
|
| const { checkType } = defaultFileConfig; |
|
|
| export const validateFiles = ({ |
| files, |
| fileList, |
| setError, |
| endpointFileConfig, |
| toolResource, |
| fileConfig, |
| }: { |
| fileList: File[]; |
| files: Map<string, ExtendedFile>; |
| setError: (error: string) => void; |
| endpointFileConfig: EndpointFileConfig; |
| toolResource?: string; |
| fileConfig: FileConfig | null; |
| }) => { |
| const { fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes, disabled } = |
| endpointFileConfig; |
| |
| if (disabled === true) { |
| setError('com_ui_attach_error_disabled'); |
| return false; |
| } |
| const existingFiles = Array.from(files.values()); |
| const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0); |
| if (incomingTotalSize === 0) { |
| setError('com_error_files_empty'); |
| return false; |
| } |
| const currentTotalSize = existingFiles.reduce((total, file) => total + file.size, 0); |
|
|
| if (fileLimit && fileList.length + files.size > fileLimit) { |
| setError(`You can only upload up to ${fileLimit} files at a time.`); |
| return false; |
| } |
|
|
| for (let i = 0; i < fileList.length; i++) { |
| let originalFile = fileList[i]; |
| const fileType = inferMimeType(originalFile.name, originalFile.type); |
|
|
| |
| if (!fileType) { |
| setError('Unable to determine file type for: ' + originalFile.name); |
| return false; |
| } |
|
|
| |
| if (originalFile.type !== fileType) { |
| const newFile = new File([originalFile], originalFile.name, { type: fileType }); |
| originalFile = newFile; |
| fileList[i] = newFile; |
| } |
|
|
| let mimeTypesToCheck = supportedMimeTypes; |
| if (toolResource === EToolResources.context) { |
| mimeTypesToCheck = [ |
| ...(fileConfig?.text?.supportedMimeTypes || []), |
| ...(fileConfig?.ocr?.supportedMimeTypes || []), |
| ...(fileConfig?.stt?.supportedMimeTypes || []), |
| ]; |
| } |
|
|
| if (!checkType(originalFile.type, mimeTypesToCheck)) { |
| console.log(originalFile); |
| setError('Currently, unsupported file type: ' + originalFile.type); |
| return false; |
| } |
|
|
| if (fileSizeLimit && originalFile.size >= fileSizeLimit) { |
| setError(`File size exceeds ${fileSizeLimit / megabyte} MB.`); |
| return false; |
| } |
| } |
|
|
| if (totalSizeLimit && currentTotalSize + incomingTotalSize > totalSizeLimit) { |
| setError(`The total size of the files cannot exceed ${totalSizeLimit / megabyte} MB.`); |
| return false; |
| } |
|
|
| const combinedFilesInfo = [ |
| ...existingFiles.map( |
| (file) => |
| `${file.file?.name ?? file.filename}-${file.size}-${file.type?.split('/')[0] ?? 'file'}`, |
| ), |
| ...fileList.map( |
| (file: File | undefined) => |
| `${file?.name}-${file?.size}-${file?.type.split('/')[0] ?? 'file'}`, |
| ), |
| ]; |
|
|
| const uniqueFilesSet = new Set(combinedFilesInfo); |
|
|
| if (uniqueFilesSet.size !== combinedFilesInfo.length) { |
| setError('com_error_files_dupe'); |
| return false; |
| } |
|
|
| return true; |
| }; |
|
|