@adminforth/bulk-ai-flow 1.24.3 → 1.24.5

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 113,381 bytes received 191 bytes 227,144.00 bytes/sec
18
+ total size is 112,667 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,47 @@ 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 getGenerationFailureGroupKey(actionType: GenerationAction, error: string) {
450
+ const normalizedError = error
451
+ .replace(/Please retry in [\d.]+s\.?/g, 'Please retry later.')
452
+ .replace(/\b\d+\.\d+s\b/g, '<duration>')
453
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, '<uuid>');
454
+ return `${actionType}:${normalizedError}`;
455
+ }
456
+
457
+ function registerGenerationFailure(record: RecordState, actionType: GenerationAction, error: string) {
458
+ const key = getGenerationFailureGroupKey(actionType, error);
459
+ let group = generationFailureGroups.get(key);
460
+ if (!group) {
461
+ group = {
462
+ actionType,
463
+ error,
464
+ recordIds: new Set(),
465
+ };
466
+ generationFailureGroups.set(key, group);
467
+ }
468
+ group.recordIds.add(String(record.id));
469
+ }
470
+
471
+ function showGenerationFailureSummary() {
472
+ for (const group of generationFailureGroups.values()) {
473
+ const failedCount = group.recordIds.size;
474
+ const firstRecordId = Array.from(group.recordIds)[0];
475
+ adminforth.alert({
476
+ message: t(
477
+ `Generation action "${getActionLabel(group.actionType)}" failed for ${failedCount} record(s). First failed record: ${firstRecordId}. Error: ${group.error}`,
478
+ ),
479
+ variant: 'danger',
480
+ timeout: 'unlimited',
481
+ });
482
+ }
434
483
  }
435
484
 
436
485
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -622,7 +671,7 @@ async function processOneRecord(recordId: string) {
622
671
  }
623
672
  }
624
673
 
625
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
674
+ const actions: GenerationAction[] = [];
626
675
  if (props.meta.isImageGeneration) {
627
676
  actions.push('generate_images');
628
677
  }
@@ -645,7 +694,7 @@ async function processOneRecord(recordId: string) {
645
694
 
646
695
  async function checkRateLimits() {
647
696
  isCheckingRateLimits.value = true;
648
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
697
+ const actionsToCheck: GenerationAction[] = [];
649
698
  if (props.meta.isImageGeneration) {
650
699
  actionsToCheck.push('generate_images');
651
700
  }
@@ -664,7 +713,7 @@ async function checkRateLimits() {
664
713
  });
665
714
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
666
715
  adminforth.alert({
667
- message: t(`Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`),
716
+ message: t(`Rate limit exceeded for "${getActionLabel(actionType)}" action. Please try again later.`),
668
717
  variant: 'danger',
669
718
  timeout: 'unlimited',
670
719
  });
@@ -672,7 +721,7 @@ async function checkRateLimits() {
672
721
  }
673
722
  } catch (e) {
674
723
  adminforth.alert({
675
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
724
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
676
725
  variant: 'danger',
677
726
  timeout: 'unlimited',
678
727
  });
@@ -684,7 +733,7 @@ async function checkRateLimits() {
684
733
  return true;
685
734
  }
686
735
 
687
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
736
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
688
737
  if (!checkIfDialogOpen()) {
689
738
  return;
690
739
  }
@@ -722,6 +771,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
722
771
  });
723
772
  } catch (e) {
724
773
  record.aiStatus[responseFlag] = true;
774
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
725
775
  throw e;
726
776
  }
727
777
 
@@ -731,11 +781,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
731
781
 
732
782
  if (createJobResponse?.error || !createJobResponse?.jobId) {
733
783
  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
- });
784
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
739
785
  throw new Error(createJobResponse?.error || 'Failed to create job');
740
786
  }
741
787
 
