@adminforth/bulk-ai-flow 1.24.3 → 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,955 bytes received 191 bytes 224,292.00 bytes/sec
18
- total size is 111,251 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
@@ -197,6 +197,7 @@ const props = defineProps<{
197
197
  }>();
198
198
 
199
199
  type RecordStatus = 'pending' | 'processing' | 'completed' | 'failed';
200
+ type GenerationAction = 'analyze' | 'analyze_no_images' | 'generate_images';
200
201
 
201
202
  type RecordState = {
202
203
  id: string | number;
@@ -256,6 +257,11 @@ const startedRecordCount = ref(0);
256
257
  const isCheckingRateLimits = ref(false);
257
258
  let startGate = Promise.resolve();
258
259
  const tableRef = ref<any>(null);
260
+ const generationFailureGroups = new Map<string, {
261
+ actionType: GenerationAction;
262
+ error: string;
263
+ recordIds: Set<string>;
264
+ }>();
259
265
  const processedCount = computed(() => {
260
266
  recordsVersion.value;
261
267
  return Array.from(recordsById.values()).filter(record => record.status === 'completed' || record.status === 'failed').length;
@@ -398,12 +404,14 @@ async function runAiActions() {
398
404
  isActiveGeneration.value = true;
399
405
  completedRecordIds.value = new Set();
400
406
  startedRecordCount.value = 0;
407
+ generationFailureGroups.clear();
401
408
  await nextTick();
402
409
  tableRef.value?.refresh();
403
410
  const limit = pLimit(props.meta.concurrencyLimit || 10);
404
411
  const tasks = recordIds.value
405
412
  .map(id => limit(() => processOneRecord(String(id))));
406
413
  await Promise.all(tasks);
414
+ showGenerationFailureSummary();
407
415
  isActiveGeneration.value = false;
408
416
  }
409
417
 
@@ -431,6 +439,39 @@ function resetGlobalState() {
431
439
  recordIds.value = [];
432
440
  recordsById.clear();
433
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
+ }
434
475
  }
435
476
 
436
477
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -622,7 +663,7 @@ async function processOneRecord(recordId: string) {
622
663
  }
623
664
  }
624
665
 
625
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
666
+ const actions: GenerationAction[] = [];
626
667
  if (props.meta.isImageGeneration) {
627
668
  actions.push('generate_images');
628
669
  }
@@ -645,7 +686,7 @@ async function processOneRecord(recordId: string) {
645
686
 
646
687
  async function checkRateLimits() {
647
688
  isCheckingRateLimits.value = true;
648
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
689
+ const actionsToCheck: GenerationAction[] = [];
649
690
  if (props.meta.isImageGeneration) {
650
691
  actionsToCheck.push('generate_images');
651
692
  }
@@ -664,7 +705,7 @@ async function checkRateLimits() {
664
705
  });
665
706
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
666
707
  adminforth.alert({
667
- 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.`),
668
709
  variant: 'danger',
669
710
  timeout: 'unlimited',
670
711
  });
@@ -672,7 +713,7 @@ async function checkRateLimits() {
672
713
  }
673
714
  } catch (e) {
674
715
  adminforth.alert({
675
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
716
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
676
717
  variant: 'danger',
677
718
  timeout: 'unlimited',
678
719
  });
@@ -684,7 +725,7 @@ async function checkRateLimits() {
684
725
  return true;
685
726
  }
686
727
 
687
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
728
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
688
729
  if (!checkIfDialogOpen()) {
689
730
  return;
690
731
  }
@@ -722,6 +763,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
722
763
  });
723
764
  } catch (e) {
724
765
  record.aiStatus[responseFlag] = true;
766
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
725
767
  throw e;
726
768
  }
727
769
 
@@ -731,11 +773,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
731
773
 
732
774
  if (createJobResponse?.error || !createJobResponse?.jobId) {
733
775
  record.aiStatus[responseFlag] = true;
734
- adminforth.alert({
735
- message: t(`Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`),
736
- variant: 'danger',
737
- timeout: 'unlimited',
738
- });
776
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
739
777
  throw new Error(createJobResponse?.error || 'Failed to create job');
740
778
  }
741
779
 
@@ -745,7 +783,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
745
783
  async function pollJob(
746
784
  record: RecordState,
747
785
  jobId: string,
748
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
786
+ actionType: GenerationAction,
749
787
  responseFlag: keyof RecordState['aiStatus']
750
788
  ) {
751
789
  let isInProgress = true;
@@ -762,6 +800,7 @@ async function pollJob(
762
800
  }
763
801
  if (jobResponse?.error) {
764
802
  record.aiStatus[responseFlag] = true;
803
+ registerGenerationFailure(record, actionType, jobResponse.error);
765
804
  throw new Error(jobResponse.error);
766
805
  }
767
806
  const jobStatus = jobResponse?.job?.status;
@@ -783,7 +822,7 @@ async function pollJob(
783
822
  }
784
823
  }
785
824
 
786
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
825
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
787
826
  if (actionType === 'generate_images') {
788
827
  for (const fieldName of props.meta.outputImageFields || []) {
789
828
  const resultValue = job?.result?.[fieldName];
@@ -805,12 +844,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
805
844
  touchRecords();
806
845
  }
807
846
 
808
- function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
809
- adminforth.alert({
810
- message: t(`Generation action "${actionType.replace('_', ' ')}" failed for record: ${record.id}. Error: ${job?.error || 'Unknown error'}`),
811
- variant: 'danger',
812
- timeout: 'unlimited',
813
- });
847
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
848
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
814
849
  if (actionType === 'generate_images') {
815
850
  record.imageGenerationFailed = true;
816
851
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -826,7 +861,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
826
861
  touchRecords();
827
862
  }
828
863
 
829
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
864
+ async function waitForRefresh(actionType: GenerationAction) {
830
865
  if (actionType === 'generate_images') {
831
866
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
832
867
  } else if (actionType === 'analyze') {
@@ -1532,4 +1567,4 @@ async function saveCurrentGenerated() {
1532
1567
  props.updateList();
1533
1568
  }
1534
1569
 
1535
- </script>
1570
+ </script>
@@ -197,6 +197,7 @@ const props = defineProps<{
197
197
  }>();
198
198
 
199
199
  type RecordStatus = 'pending' | 'processing' | 'completed' | 'failed';
200
+ type GenerationAction = 'analyze' | 'analyze_no_images' | 'generate_images';
200
201
 
201
202
  type RecordState = {
202
203
  id: string | number;
@@ -256,6 +257,11 @@ const startedRecordCount = ref(0);
256
257
  const isCheckingRateLimits = ref(false);
257
258
  let startGate = Promise.resolve();
258
259
  const tableRef = ref<any>(null);
260
+ const generationFailureGroups = new Map<string, {
261
+ actionType: GenerationAction;
262
+ error: string;
263
+ recordIds: Set<string>;
264
+ }>();
259
265
  const processedCount = computed(() => {
260
266
  recordsVersion.value;
261
267
  return Array.from(recordsById.values()).filter(record => record.status === 'completed' || record.status === 'failed').length;
@@ -398,12 +404,14 @@ async function runAiActions() {
398
404
  isActiveGeneration.value = true;
399
405
  completedRecordIds.value = new Set();
400
406
  startedRecordCount.value = 0;
407
+ generationFailureGroups.clear();
401
408
  await nextTick();
402
409
  tableRef.value?.refresh();
403
410
  const limit = pLimit(props.meta.concurrencyLimit || 10);
404
411
  const tasks = recordIds.value
405
412
  .map(id => limit(() => processOneRecord(String(id))));
406
413
  await Promise.all(tasks);
414
+ showGenerationFailureSummary();
407
415
  isActiveGeneration.value = false;
408
416
  }
409
417
 
@@ -431,6 +439,39 @@ function resetGlobalState() {
431
439
  recordIds.value = [];
432
440
  recordsById.clear();
433
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
+ }
434
475
  }
435
476
 
436
477
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -622,7 +663,7 @@ async function processOneRecord(recordId: string) {
622
663
  }
623
664
  }
624
665
 
625
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
666
+ const actions: GenerationAction[] = [];
626
667
  if (props.meta.isImageGeneration) {
627
668
  actions.push('generate_images');
628
669
  }
@@ -645,7 +686,7 @@ async function processOneRecord(recordId: string) {
645
686
 
646
687
  async function checkRateLimits() {
647
688
  isCheckingRateLimits.value = true;
648
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
689
+ const actionsToCheck: GenerationAction[] = [];
649
690
  if (props.meta.isImageGeneration) {
650
691
  actionsToCheck.push('generate_images');
651
692
  }
@@ -664,7 +705,7 @@ async function checkRateLimits() {
664
705
  });
665
706
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
666
707
  adminforth.alert({
667
- 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.`),
668
709
  variant: 'danger',
669
710
  timeout: 'unlimited',
670
711
  });
@@ -672,7 +713,7 @@ async function checkRateLimits() {
672
713
  }
673
714
  } catch (e) {
674
715
  adminforth.alert({
675
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
716
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
676
717
  variant: 'danger',
677
718
  timeout: 'unlimited',
678
719
  });
@@ -684,7 +725,7 @@ async function checkRateLimits() {
684
725
  return true;
685
726
  }
686
727
 
687
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
728
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
688
729
  if (!checkIfDialogOpen()) {
689
730
  return;
690
731
  }
@@ -722,6 +763,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
722
763
  });
