@@ -4,7 +4,7 @@ import { NextRequest } from 'next/server';
44
55// Maximum file sizes based on type
66const MAX_DOCUMENT_SIZE = 8 * 1024 * 1024 ; // 8MB for documents
7- const MAX_IMAGE_SIZE = 1 * 1024 * 1024 ; // 1MB for images
7+ const MAX_IMAGE_SIZE = 500 * 1024 ; // 500KB for images (SDK has 1MB JSON limit, base64 adds ~33% overhead)
88
99// Determine if a file is an image based on content type
1010const isImageFile = ( contentType : string ) : boolean => {
@@ -23,6 +23,75 @@ const formatSizeLimit = (contentType: string): string => {
2323 return `${ sizeInMB } MB for ${ fileType } ` ;
2424} ;
2525
26+ // Compress image if it exceeds size limit
27+ // Returns compressed image buffer or original if already small enough
28+ async function compressImageIfNeeded (
29+ buffer : ArrayBuffer ,
30+ contentType : string ,
31+ maxSize : number
32+ ) : Promise < ArrayBuffer > {
33+ // Only compress actual images (not SVGs or other formats that won't benefit)
34+ if ( ! contentType . match ( / ^ i m a g e \/ ( j p e g | j p g | p n g | w e b p ) $ / i) ) {
35+ return buffer ;
36+ }
37+
38+ // If already under limit, return as-is
39+ if ( buffer . byteLength <= maxSize ) {
40+ return buffer ;
41+ }
42+
43+ // Use canvas API to resize and compress
44+ // Note: This requires browser APIs, but we're in Next.js API route (Node.js)
45+ // We'll use sharp library for server-side image processing
46+ try {
47+ const sharp = ( await import ( 'sharp' ) ) . default ;
48+
49+ // Start with moderate compression
50+ let quality = 80 ;
51+ let compressed = await sharp ( Buffer . from ( buffer ) )
52+ . jpeg ( { quality, mozjpeg : true } ) // Convert to JPEG for better compression
53+ . toBuffer ( ) ;
54+
55+ // Reduce quality iteratively if still too large
56+ while ( compressed . byteLength > maxSize && quality > 20 ) {
57+ quality -= 10 ;
58+ compressed = await sharp ( Buffer . from ( buffer ) )
59+ . jpeg ( { quality, mozjpeg : true } )
60+ . toBuffer ( ) ;
61+ }
62+
63+ // If still too large, resize dimensions
64+ if ( compressed . byteLength > maxSize ) {
65+ const metadata = await sharp ( Buffer . from ( buffer ) ) . metadata ( ) ;
66+ const width = metadata . width || 1920 ;
67+ const height = metadata . height || 1080 ;
68+
69+ // Reduce by 25% iteratively
70+ let scale = 0.75 ;
71+ while ( compressed . byteLength > maxSize && scale > 0.25 ) {
72+ compressed = await sharp ( Buffer . from ( buffer ) )
73+ . resize ( Math . floor ( width * scale ) , Math . floor ( height * scale ) , {
74+ fit : 'inside' ,
75+ withoutEnlargement : true ,
76+ } )
77+ . jpeg ( { quality : 70 , mozjpeg : true } )
78+ . toBuffer ( ) ;
79+ scale -= 0.1 ;
80+ }
81+ }
82+
83+ console . log (
84+ `Compressed image: ${ buffer . byteLength } bytes -> ${ compressed . byteLength } bytes (${ Math . round ( ( compressed . byteLength / buffer . byteLength ) * 100 ) } %)`
85+ ) ;
86+
87+ return compressed . buffer ;
88+ } catch ( error ) {
89+ console . error ( 'Failed to compress image:' , error ) ;
90+ // If compression fails, throw error rather than uploading oversized file
91+ throw new Error ( 'Image too large and compression failed' ) ;
92+ }
93+ }
94+
2695export async function POST (
2796 request : NextRequest ,
2897 { params } : { params : Promise < { name : string ; sessionName : string } > } ,
@@ -46,22 +115,40 @@ export async function POST(
46115
47116 const filename = ( formData . get ( 'filename' ) as string ) || file . name ;
48117 const contentType = file . type || 'application/octet-stream' ;
49-
50- // Check file size based on type
51118 const maxSize = getMaxFileSize ( contentType ) ;
52- if ( file . size > maxSize ) {
53- return new Response (
54- JSON . stringify ( {
55- error : `File too large. Maximum size is ${ formatSizeLimit ( contentType ) } `
56- } ) ,
57- {
58- status : 413 , // Payload Too Large
59- headers : { 'Content-Type' : 'application/json' } ,
60- }
61- ) ;
62- }
63119
64- const fileBuffer = await file . arrayBuffer ( ) ;
120+ // Get initial file buffer
121+ let fileBuffer = await file . arrayBuffer ( ) ;
122+
123+ // For images, compress if needed instead of rejecting
124+ if ( isImageFile ( contentType ) ) {
125+ try {
126+ fileBuffer = await compressImageIfNeeded ( fileBuffer , contentType , maxSize ) ;
127+ } catch ( compressionError ) {
128+ return new Response (
129+ JSON . stringify ( {
130+ error : `Image too large and could not be compressed. Please reduce image size and try again.`
131+ } ) ,
132+ {
133+ status : 413 , // Payload Too Large
134+ headers : { 'Content-Type' : 'application/json' } ,
135+ }
136+ ) ;
137+ }
138+ } else {
139+ // For non-images, enforce strict size limit
140+ if ( file . size > maxSize ) {
141+ return new Response (
142+ JSON . stringify ( {
143+ error : `File too large. Maximum size is ${ formatSizeLimit ( contentType ) } `
144+ } ) ,
145+ {
146+ status : 413 , // Payload Too Large
147+ headers : { 'Content-Type' : 'application/json' } ,
148+ }
149+ ) ;
150+ }
151+ }
65152
66153 // Upload to workspace/file-uploads directory using the PUT endpoint
67154 // Retry logic: if backend returns 202 (content service starting), retry up to 3 times
@@ -133,21 +220,38 @@ export async function POST(
133220 } ) ;
134221 }
135222
136- const fileBuffer = await fileResp . arrayBuffer ( ) ;
223+ let fileBuffer = await fileResp . arrayBuffer ( ) ;
137224 const contentType = fileResp . headers . get ( 'content-type' ) || 'application/octet-stream' ;
138-
139- // Check file size based on type
140225 const maxSize = getMaxFileSize ( contentType ) ;
141- if ( fileBuffer . byteLength > maxSize ) {
142- return new Response (
143- JSON . stringify ( {
144- error : `File too large. Maximum size is ${ formatSizeLimit ( contentType ) } `
145- } ) ,
146- {
147- status : 413 , // Payload Too Large
148- headers : { 'Content-Type' : 'application/json' } ,
149- }
150- ) ;
226+
227+ // For images, compress if needed instead of rejecting
228+ if ( isImageFile ( contentType ) ) {
229+ try {
230+ fileBuffer = await compressImageIfNeeded ( fileBuffer , contentType , maxSize ) ;
231+ } catch ( compressionError ) {
232+ return new Response (
233+ JSON . stringify ( {
234+ error : `Image too large and could not be compressed. Please reduce image size and try again.`
235+ } ) ,
236+ {
237+ status : 413 , // Payload Too Large
238+ headers : { 'Content-Type' : 'application/json' } ,
239+ }
240+ ) ;
241+ }
242+ } else {
243+ // For non-images, enforce strict size limit
244+ if ( fileBuffer . byteLength > maxSize ) {
245+ return new Response (
246+ JSON . stringify ( {
247+ error : `File too large. Maximum size is ${ formatSizeLimit ( contentType ) } `
248+ } ) ,
249+ {
250+ status : 413 , // Payload Too Large
251+ headers : { 'Content-Type' : 'application/json' } ,
252+ }
253+ ) ;
254+ }
151255 }
152256
153257 // Upload to workspace/file-uploads directory using the PUT endpoint
0 commit comments