@@ -745,7 +791,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
745
791
  async function pollJob(
746
792
  record: RecordState,
747
793
  jobId: string,
748
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
794
+ actionType: GenerationAction,
749
795
  responseFlag: keyof RecordState['aiStatus']
750
796
  ) {
751
797
  let isInProgress = true;
@@ -762,6 +808,7 @@ async function pollJob(
762
808
  }
763
809
  if (jobResponse?.error) {
764
810
  record.aiStatus[responseFlag] = true;
811
+ registerGenerationFailure(record, actionType, jobResponse.error);
765
812
  throw new Error(jobResponse.error);
766
813
  }
767
814
  const jobStatus = jobResponse?.job?.status;
@@ -783,7 +830,7 @@ async function pollJob(
783
830
  }
784
831
  }
785
832
 
786
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
833
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
787
834
  if (actionType === 'generate_images') {
788
835
  for (const fieldName of props.meta.outputImageFields || []) {
789
836
  const resultValue = job?.result?.[fieldName];
@@ -805,12 +852,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
805
852
  touchRecords();
806
853
  }
807
854
 
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
- });
855
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
856
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
814
857
  if (actionType === 'generate_images') {
815
858
  record.imageGenerationFailed = true;
816
859
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -826,7 +869,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
826
869
  touchRecords();
827
870
  }
828
871
 
829
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
872
+ async function waitForRefresh(actionType: GenerationAction) {
830
873
  if (actionType === 'generate_images') {
831
874
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
832
875
  } else if (actionType === 'analyze') {
@@ -1532,4 +1575,4 @@ async function saveCurrentGenerated() {
1532
1575
  props.updateList();
1533
1576
  }
1534
1577
 
1535
- </script>
1578
+ </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,47 @@ 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 getGenerationFailureGroupKey(actionType: GenerationAction, error: string) {
450
+ const normalizedError = error
451
+ .replace(/Please retry in [\d.]+s\.?/g, 'Please retry later.')
452
+ .replace(/\b\d+\.\d+s\b/g, '<duration>')
453
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, '<uuid>');
454
+ return `${actionType}:${normalizedError}`;
455
+ }
456
+
457
+ function registerGenerationFailure(record: RecordState, actionType: GenerationAction, error: string) {
458
+ const key = getGenerationFailureGroupKey(actionType, error);
459
+ let group = generationFailureGroups.get(key);
460
+ if (!group) {
461
+ group = {
462
+ actionType,
463
+ error,
464
+ recordIds: new Set(),
465
+ };
466
+ generationFailureGroups.set(key, group);
467
+ }
468
+ group.recordIds.add(String(record.id));
469
+ }
470
+
471
+ function showGenerationFailureSummary() {
472
+ for (const group of generationFailureGroups.values()) {
473
+ const failedCount = group.recordIds.size;
474
+ const firstRecordId = Array.from(group.recordIds)[0];
475
+ adminforth.alert({
476
+ message: t(
477
+ `Generation action "${getActionLabel(group.actionType)}" failed for ${failedCount} record(s). First failed record: ${firstRecordId}. Error: ${group.error}`,
478
+ ),
479
+ variant: 'danger',
480
+ timeout: 'unlimited',
481
+ });
482
+ }
434
483
  }
435
484
 
436
485
  function getOrCreateRecord(recordId: string | number): RecordState {
@@ -622,7 +671,7 @@ async function processOneRecord(recordId: string) {
622
671
  }
623
672
  }
624
673
 
625
- const actions: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
674
+ const actions: GenerationAction[] = [];
626
675
  if (props.meta.isImageGeneration) {
627
676
  actions.push('generate_images');
628
677
  }
@@ -645,7 +694,7 @@ async function processOneRecord(recordId: string) {
645
694
 
646
695
  async function checkRateLimits() {
647
696
  isCheckingRateLimits.value = true;
648
- const actionsToCheck: Array<'generate_images' | 'analyze' | 'analyze_no_images'> = [];
697
+ const actionsToCheck: GenerationAction[] = [];
649
698
  if (props.meta.isImageGeneration) {
650
699
  actionsToCheck.push('generate_images');
651
700
  }
@@ -664,7 +713,7 @@ async function checkRateLimits() {
664
713
  });
665
714
  if (rateLimitRes?.error || rateLimitRes?.ok === false) {
666
715
  adminforth.alert({
667
- message: t(`Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`),
716
+ message: t(`Rate limit exceeded for "${getActionLabel(actionType)}" action. Please try again later.`),
668
717
  variant: 'danger',
669
718
  timeout: 'unlimited',
670
719
  });
@@ -672,7 +721,7 @@ async function checkRateLimits() {
672
721
  }
673
722
  } catch (e) {
674
723
  adminforth.alert({
675
- message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
724
+ message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
676
725
  variant: 'danger',
677
726
  timeout: 'unlimited',
678
727
  });
@@ -684,7 +733,7 @@ async function checkRateLimits() {
684
733
  return true;
685
734
  }
686
735
 
687
- async function runActionForRecord(record: RecordState, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
736
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
688
737
  if (!checkIfDialogOpen()) {
689
738
  return;
690
739
  }
@@ -722,6 +771,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
722
771
  });
723
772
  } catch (e) {
724
773
  record.aiStatus[responseFlag] = true;
774
+ registerGenerationFailure(record, actionType, e instanceof Error ? e.message : String(e));
725
775
  throw e;
726
776
  }
727
777
 
@@ -731,11 +781,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
731
781
 
732
782
  if (createJobResponse?.error || !createJobResponse?.jobId) {
733
783
  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
- });
784
+ registerGenerationFailure(record, actionType, createJobResponse?.error || `Failed to ${getActionLabel(actionType)}. Please, try to re-run the action.`);
739
785
  throw new Error(createJobResponse?.error || 'Failed to create job');
