@adminforth/bulk-ai-flow 1.24.2 → 1.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build.log CHANGED
@@ -14,5 +14,5 @@ custom/package.json
14
14
  custom/pnpm-lock.yaml
15
15
  custom/tsconfig.json
16
16
 
17
- sent 111,946 bytes received 191 bytes 224,274.00 bytes/sec
18
- total size is 111,232 speedup is 0.99
17
+ sent 112,987 bytes received 191 bytes 226,356.00 bytes/sec
18
+ total size is 112,273 speedup is 0.99
@@ -16,31 +16,7 @@
16
16
  :closable="false"
17
17
  :askForCloseConfirmation="popupMode === 'generation' ? true : false"
18
18
  :closeConfirmationText="t('Are you sure you want to close without saving?')"
19
- :buttons="popupMode === 'generation' ? [
20
- {
21
- label: checkedCount > 1 ? t('Save fields') : t('Save field'),
22
- options: {
23
- disabled: isLoading || checkedCount < 1 || isFetchingRecords || isProcessingAny || isGenerationPaused,
24
- loader: isLoading, class: 'w-fit'
25
- },
26
- onclick: async (dialog) => { await saveData(); dialog.hide(); }
27
- },
28
- {
29
- label: t('Save current'),
30
- options: {
31
- disabled: isLoading || isSavingCurrent || completedRecordIds.size < 1,
32
- loader: isSavingCurrent, class: 'w-fit'
33
- },
34
- onclick: async () => { await saveCurrentGenerated(); }
35
- },
36
- {
37
- label: t('Cancel'),
38
- options: {
39
- class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
40
- },
41
- onclick: (dialog) => confirmDialog.tryToHideModal()
42
- },
43
- ] : popupMode === 'settings' ? [
19
+ :buttons="popupMode === 'generation' ? generationModeButtons : popupMode === 'settings' ? [
44
20
  {
45
21
  label: t('Save settings'),
46
22
  options: {
@@ -50,13 +26,6 @@
50
26
  },
51
27
  ] :
52
28
  [
53
- // {
54
- // label: t('Edit prompts'),
55
- // options: {
56
- // class: 'w-fit ml-auto'
57
- // },
58
- // onclick: (dialog) => { clickSettingsButton(); }
59
- // },
60
29
  {
61
30
  label: t('Cancel'),
62
31
  options: {
@@ -105,7 +74,7 @@
105
74
  :aria-valuemax="totalRecords"
106
75
  >
107
76
  <div
108
- class="h-full bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 transition-all duration-200 "
77
+ class="h-full bg-gradient-to-r from-lightPrimary/70 via-lightPrimary/80 to-lightPrimary/90 dark:from-darkPrimary/70 dark:via-darkPrimary/80 dark:to-darkPrimary/90 transition-all duration-200 "
109
78
  :style="{ width: `${displayedProgressPercent}%` }"
110
79
  ></div>
111
80
  <div class="absolute inset-0 flex items-center justify-center text-sm font-medium text-white drop-shadow">
@@ -228,6 +197,7 @@ const props = defineProps<{
228
197
  }>();
229
198
 
230
199
  type RecordStatus = 'pending' | 'processing' | 'completed' | 'failed';
200
+ type GenerationAction = 'analyze' | 'analyze_no_images' | 'generate_images';
231
201
 
232
202
  type RecordState = {
233
203
  id: string | number;
@@ -287,6 +257,11 @@ const startedRecordCount = ref(0);
287
257
  const isCheckingRateLimits = ref(false);
288
258
  let startGate = Promise.resolve();
289
259
  const tableRef = ref<any>(null);
260
+ const generationFailureGroups = new Map<string, {
261
+ actionType: GenerationAction;
262
+ error: string;
263
+ recordIds: Set<string>;
264
+ }>();
290
265
  const processedCount = computed(() => {
291
266
  recordsVersion.value;
292
267
  return Array.from(recordsById.values()).filter(record => record.status === 'completed' || record.status === 'failed').length;
@@ -328,6 +303,40 @@ const recordsList = computed(() => {
328
303
  : recordIds.value;
329
304
  return ids.map(id => getOrCreateRecord(id));
330
305
  });
306
+
307
+ const generationModeButtons = computed(() => {
308
+ const arrayToReturn = [
309
+ {
310
+ label: checkedCount.value > 1 ? t('Save fields') : t('Save field'),
311
+ options: {
312
+ disabled: isLoading.value || checkedCount.value < 1 || isFetchingRecords.value || isProcessingAny.value || isGenerationPaused.value,
313
+ loader: isLoading.value, class: 'w-fit'
314
+ },
315
+ onclick: async (dialog) => { await saveData(); dialog.hide(); }
316
+ },
317
+ {
318
+ label: t('Cancel'),
319
+ options: {
320
+ class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
321
+ },
322
+ onclick: (dialog) => confirmDialog.value.tryToHideModal()
323
+ },
324
+ ]
325
+
326
+ if (isProcessingAny.value) {
327
+ arrayToReturn.splice(1, 0, {
328
+ label: t('Save processed'),
329
+ options: {
330
+ disabled: isLoading.value || isSavingCurrent.value || completedRecordIds.value.size < 1,
331
+ loader: isSavingCurrent.value, class: 'w-fit'
332
+ },
333
+ onclick: async () => { await saveCurrentGenerated(); }
334
+ })
335
+ }
336
+ return arrayToReturn;
337
+ });
338
+
339
+
331
340
  const isSavingCurrent = ref(false);
332
341
  function checkIfDialogOpen() {
333
342
  return isDialogOpen.value === true;
@@ -395,12 +404,14 @@ async function runAiActions() {
395
404
  isActiveGeneration.value = true;
396
405
  completedRecordIds.value = new Set();
397
406
  startedRecordCount.value = 0;
407
+ generationFailureGroups.clear();
398
408
  await nextTick();
399
409
  tableRef.value?.refresh();
400
410
  const limit = pLimit(props.meta.concurrencyLimit || 10);
401
411
  const tasks = recordIds.value
402
412
  .map(id => limit(() => processOneRecord(String(id))));
403
413
  await Promise.all(tasks);
414
+ showGenerationFailureSummary();
404
415
  isActiveGeneration.value = false;
405
416
  }
406
417
 
@@ -428,6 +439,39 @@ function resetGlobalState() {
428
439
  recordIds.value = [];
429
440
  recordsById.clear();
430
441
  uncheckedRecordIds.clear();
442
+ generationFailureGroups.clear();
443
+ }
444
+
445
+ function getActionLabel(actionType: GenerationAction) {
446
+ return actionType.replace('_', ' ');
447
+ }
448
+
449
+ function registerGenerationFailure(record: RecordState, actionType: GenerationAction, error: string) {
450
+ const key = `${actionType}:${error}`;
451
+ let group = generationFailureGroups.get(key);
452
+ if (!group) {
453
+ group = {
454
+ actionType,
455
+ error,
456
+ recordIds: new Set(),
457
+ };
458
+ generationFailureGroups.set(key, group);
459
+ }
460
+ group.recordIds.add(String(record.id));
461
+ }
462
+
463
+ function showGenerationFailureSummary() {
464
+ for (const group of generationFailureGroups.values()) {
465
+ const failedCount = group.recordIds.size;
466
+ const firstRecordId = Array.from(group.recordIds)[0];
467
+ adminforth.alert({
468
+ message: t(
469
+ `Generation action "${getActionLabel(group.actionType)}" failed for ${failedCount} record(s). First failed record: ${firstRecordId}. Error: ${group.error}`,
470
+ ),
471
+ variant: 'danger',
472
+ timeout: 'unlimited',
473
+ });
474
+ }
431
475
  }
432
476
 
433
477
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -619,7 +663,7 @@ async function processOneRecord(recordId: string) {
619
663
  }
620
664
  }
621
665
 
622
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
666
+ const actions: GenerationAction[] = [];
623
667
  if (props.meta.isImageGeneration) {
624
668
  actions.push('generate_images');
625
669
  }
@@ -642,7 +686,7 @@ async function processOneRecord(recordId: string) {
642
686
 
643
687
  async function checkRateLimits() {
644
688
  isCheckingRateLimits.value = true;
645
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
689
+ const actionsToCheck: GenerationAction[] = [];
646
690
  if (props.meta.isImageGeneration) {
647
691
  actionsToCheck.push('generate_images');
648
692
  }
@@ -661,7 +705,7 @@ async function checkRateLimits() {
661
705
  });
662
706
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
663
707
  adminforth.alert({
664
- message: t(`Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`),
708
+ message: t(`Rate limit exceeded for "${getActionLabel(actionType)}" action. Please try again later.`),
665
709
  variant: 'danger',
666
710
  timeout: 'unlimited',
667
711
  });
@@ -669,7 +713,7 @@ async function checkRateLimits() {
669
713
  }
670
714
  } catch (e) {
671
715
  adminforth.alert({
672
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
716
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
673
717
  variant: 'danger',
674
718
  timeout: 'unlimited',
675
719
  });
@@ -681,7 +725,7 @@ async function checkRateLimits() {
681
725
  return true;
682
726
  }
683
727
 
684
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
728
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
685
729
  if (!checkIfDialogOpen()) {
686
730
  return;
687
731
  }
@@ -719,6 +763,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
719
763
  });
720
764
  } catch (e) {
721
765
  record.aiStatus[responseFlag] = true;
766
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
722
767
  throw e;
723
768
  }
724
769
 
@@ -728,11 +773,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
728
773
 
729
774
  if (createJobResponse?.error || !createJobResponse?.jobId) {
730
775
  record.aiStatus[responseFlag] = true;
731
- adminforth.alert({
732
- message: t(`Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`),
733
- variant: 'danger',
734
- timeout: 'unlimited',
735
- });
776
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
736
777
  throw new Error(createJobResponse?.error || 'Failed to create job');
737
778
  }
738
779
 
@@ -742,7 +783,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
742
783
  async function pollJob(
743
784
  record: RecordState,
744
785
  jobId: string,
745
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
786
+ actionType: GenerationAction,
746
787
  responseFlag: keyof RecordState['aiStatus']
747
788
  ) {
748
789
  let isInProgress = true;
@@ -759,6 +800,7 @@ async function pollJob(
759
800
  }
760
801
  if (jobResponse?.error) {
761
802
  record.aiStatus[responseFlag] = true;
803
+ registerGenerationFailure(record, actionType, jobResponse.error);
762
804
  throw new Error(jobResponse.error);
763
805
  }
764
806
  const jobStatus = jobResponse?.job?.status;
@@ -780,7 +822,7 @@ async function pollJob(
780
822
  }
781
823
  }
782
824
 
783
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
825
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
784
826
  if (actionType === 'generate_images') {
785
827
  for (const fieldName of props.meta.outputImageFields || []) {
786
828
  const resultValue = job?.result?.[fieldName];
@@ -802,12 +844,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
802
844
  touchRecords();
803
845
  }
804
846
 
805
- function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
806
- adminforth.alert({
807
- message: t(`Generation action "${actionType.replace('_', ' ')}" failed for record: ${record.id}. Error: ${job?.error || 'Unknown error'}`),
808
- variant: 'danger',
809
- timeout: 'unlimited',
810
- });
847
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
848
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
811
849
  if (actionType === 'generate_images') {
812
850
  record.imageGenerationFailed = true;
813
851
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -823,7 +861,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
823
861
  touchRecords();
824
862
  }
825
863
 
826
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
864
+ async function waitForRefresh(actionType: GenerationAction) {
827
865
  if (actionType === 'generate_images') {
828
866
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
829
867
  } else if (actionType === 'analyze') {
@@ -1529,4 +1567,4 @@ async function saveCurrentGenerated() {
1529
1567
  props.updateList();
1530
1568
  }
1531
1569
 
1532
- </script>
1570
+ </script>
@@ -16,31 +16,7 @@
16
16
  :closable="false"
17
17
  :askForCloseConfirmation="popupMode === 'generation' ? true : false"
18
18
  :closeConfirmationText="t('Are you sure you want to close without saving?')"
19
- :buttons="popupMode === 'generation' ? [
20
- {
21
- label: checkedCount > 1 ? t('Save fields') : t('Save field'),
22
- options: {
23
- disabled: isLoading || checkedCount < 1 || isFetchingRecords || isProcessingAny || isGenerationPaused,
24
- loader: isLoading, class: 'w-fit'
25
- },
26
- onclick: async (dialog) => { await saveData(); dialog.hide(); }
27
- },
28
- {
29
- label: t('Save current'),
30
- options: {
31
- disabled: isLoading || isSavingCurrent || completedRecordIds.size < 1,
32
- loader: isSavingCurrent, class: 'w-fit'
33
- },
34
- onclick: async () => { await saveCurrentGenerated(); }
35
- },
36
- {
37
- label: t('Cancel'),
38
- options: {
39
- class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
40
- },
41
- onclick: (dialog) => confirmDialog.tryToHideModal()
42
- },
43
- ] : popupMode === 'settings' ? [
19
+ :buttons="popupMode === 'generation' ? generationModeButtons : popupMode === 'settings' ? [
44
20
  {
45
21
  label: t('Save settings'),
46
22
  options: {
@@ -50,13 +26,6 @@
50
26
  },
51
27
  ] :
52
28
  [
53
- // {
54
- // label: t('Edit prompts'),
55
- // options: {
56
- // class: 'w-fit ml-auto'
57
- // },
58
- // onclick: (dialog) => { clickSettingsButton(); }
59
- // },
60
29
  {
61
30
  label: t('Cancel'),
62
31
  options: {
@@ -105,7 +74,7 @@
105
74
  :aria-valuemax="totalRecords"
106
75
  >
107
76
  <div
108
- class="h-full bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 transition-all duration-200 "
77
+ class="h-full bg-gradient-to-r from-lightPrimary/70 via-lightPrimary/80 to-lightPrimary/90 dark:from-darkPrimary/70 dark:via-darkPrimary/80 dark:to-darkPrimary/90 transition-all duration-200 "
109
78
  :style="{ width: `${displayedProgressPercent}%` }"
110
79
  ></div>
111
80
  <div class="absolute inset-0 flex items-center justify-center text-sm font-medium text-white drop-shadow">
@@ -228,6 +197,7 @@ const props = defineProps<{
228
197
  }>();
229
198
 
230
199
  type RecordStatus = 'pending' | 'processing' | 'completed' | 'failed';
200
+ type GenerationAction = 'analyze' | 'analyze_no_images' | 'generate_images';
231
201
 
232
202
  type RecordState = {
233
203
  id: string | number;
@@ -287,6 +257,11 @@ const startedRecordCount = ref(0);
287
257
  const isCheckingRateLimits = ref(false);
288
258
  let startGate = Promise.resolve();
289
259
  const tableRef = ref<any>(null);
260
+ const generationFailureGroups = new Map<string, {
261
+ actionType: GenerationAction;
262
+ error: string;
263
+ recordIds: Set<string>;
264
+ }>();
290
265
  const processedCount = computed(() => {
291
266
  recordsVersion.value;
292
267
  return Array.from(recordsById.values()).filter(record => record.status === 'completed' || record.status === 'failed').length;
@@ -328,6 +303,40 @@ const recordsList = computed(() => {
328
303
  : recordIds.value;
329
304
  return ids.map(id => getOrCreateRecord(id));
330
305
  });
306
+
307
+ const generationModeButtons = computed(() => {
308
+ const arrayToReturn = [
309
+ {
310
+ label: checkedCount.value > 1 ? t('Save fields') : t('Save field'),
311
+ options: {
312
+ disabled: isLoading.value || checkedCount.value < 1 || isFetchingRecords.value || isProcessingAny.value || isGenerationPaused.value,
313
+ loader: isLoading.value, class: 'w-fit'
314
+ },
315
+ onclick: async (dialog) => { await saveData(); dialog.hide(); }
316
+ },
317
+ {
318
+ label: t('Cancel'),
319
+ options: {
320
+ class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
321
+ },
322
+ onclick: (dialog) => confirmDialog.value.tryToHideModal()
323
+ },
324
+ ]
325
+
326
+ if (isProcessingAny.value) {
327
+ arrayToReturn.splice(1, 0, {
328
+ label: t('Save processed'),
329
+ options: {
330
+ disabled: isLoading.value || isSavingCurrent.value || completedRecordIds.value.size < 1,
331
+ loader: isSavingCurrent.value, class: 'w-fit'
332
+ },
333
+ onclick: async () => { await saveCurrentGenerated(); }
334
+ })
335
+ }
336
+ return arrayToReturn;
337
+ });
338
+
339
+
331
340
  const isSavingCurrent = ref(false);
332
341
  function checkIfDialogOpen() {
333
342
  return isDialogOpen.value === true;
@@ -395,12 +404,14 @@ async function runAiActions() {
395
404
  isActiveGeneration.value = true;
396
405
  completedRecordIds.value = new Set();
397
406
  startedRecordCount.value = 0;
407
+ generationFailureGroups.clear();
398
408
  await nextTick();
399
409
  tableRef.value?.refresh();
400
410
  const limit = pLimit(props.meta.concurrencyLimit || 10);
401
411
  const tasks = recordIds.value
402
412
  .map(id => limit(() => processOneRecord(String(id))));
403
413
  await Promise.all(tasks);
414
+ showGenerationFailureSummary();
404
415
  isActiveGeneration.value = false;
405
416
  }
406
417
 
@@ -428,6 +439,39 @@ function resetGlobalState() {
428
439
  recordIds.value = [];
429
440
  recordsById.clear();
430
441
  uncheckedRecordIds.clear();
442
+ generationFailureGroups.clear();
443
+ }
444
+
445
+ function getActionLabel(actionType: GenerationAction) {
446
+ return actionType.replace('_', ' ');
447
+ }
448
+
449
+ function registerGenerationFailure(record: RecordState, actionType: GenerationAction, error: string) {
450
+ const key = `${actionType}:${error}`;
451
+ let group = generationFailureGroups.get(key);
452
+ if (!group) {
453
+ group = {
454
+ actionType,
455
+ error,
456
+ recordIds: new Set(),
457
+ };
458
+ generationFailureGroups.set(key, group);
459
+ }
460
+ group.recordIds.add(String(record.id));
461
+ }
462
+
463
+ function showGenerationFailureSummary() {
464
+ for (const group of generationFailureGroups.values()) {
465
+ const failedCount = group.recordIds.size;
466
+ const firstRecordId = Array.from(group.recordIds)[0];
467
+ adminforth.alert({
468
+ message: t(
469
+ `Generation action "${getActionLabel(group.actionType)}" failed for ${failedCount} record(s). First failed record: ${firstRecordId}. Error: ${group.error}`,
470
+ ),
471
+ variant: 'danger',
472
+ timeout: 'unlimited',
473
+ });
474
+ }
431
475
  }
432
476
 
433
477
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -619,7 +663,7 @@ async function processOneRecord(recordId: string) {
619
663
  }
620
664
  }
621
665
 
622
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
666
+ const actions: GenerationAction[] = [];
623
667
  if (props.meta.isImageGeneration) {
624
668
  actions.push('generate_images');
625
669
  }
@@ -642,7 +686,7 @@ async function processOneRecord(recordId: string) {
642
686
 
643
687
  async function checkRateLimits() {
644
688
  isCheckingRateLimits.value = true;
645
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
689
+ const actionsToCheck: GenerationAction[] = [];
646
690
  if (props.meta.isImageGeneration) {
647
691
  actionsToCheck.push('generate_images');
648
692
  }
@@ -661,7 +705,7 @@ async function checkRateLimits() {
661
705
  });
662
706
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
663
707
  adminforth.alert({
664
- message: t(`Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`),
708
+ message: t(`Rate limit exceeded for "${getActionLabel(actionType)}" action. Please try again later.`),
665
709
  variant: 'danger',
666
710
  timeout: 'unlimited',
667
711
  });
@@ -669,7 +713,7 @@ async function checkRateLimits() {
669
713
  }
670
714
  } catch (e) {
671
715
  adminforth.alert({
672
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
716
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
673
717
  variant: 'danger',
674
718
  timeout: 'unlimited',
675
719
  });
@@ -681,7 +725,7 @@ async function checkRateLimits() {
681
725
  return true;
682
726
  }
683
727
 
684
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
728
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
685
729
  if (!checkIfDialogOpen()) {
686
730
  return;
687
731
  }
@@ -719,6 +763,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
719
763
  });
720
764
  } catch (e) {
721
765
  record.aiStatus[responseFlag] = true;
766
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
722
767
  throw e;
723
768
  }
724
769
 
@@ -728,11 +773,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
728
773
 
729
774
  if (createJobResponse?.error || !createJobResponse?.jobId) {
730
775
  record.aiStatus[responseFlag] = true;
731
- adminforth.alert({
732
- message: t(`Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`),
733
- variant: 'danger',
734
- timeout: 'unlimited',
735
- });
776
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
736
777
  throw new Error(createJobResponse?.error || 'Failed to create job');
737
778
  }
738
779
 
@@ -742,7 +783,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
742
783
  async function pollJob(
743
784
  record: RecordState,
744
785
  jobId: string,
745
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
786
+ actionType: GenerationAction,
746
787
  responseFlag: keyof RecordState['aiStatus']
747
788
  ) {
748
789
  let isInProgress = true;
@@ -759,6 +800,7 @@ async function pollJob(
759
800
  }
760
801
  if (jobResponse?.error) {
761
802
  record.aiStatus[responseFlag] = true;
803
+ registerGenerationFailure(record, actionType, jobResponse.error);
762
804
  throw new Error(jobResponse.error);
763
805
  }
764
806
  const jobStatus = jobResponse?.job?.status;
@@ -780,7 +822,7 @@ async function pollJob(
780
822
  }
781
823
  }
782
824
 
783
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
825
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
784
826
  if (actionType === 'generate_images') {
785
827
  for (const fieldName of props.meta.outputImageFields || []) {
786
828
  const resultValue = job?.result?.[fieldName];
@@ -802,12 +844,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
802
844
  touchRecords();
803
845
  }
804
846
 
805
- function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
806
- adminforth.alert({
807
- message: t(`Generation action "${actionType.replace('_', ' ')}" failed for record: ${record.id}. Error: ${job?.error || 'Unknown error'}`),
808
- variant: 'danger',
809
- timeout: 'unlimited',
810
- });
847
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
848
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
811
849
  if (actionType === 'generate_images') {
812
850
  record.imageGenerationFailed = true;
813
851
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -823,7 +861,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
823
861
  touchRecords();
824
862
  }
825
863
 
826
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
864
+ async function waitForRefresh(actionType: GenerationAction) {
827
865
  if (actionType === 'generate_images') {
828
866
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
829
867
  } else if (actionType === 'analyze') {
@@ -1529,4 +1567,4 @@ async function saveCurrentGenerated() {
1529
1567
  props.updateList();
1530
1568
  }
1531
1569
 
1532
- </script>
1570
+ </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.24.2",
3
+ "version": "1.24.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },