diff --git a/client/src/hooks/Files/useClientSideResize.ts b/client/src/hooks/Files/useClientSideResize.ts index f7df82aa95..7ccf8084e6 100644 --- a/client/src/hooks/Files/useClientSideResize.ts +++ b/client/src/hooks/Files/useClientSideResize.ts @@ -2,11 +2,11 @@ import { mergeFileConfig } from 'librechat-data-provider'; import { useCallback } from 'react'; import { useGetFileConfig } from '~/data-provider'; import { - resizeImage, - shouldResizeImage, - supportsClientSideResize, - type ResizeOptions, - type ResizeResult, + resizeImage, + shouldResizeImage, + supportsClientSideResize, + type ResizeOptions, + type ResizeResult, } from '~/utils/imageResize'; /** @@ -19,6 +19,7 @@ export const useClientSideResize = () => { }); // Safe access to clientSideImageResize config with fallbacks + // eslint-disable-next-line react-hooks/exhaustive-deps const config = (fileConfig as any)?.clientSideImageResize ?? { enabled: false, maxWidth: 1900, @@ -66,7 +67,6 @@ export const useClientSideResize = () => { return { file: result.file, resized: true, result }; } catch (error) { console.warn('Client-side image resizing failed:', error); - // Return original file on error return { file, resized: false }; } }, @@ -81,4 +81,4 @@ export const useClientSideResize = () => { }; }; -export default useClientSideResize; \ No newline at end of file +export default useClientSideResize; diff --git a/client/src/hooks/Files/useFileHandling.ts b/client/src/hooks/Files/useFileHandling.ts index 2adea8ce78..ef71ce72e6 100644 --- a/client/src/hooks/Files/useFileHandling.ts +++ b/client/src/hooks/Files/useFileHandling.ts @@ -265,20 +265,20 @@ const useFileHandling = (params?: UseFileHandling) => { const file_id = v4(); try { let processedFile = originalFile; - + // Apply client-side resizing if available and appropriate if (originalFile.type.startsWith('image/')) { try { const resizeResult = await resizeImageIfNeeded(originalFile); processedFile = resizeResult.file; - + // Show toast notification if image was resized if (resizeResult.resized && resizeResult.result) { const { originalSize, newSize, compressionRatio } = resizeResult.result; const originalSizeMB = (originalSize / (1024 * 1024)).toFixed(1); const newSizeMB = (newSize / (1024 * 1024)).toFixed(1); const savedPercent = Math.round((1 - compressionRatio) * 100); - + showToast({ message: `Image resized: ${originalSizeMB}MB → ${newSizeMB}MB (${savedPercent}% smaller)`, status: 'success', diff --git a/client/src/utils/__tests__/imageResize.test.ts b/client/src/utils/__tests__/imageResize.test.ts index e6be1e37df..c1d7da7dd8 100644 --- a/client/src/utils/__tests__/imageResize.test.ts +++ b/client/src/utils/__tests__/imageResize.test.ts @@ -44,10 +44,10 @@ describe('imageResize utility', () => { const originalCanvas = global.HTMLCanvasElement; // @ts-ignore delete global.HTMLCanvasElement; - + const result = supportsClientSideResize(); expect(result).toBe(false); - + global.HTMLCanvasElement = originalCanvas; }); }); @@ -58,7 +58,7 @@ describe('imageResize utility', () => { type: 'image/jpeg', lastModified: Date.now(), }); - + // Mock large file size Object.defineProperty(largeImageFile, 'size', { value: 100 * 1024 * 1024, // 100MB @@ -74,7 +74,7 @@ describe('imageResize utility', () => { type: 'image/jpeg', lastModified: Date.now(), }); - + // Mock small file size Object.defineProperty(smallImageFile, 'size', { value: 1024, // 1KB @@ -90,7 +90,7 @@ describe('imageResize utility', () => { type: 'text/plain', lastModified: Date.now(), }); - + const result = shouldResizeImage(textFile); expect(result).toBe(false); }); @@ -100,9 +100,9 @@ describe('imageResize utility', () => { type: 'image/gif', lastModified: Date.now(), }); - + const result = shouldResizeImage(gifFile); expect(result).toBe(false); }); }); -}); \ No newline at end of file +}); diff --git a/client/src/utils/imageResize.ts b/client/src/utils/imageResize.ts index 09b23f4e30..8df22f32ba 100644 --- a/client/src/utils/imageResize.ts +++ b/client/src/utils/imageResize.ts @@ -25,10 +25,10 @@ export interface ResizeResult { * We use slightly smaller values to ensure no backend resizing is triggered */ const DEFAULT_RESIZE_OPTIONS: ResizeOptions = { - maxWidth: 1900, // Slightly less than backend maxLongSide=2000 + maxWidth: 1900, // Slightly less than backend maxLongSide=2000 maxHeight: 1900, // Slightly less than backend maxLongSide=2000 - quality: 0.92, // High quality while reducing file size - format: 'jpeg', // Most compatible format + quality: 0.92, // High quality while reducing file size + format: 'jpeg', // Most compatible format }; /** @@ -40,11 +40,11 @@ export function supportsClientSideResize(): boolean { if (typeof HTMLCanvasElement === 'undefined') return false; if (typeof FileReader === 'undefined') return false; if (typeof Image === 'undefined') return false; - + // Test canvas creation const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); - + return !!(ctx && ctx.drawImage && canvas.toBlob); } catch { return false; @@ -60,18 +60,18 @@ function calculateDimensions( maxWidth: number, maxHeight: number, ): { width: number; height: number } { - let { width, height } = { width: originalWidth, height: originalHeight }; - + const { width, height } = { width: originalWidth, height: originalHeight }; + // If image is smaller than max dimensions, don't upscale if (width <= maxWidth && height <= maxHeight) { return { width, height }; } - + // Calculate scaling factor const widthRatio = maxWidth / width; const heightRatio = maxHeight / height; const scalingFactor = Math.min(widthRatio, heightRatio); - + return { width: Math.round(width * scalingFactor), height: Math.round(height * scalingFactor), @@ -91,19 +91,19 @@ export function resizeImage( reject(new Error('Browser does not support client-side image resizing')); return; } - + // Only process image files if (!file.type.startsWith('image/')) { reject(new Error('File is not an image')); return; } - + const opts = { ...DEFAULT_RESIZE_OPTIONS, ...options }; const reader = new FileReader(); - + reader.onload = (event) => { const img = new Image(); - + img.onload = () => { try { const originalDimensions = { width: img.width, height: img.height }; @@ -113,7 +113,7 @@ export function resizeImage( opts.maxWidth!, opts.maxHeight!, ); - + // If no resizing needed, return original file if ( newDimensions.width === originalDimensions.width && @@ -129,21 +129,21 @@ export function resizeImage( }); return; } - + // Create canvas and resize const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; - + canvas.width = newDimensions.width; canvas.height = newDimensions.height; - + // Use high-quality image smoothing ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; - + // Draw resized image ctx.drawImage(img, 0, 0, newDimensions.width, newDimensions.height); - + // Convert to blob canvas.toBlob( (blob) => { @@ -151,17 +151,17 @@ export function resizeImage( reject(new Error('Failed to create blob from canvas')); return; } - + // Create new file with same name but potentially different extension const extension = opts.format === 'jpeg' ? '.jpg' : `.${opts.format}`; const baseName = file.name.replace(/\.[^/.]+$/, ''); const newFileName = `${baseName}${extension}`; - + const resizedFile = new File([blob], newFileName, { type: `image/${opts.format}`, lastModified: Date.now(), }); - + resolve({ file: resizedFile, originalSize: file.size, @@ -178,11 +178,11 @@ export function resizeImage( reject(error); } }; - + img.onerror = () => reject(new Error('Failed to load image')); img.src = event.target?.result as string; }; - + reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsDataURL(file); }); @@ -196,19 +196,20 @@ export function shouldResizeImage( fileSizeLimit: number = 512 * 1024 * 1024, // 512MB default ): boolean { // Don't resize if file is already small - if (file.size < fileSizeLimit * 0.1) { // Less than 10% of limit + if (file.size < fileSizeLimit * 0.1) { + // Less than 10% of limit return false; } - + // Don't process non-images if (!file.type.startsWith('image/')) { return false; } - + // Don't process GIFs (they might be animated) if (file.type === 'image/gif') { return false; } - + return true; -} \ No newline at end of file +}