1010    header =" Bulk AI Flow" 
1111    class =" !max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]" 
1212    :buttons =" [
13-       { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } }, 
13+       { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages , loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } }, 
1414      { label: 'Cancel', onclick: (dialog) => dialog.hide() }, 
1515    ]"  
1616  >
3333          @error =" handleTableError" 
3434          :carouselSaveImages =" carouselSaveImages" 
3535          :carouselImageIndex =" carouselImageIndex" 
36+           :regenerateImagesRefreshRate =" props.meta.refreshRates?.regenerateImages" 
3637        />
3738      </div >
3839      <div  class =" text-red-600 flex items-center w-full" 
@@ -88,9 +89,14 @@ const isCriticalError = ref(false);
8889const =  ref (false );
8990const =  ref (' ' 
9091const =  ref (0 );
92+ const =  ref (false );
93+ const =  ref (false );
94+ const =  ref (false );
95+ 
9196
9297const =  async  () =>  {
9398  confirmDialog .value .open (); 
99+   isFetchingRecords .value  =  true ; 
94100  await  getRecords (); 
95101  if  (props .meta .isAttachFiles ) { 
96102    await  getImages (); 
@@ -110,42 +116,39 @@ const openDialog = async () => {
110116      return  acc ; 
111117    },{[primaryKey ]: records .value [i ][primaryKey ]} as  Record <string , boolean >); 
112118  } 
113-   isFetchingRecords .value  =  true ; 
119+   isFetchingRecords .value  =  false ; 
114120   
115121  if  (props .meta .isImageGeneration ) { 
122+     isGeneratingImages .value  =  true ; 
116123    runAiAction ({ 
117124      endpoint: ' initial_image_generate'  
118125      actionType: ' generate_images'  
119126      responseFlag: isAiResponseReceivedImage , 
120127    }); 
121128  } 
122129  if  (props .meta .isFieldsForAnalizeFromImages ) { 
130+     isAnalizingImages .value  =  true ; 
123131    runAiAction ({ 
124132      endpoint: ' analyze'  
125133      actionType: ' analyze'  
126134      responseFlag: isAiResponseReceivedAnalize , 
127135    }); 
128136  } 
129137  if  (props .meta .isFieldsForAnalizePlain ) { 
138+     isAnalizingFields .value  =  true ; 
130139    runAiAction ({ 
131140      endpoint: ' analyze_no_images'  
132141      actionType: ' analyze_no_images'  
133142      responseFlag: isAiResponseReceivedAnalize , 
134143    }); 
135144  } 
136-    
137-   isFetchingRecords .value  =  false ; 
138145} 
139146  
140147watch (selected , (val ) =>  {
141-   console .log (' Selected changed:' val ); 
148+   // console.log('Selected changed:', val); 
142149  checkedCount .value  =  val .filter (item  =>  item .isChecked  ===  true ).length ; 
143150}, { deep: true  }); 
144151
145- watch (carouselSaveImages , (val ) =>  {
146-   console .log (' carouselSaveImages changed:' val ); 
147- }, { deep: true  }); 
148- 
149152function  fillCarouselSaveImages() {
150153  for  (const of  selected .value ) { 
151154    const :  any  =  {}; 
@@ -400,19 +403,20 @@ async function runAiAction({
400403  responseFlag:  Ref <boolean []>; 
401404  updateOnSuccess? :  boolean ; 
402405}) { 
403-   const :  any [] =  new  Array (props .checkboxes .length ); 
404406  let  hasError =  false ; 
405407  let  errorMessage =  ' '  
406-    
408+   const  jobsIds :  { jobId :   any ; recordId :   any ; }[]  =  [];  
407409  responseFlag .value  =  props .checkboxes .map (() =>  false ); 
408410
411+   // creating jobs 
409412  const =  props .checkboxes .map (async  (checkbox , i ) =>  { 
410413    try  { 
411414      const =  await  callAdminForthApi ({ 
412-         path: ` /plugin/${props .meta .pluginInstanceId }/${ endpoint }  ` , 
415+         path: ` /plugin/${props .meta .pluginInstanceId }/create-job  ` , 
413416        method: ' POST'  
414417        body: { 
415-           selectedId: checkbox , 
418+           actionType: actionType , 
419+           recordId: checkbox , 
416420        }, 
417421      }); 
418422
@@ -424,41 +428,96 @@ async function runAiAction({
424428        throw  new  Error (` ${actionType } request returned empty response. ` ); 
425429      } 
426430
427-       results [i ] =  res ; 
428-        
429-       if  (actionType  !==  ' analyze_no_images' ||  ! props .meta .isFieldsForAnalizeFromImages ) { 
430-         responseFlag .value [i ] =  true ; 
431-       } 
431+       jobsIds .push ({ jobId: res .jobId , recordId: checkbox  }); 
432+     } catch  (e ) { 
433+       console .error (` Error during ${actionType } for item ${i }: ` , e ); 
434+       hasError  =  true ; 
435+       errorMessage  =  ` Failed to ${actionType .replace (' _' '  '  ` ; 
436+       return  { success: false , index: i , error: e  }; 
437+     } 
438+   }); 
439+   await  Promise .all (tasks ); 
432440
433-       if  (res .result ) { 
441+   // polling jobs 
442+   let  isInProgress =  true ; 
443+   // if no jobs were created, skip polling 
444+   while  (isInProgress ) { 
445+     // check if at least one job is still in progress 
446+     let  isAtLeastOneInProgress =  false ; 
447+     // checking status of each job 
448+     for  (const of  jobsIds ) { 
449+       // check job status 
450+       const =  await  callAdminForthApi ({ 
451+         path: ` /plugin/${props .meta .pluginInstanceId }/get-job-status ` , 
452+         method: ' POST'  
453+         body: { jobId  }, 
454+       }); 
455+       // check for errors 
456+       if  (jobResponse ?.error ) { 
457+         console .error (` Error during ${actionType }: ` , jobResponse .error ); 
458+         break ; 
459+       }; 
460+       //  extract job status 
461+       let  jobStatus =  jobResponse ?.job ?.status ; 
462+       //  check if job is still in progress. If in progress - skip to next job 
463+       if  (jobStatus  ===  ' in_progress'  
464+         isAtLeastOneInProgress  =  true ; 
465+       // if job is completed - update record data 
466+       } else  if  (jobStatus  ===  ' completed'  
467+         //  finding index of the record in selected array 
468+         const =  selected .value .findIndex (item  =>  String (item [primaryKey ]) ===  String (recordId )); 
469+         // if we are generating images - update carouselSaveImages with new image 
434470        if  (actionType  ===  ' generate_images'  
435-           for  (const of  Object .entries (carouselSaveImages .value [i ])) { 
471+           for  (const of  Object .entries (carouselSaveImages .value [index ])) { 
436472            if  (props .meta .outputImageFields ?.includes (key )) { 
437-               carouselSaveImages .value [i ][key ] =  [res .result [key ]]; 
473+               carouselSaveImages .value [index ][key ] =  [jobResponse . job .result [key ]]; 
438474            } 
439475          } 
440476        } 
441- 
442-         const =  selected .value [i ]?.[primaryKey ]; 
477+         // marking that we received response for this record 
478+         if  (actionType  !==  ' analyze_no_images' ||  ! props .meta .isFieldsForAnalizeFromImages ) { 
479+           responseFlag .value [index ] =  true ; 
480+         } 
481+         // updating selected with new data from AI 
482+         const =  selected .value [index ]?.[primaryKey ]; 
443483        if  (pk ) { 
444-           selected .value [i ] =  { 
445-             ... selected .value [i ], 
446-             ... res .result , 
484+           selected .value [index ] =  { 
485+             ... selected .value [index ], 
486+             ... jobResponse . job .result , 
447487            isChecked: true , 
448488            [primaryKey ]: pk , 
449489          }; 
450490        } 
491+         // removing job from jobsIds 
492+         if  (index  !==  - 1 ) { 
493+           jobsIds .splice (jobsIds .findIndex (j  =>  j .jobId  ===  jobId ), 1 ); 
494+         } 
495+         //  checking one more time if we have in progress jobs 
496+         isAtLeastOneInProgress  =  true ; 
497+         //  if job is failed - set error 
498+       } else  if  (jobStatus  ===  ' failed'  
499+         adminforth .alert ({ 
500+           message: ` Generation action "${actionType .replace (' _' '  ' recordId }. Error: ${jobResponse .job ?.error  ||  ' Unknown error'  ` , 
501+           variant: ' danger'  
502+           timeout: ' unlimited'  
503+         }); 
451504      } 
452-       return  { success: true , index: i , data: res  }; 
453-     } catch  (e ) { 
454-       console .error (` Error during ${actionType } for item ${i }: ` , e ); 
455-       hasError  =  true ; 
456-       errorMessage  =  ` Failed to ${actionType .replace (' _' '  '  ` ; 
457-       return  { success: false , index: i , error: e  }; 
458505    } 
459-   }); 
460- 
461-   await  Promise .all (tasks ); 
506+     if  (! isAtLeastOneInProgress ) { 
507+       isInProgress  =  false ; 
508+     } 
509+     if  (jobsIds .length  >  0 ) { 
510+       if  (actionType  ===  ' generate_images'  
511+         await  new  Promise (resolve  =>  setTimeout (resolve , props .meta .refreshRates ?.generateImages )); 
512+       } else  if  (actionType  ===  ' analyze'  
513+         await  new  Promise (resolve  =>  setTimeout (resolve , props .meta .refreshRates ?.fillFieldsFromImages )); 
514+       } else  if  (actionType  ===  ' analyze_no_images'  
515+         await  new  Promise (resolve  =>  setTimeout (resolve , props .meta .refreshRates ?.fillPlainFields )); 
516+       } else  { 
517+         await  new  Promise (resolve  =>  setTimeout (resolve , 2000 )); 
518+       } 
519+     } 
520+   } 
462521
463522  if  (hasError ) { 
464523    adminforth .alert ({ 
@@ -473,6 +532,14 @@ async function runAiAction({
473532    this .errorMessage .value  =  errorMessage ; 
474533    return ; 
475534  } 
535+ 
536+   if  (actionType  ===  ' generate_images'  
537+     isGeneratingImages .value  =  false ; 
538+   } else  if  (actionType  ===  ' analyze'  
539+     isAnalizingImages .value  =  false ; 
540+   } else  if  (actionType  ===  ' analyze_no_images'  
541+     isAnalizingFields .value  =  false ; 
542+   } 
476543} 
477544
478545async  function  uploadImage(imgBlob , id , fieldName ) {
0 commit comments