740
786
  }
741
787
 
@@ -745,7 +791,7 @@ async function runActionForRecord(record: RecordState, actionType: 'analyze' | '
745
791
  async function pollJob(
746
792
  record: RecordState,
747
793
  jobId: string,
748
- actionType: 'analyze' | 'analyze_no_images' | 'generate_images',
794
+ actionType: GenerationAction,
749
795
  responseFlag: keyof RecordState['aiStatus']
750
796
  ) {
751
797
  let isInProgress = true;
@@ -762,6 +808,7 @@ async function pollJob(
762
808
  }
763
809
  if (jobResponse?.error) {
764
810
  record.aiStatus[responseFlag] = true;
811
+ registerGenerationFailure(record, actionType, jobResponse.error);
765
812
  throw new Error(jobResponse.error);
766
813
  }
767
814
  const jobStatus = jobResponse?.job?.status;
@@ -783,7 +830,7 @@ async function pollJob(
783
830
  }
784
831
  }
785
832
 
786
- function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
833
+ function applyJobResult(record: RecordState, job: any, actionType: GenerationAction) {
787
834
  if (actionType === 'generate_images') {
788
835
  for (const fieldName of props.meta.outputImageFields || []) {
789
836
  const resultValue = job?.result?.[fieldName];
@@ -805,12 +852,8 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
805
852
  touchRecords();
806
853
  }
807
854
 
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
- });
855
+ function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
856
+ registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
814
857
  if (actionType === 'generate_images') {
815
858
  record.imageGenerationFailed = true;
816
859
  record.imageGenerationErrorMessage = job?.error || 'Unknown error';
@@ -826,7 +869,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
826
869
  touchRecords();
827
870
  }
828
871
 
829
- async function waitForRefresh(actionType: 'analyze' | 'analyze_no_images' | 'generate_images') {
872
+ async function waitForRefresh(actionType: GenerationAction) {
830
873
  if (actionType === 'generate_images') {
831
874
  await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
832
875
  } else if (actionType === 'analyze') {
@@ -1532,4 +1575,4 @@ async function saveCurrentGenerated() {
1532
1575
  props.updateList();
1533
1576
  }
1534
1577
 
1535
- </script>
1578
+ </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.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },