@@ -45,6 +45,10 @@ function builder(
4545 }
4646 return value ;
4747 } ,
48+ } ) . option ( "upload-batch" , {
49+ type : "number" ,
50+ description : "Number of files to process in parallel batches" ,
51+ default : 20 ,
4852 } ) ;
4953}
5054
@@ -128,6 +132,7 @@ async function handler(
128132 branch ?: string ;
129133 commitSha ?: string ;
130134 commitShaDate ?: string ;
135+ "upload-batch" : number ;
131136 } ,
132137) {
133138 const napiConfig = argv . napiConfig as z . infer < typeof localConfigSchema > ;
@@ -274,6 +279,15 @@ async function handler(
274279 `\nView it here: https://app.nanoapi.io/projects/${ projectId } /manifests/${ responseBody . id } ` ,
275280 ) ;
276281 }
282+
283+ console . info ( "🔍 Requesting labeling..." ) ;
284+ await requestLabeling (
285+ files ,
286+ responseBody . id ,
287+ apiService ,
288+ argv [ "upload-batch" ] ,
289+ ) ;
290+ console . info ( "✅ Labeling requested successfully" ) ;
277291 } catch ( error ) {
278292 console . error (
279293 `❌ Failed to upload manifest to API for project id: ${ projectId } ` ,
@@ -305,3 +319,141 @@ export default {
305319 builder,
306320 handler,
307321} ;
322+
323+ async function requestLabeling (
324+ files : Map < string , { path : string ; content : string } > ,
325+ manifestId : number ,
326+ apiService : ApiService ,
327+ batchSize : number ,
328+ ) {
329+ // Prepare for labeling
330+ console . info ( "🔍 Preparing for labeling..." ) ;
331+ const filesArray = Array . from ( files . values ( ) ) ;
332+
333+ // Upload files in configurable batches
334+ const allUploadResults : { path : string ; bucketName : string | null } [ ] = [ ] ;
335+
336+ for ( let i = 0 ; i < filesArray . length ; i += batchSize ) {
337+ const batch = filesArray . slice ( i , i + batchSize ) ;
338+ const batchNumber = Math . floor ( i / batchSize ) + 1 ;
339+ const totalBatches = Math . ceil ( filesArray . length / batchSize ) ;
340+
341+ console . info (
342+ `📝 Processing batch ${ batchNumber } /${ totalBatches } (${ batch . length } files)...` ,
343+ ) ;
344+
345+ // Separate empty files from files that need uploading
346+ const filesToUpload = batch . filter ( ( file ) =>
347+ file . content && file . content . trim ( ) !== ""
348+ ) ;
349+ const emptyFiles = batch . filter ( ( file ) =>
350+ ! file . content || file . content . trim ( ) === ""
351+ ) ;
352+
353+ // Add empty files to results with null bucket names
354+ emptyFiles . forEach ( ( file ) => {
355+ allUploadResults . push ( { path : file . path , bucketName : null } ) ;
356+ } ) ;
357+
358+ // Upload non-empty files in parallel
359+ if ( filesToUpload . length > 0 ) {
360+ const batchPromises = filesToUpload . map ( async ( file ) => {
361+ try {
362+ const response = await apiService . performRequest (
363+ "POST" ,
364+ "/labeling/temp" ,
365+ { path : file . path , content : file . content } ,
366+ ) ;
367+
368+ if ( response . status !== 200 ) {
369+ const errorText = await response . text ( ) ;
370+ throw new Error (
371+ `Failed to upload file ${ file . path } : ${ errorText } ${ response . status } ` ,
372+ ) ;
373+ }
374+
375+ const { bucketName } = await response . json ( ) as {
376+ bucketName : string ;
377+ } ;
378+ return { path : file . path , bucketName } ;
379+ } catch ( error ) {
380+ console . error (
381+ `❌ Failed to upload ${ file . path } : ${
382+ error instanceof Error ? error . message : String ( error )
383+ } `,
384+ ) ;
385+ return null ;
386+ }
387+ } ) ;
388+
389+ const batchResults = await Promise . all ( batchPromises ) ;
390+ const successfulResults = batchResults . filter ( (
391+ result ,
392+ ) : result is { path : string ; bucketName : string } => result !== null ) ;
393+ allUploadResults . push ( ...successfulResults ) ;
394+
395+ const skippedCount = emptyFiles . length ;
396+ const failedCount = filesToUpload . length - successfulResults . length ;
397+
398+ if ( failedCount > 0 ) {
399+ console . warn (
400+ `⚠️ Batch ${ batchNumber } completed (${ successfulResults . length } uploaded, ${ failedCount } failed, ${ skippedCount } skipped)` ,
401+ ) ;
402+ } else {
403+ console . info (
404+ `✅ Batch ${ batchNumber } completed (${ successfulResults . length } files uploaded, ${ skippedCount } skipped)` ,
405+ ) ;
406+ }
407+ } else {
408+ console . info (
409+ `✅ Batch ${ batchNumber } completed (${ emptyFiles . length } files skipped)` ,
410+ ) ;
411+ }
412+ }
413+
414+ const labelingMap = Object . fromEntries (
415+ allUploadResults . map ( ( { path, bucketName } ) => [ path , bucketName ] ) ,
416+ ) ;
417+
418+ // Upload labeling payload as a temp file
419+ const labelingMapFile = await apiService . performRequest (
420+ "POST" ,
421+ "/labeling/temp" ,
422+ {
423+ path : "labelingMapFile.json" ,
424+ content : JSON . stringify ( labelingMap , null , 2 ) ,
425+ } ,
426+ ) ;
427+
428+ if ( labelingMapFile . status !== 200 ) {
429+ console . error ( `❌ Failed to upload labeling payload` ) ;
430+ Deno . exit ( 1 ) ;
431+ }
432+
433+ const labelingFileMapFileResponse = await labelingMapFile . json ( ) as {
434+ bucketName : string ;
435+ } ;
436+
437+ console . info ( `✅ Labeling preparation completed successfully` ) ;
438+
439+ // request labeling to API with the temp file
440+ const labelingResponse = await apiService . performRequest (
441+ "POST" ,
442+ "/labeling/request" ,
443+ {
444+ manifestId,
445+ fileMapName : labelingFileMapFileResponse . bucketName ,
446+ } ,
447+ ) ;
448+
449+ if ( labelingResponse . status !== 200 ) {
450+ console . error ( `❌ Failed to request labeling` ) ;
451+ console . error (
452+ ` Error: ${ labelingResponse . statusText } ${ labelingResponse . status } ${ await labelingResponse
453+ . text ( ) } `,
454+ ) ;
455+ Deno . exit ( 1 ) ;
456+ }
457+
458+ console . info ( `✅ Labeling request completed successfully` ) ;
459+ }
0 commit comments