723
764
  } catch (e) {
724
765
  record.aiStatus[responseFlag] = true;
766
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
725
767
  throw e;
726
768
  }
727
769
 
@@ -731,11 +773,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
731
773
 
732
774
  if (createJobResponse?.error || !createJobResponse?.jobId) {
733
775
  record.aiStatus[responseFlag] = true;
734
- adminforth.alert({
735
- message: t(`Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`),
736
- variant: 'danger',
737
- timeout: 'unlimited',
738
- });
776
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
739
777
  throw new Error(createJobResponse?.error || 'Failed to create job');
740
778
  }
741
779
 
@@ -745,7 +783,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
745
783
  async function pollJob(
746
784
  record: RecordState,
747
785
  jobId: string,
748
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
786
+ actionType: GenerationAction,
749
787
  responseFlag: keyof RecordState['aiStatus']
750
788
  ) {
751
789
  let isInProgress = true;
@@ -762,6 +800,7 @@ async function pollJob(
762
800
  }
763
801
  if (jobResponse?.error) {
764
802
  record.aiStatus[responseFlag] = true;
803
+ registerGenerationFailure(record, actionType, jobResponse.error);
765
804
  throw new Error(jobResponse.error);
766
805
  }
767
806
  const jobStatus = jobResponse?.job?.status;
@@ -783,7 +822,7 @@ async function pollJob(
783
822
  }
784
823
  }
785
824
 
786
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
825
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
787
826
  if (actionType === 'generate_images') {
788
827
  for (const fieldName of props.meta.outputImageFields || []) {
789
828
  const resultValue = job?.result?.[fieldName];
@@ -805,12 +844,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
805
844
  touchRecords();
806
845
  }
807
846
 
808
- function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
809
- adminforth.alert({
810
- message: t(`Generation action "${actionType.replace('_', ' ')}" failed for record: ${record.id}. Error: ${job?.error || 'Unknown error'}`),
811
- variant: 'danger',
812
- timeout: 'unlimited',
813
- });
847
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
848
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
814
849
  if (actionType === 'generate_images') {
815
850
  record.imageGenerationFailed = true;
816
851
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -826,7 +861,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
826
861
  touchRecords();
827
862
  }
828
863
 
829
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
864
+ async function waitForRefresh(actionType: GenerationAction) {
830
865
  if (actionType === 'generate_images') {
831
866
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
832
867
  } else if (actionType === 'analyze') {
@@ -1532,4 +1567,4 @@ async function saveCurrentGenerated() {
1532
1567
  props.updateList();
1533
1568
  }
1534
1569
 
1535
- </script>
1570
+ </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.24.3",
3
+ "version": "1.24.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },