@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 +2 -2
- package/custom/VisionAction.vue +91 -53
- package/dist/custom/VisionAction.vue +91 -53
- package/package.json +1 -1
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
|
|
18
|
-
total size is
|
|
17
|
+
sent 112,987 bytes received 191 bytes 226,356.00 bytes/sec
|
|
18
|
+
total size is 112,273 speedup is 0.99
|
package/custom/VisionAction.vue
CHANGED
|
@@ -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-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
806
|
-
|
|
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:
|
|
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-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
806
|
-
|
|
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:
|
|
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>
|