@adminforth/bulk-ai-flow 1.8.1 → 1.9.0
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/ImageGenerationCarousel.vue +42 -13
- package/custom/VisionAction.vue +153 -64
- package/custom/VisionTable.vue +3 -1
- package/dist/custom/ImageGenerationCarousel.vue +42 -13
- package/dist/custom/VisionAction.vue +153 -64
- package/dist/custom/VisionTable.vue +3 -1
- package/dist/index.js +310 -195
- package/index.ts +315 -207
- package/package.json +1 -1
- package/types.ts +9 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
header="Bulk AI Flow"
|
|
11
11
|
class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
|
|
12
12
|
: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(); } },
|
|
14
14
|
{ label: 'Cancel', onclick: (dialog) => dialog.hide() },
|
|
15
15
|
]"
|
|
16
16
|
>
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
@error="handleTableError"
|
|
34
34
|
:carouselSaveImages="carouselSaveImages"
|
|
35
35
|
:carouselImageIndex="carouselImageIndex"
|
|
36
|
+
:regenerateImagesRefreshRate="props.meta.refreshRates?.regenerateImages"
|
|
36
37
|
/>
|
|
37
38
|
</div>
|
|
38
39
|
<div class="text-red-600 flex items-center w-full">
|
|
@@ -88,9 +89,14 @@ const isCriticalError = ref(false);
|
|
|
88
89
|
const isImageGenerationError = ref(false);
|
|
89
90
|
const errorMessage = ref('');
|
|
90
91
|
const checkedCount = ref(0);
|
|
92
|
+
const isGeneratingImages = ref(false);
|
|
93
|
+
const isAnalizingFields = ref(false);
|
|
94
|
+
const isAnalizingImages = ref(false);
|
|
95
|
+
|
|
91
96
|
|
|
92
97
|
const openDialog = async () => {
|
|
93
98
|
confirmDialog.value.open();
|
|
99
|
+
isFetchingRecords.value = true;
|
|
94
100
|
await getRecords();
|
|
95
101
|
if (props.meta.isAttachFiles) {
|
|
96
102
|
await getImages();
|
|
@@ -101,42 +107,41 @@ const openDialog = async () => {
|
|
|
101
107
|
tableColumnsIndexes.value = result.indexes;
|
|
102
108
|
customFieldNames.value = tableHeaders.value.slice((props.meta.isAttachFiles) ? 3 : 2).map(h => h.fieldName);
|
|
103
109
|
setSelected();
|
|
110
|
+
if (props.meta.isImageGeneration) {
|
|
111
|
+
fillCarouselSaveImages();
|
|
112
|
+
}
|
|
104
113
|
for (let i = 0; i < selected.value?.length; i++) {
|
|
105
114
|
openGenerationCarousel.value[i] = props.meta.outputImageFields?.reduce((acc,key) =>{
|
|
106
115
|
acc[key] = false;
|
|
107
116
|
return acc;
|
|
108
117
|
},{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
|
|
109
118
|
}
|
|
110
|
-
isFetchingRecords.value =
|
|
111
|
-
|
|
119
|
+
isFetchingRecords.value = false;
|
|
120
|
+
|
|
121
|
+
if (props.meta.isImageGeneration) {
|
|
122
|
+
isGeneratingImages.value = true;
|
|
123
|
+
runAiAction({
|
|
124
|
+
endpoint: 'initial_image_generate',
|
|
125
|
+
actionType: 'generate_images',
|
|
126
|
+
responseFlag: isAiResponseReceivedImage,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
112
129
|
if (props.meta.isFieldsForAnalizeFromImages) {
|
|
113
|
-
|
|
130
|
+
isAnalizingImages.value = true;
|
|
131
|
+
runAiAction({
|
|
114
132
|
endpoint: 'analyze',
|
|
115
133
|
actionType: 'analyze',
|
|
116
134
|
responseFlag: isAiResponseReceivedAnalize,
|
|
117
|
-
})
|
|
135
|
+
});
|
|
118
136
|
}
|
|
119
137
|
if (props.meta.isFieldsForAnalizePlain) {
|
|
120
|
-
|
|
138
|
+
isAnalizingFields.value = true;
|
|
139
|
+
runAiAction({
|
|
121
140
|
endpoint: 'analyze_no_images',
|
|
122
141
|
actionType: 'analyze_no_images',
|
|
123
142
|
responseFlag: isAiResponseReceivedAnalize,
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
if (props.meta.isImageGeneration) {
|
|
127
|
-
tasks.push(runAiAction({
|
|
128
|
-
endpoint: 'initial_image_generate',
|
|
129
|
-
actionType: 'generate_images',
|
|
130
|
-
responseFlag: isAiResponseReceivedImage,
|
|
131
|
-
}));
|
|
132
|
-
}
|
|
133
|
-
await Promise.all(tasks);
|
|
134
|
-
|
|
135
|
-
if (props.meta.isImageGeneration) {
|
|
136
|
-
fillCarouselSaveImages();
|
|
143
|
+
});
|
|
137
144
|
}
|
|
138
|
-
|
|
139
|
-
isFetchingRecords.value = false;
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
watch(selected, (val) => {
|
|
@@ -149,10 +154,10 @@ function fillCarouselSaveImages() {
|
|
|
149
154
|
const tempItem: any = {};
|
|
150
155
|
const tempItemIndex: any = {};
|
|
151
156
|
for (const [key, value] of Object.entries(item)) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
158
|
+
tempItem[key] = [];
|
|
159
|
+
tempItemIndex[key] = 0;
|
|
160
|
+
}
|
|
156
161
|
}
|
|
157
162
|
carouselSaveImages.value.push(tempItem);
|
|
158
163
|
carouselImageIndex.value.push(tempItemIndex);
|
|
@@ -398,38 +403,129 @@ async function runAiAction({
|
|
|
398
403
|
responseFlag: Ref<boolean[]>;
|
|
399
404
|
updateOnSuccess?: boolean;
|
|
400
405
|
}) {
|
|
401
|
-
let
|
|
402
|
-
let
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
let hasError = false;
|
|
407
|
+
let errorMessage = '';
|
|
408
|
+
const jobsIds: { jobId: any; recordId: any; }[] = [];
|
|
409
|
+
responseFlag.value = props.checkboxes.map(() => false);
|
|
410
|
+
|
|
411
|
+
//creating jobs
|
|
412
|
+
const tasks = props.checkboxes.map(async (checkbox, i) => {
|
|
413
|
+
try {
|
|
414
|
+
const res = await callAdminForthApi({
|
|
415
|
+
path: `/plugin/${props.meta.pluginInstanceId}/create-job`,
|
|
416
|
+
method: 'POST',
|
|
417
|
+
body: {
|
|
418
|
+
actionType: actionType,
|
|
419
|
+
recordId: checkbox,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
406
422
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
423
|
+
if (res?.error) {
|
|
424
|
+
throw new Error(res.error);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!res) {
|
|
428
|
+
throw new Error(`${actionType} request returned empty response.`);
|
|
429
|
+
}
|
|
414
430
|
|
|
415
|
-
|
|
416
|
-
|
|
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('_', ' ')}. Please, try to re-run the action.`;
|
|
436
|
+
return { success: false, index: i, error: e };
|
|
417
437
|
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
error = `Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`;
|
|
421
|
-
}
|
|
438
|
+
});
|
|
439
|
+
await Promise.all(tasks);
|
|
422
440
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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 { jobId, recordId } of jobsIds) {
|
|
449
|
+
//check job status
|
|
450
|
+
const jobResponse = 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 index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordId));
|
|
469
|
+
//if we are generating images - update carouselSaveImages with new image
|
|
470
|
+
if (actionType === 'generate_images') {
|
|
471
|
+
for (const [key, value] of Object.entries(carouselSaveImages.value[index])) {
|
|
472
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
473
|
+
carouselSaveImages.value[index][key] = [jobResponse.job.result[key]];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
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 pk = selected.value[index]?.[primaryKey];
|
|
483
|
+
if (pk) {
|
|
484
|
+
selected.value[index] = {
|
|
485
|
+
...selected.value[index],
|
|
486
|
+
...jobResponse.job.result,
|
|
487
|
+
isChecked: true,
|
|
488
|
+
[primaryKey]: pk,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
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
|
+
const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordId));
|
|
500
|
+
if (actionType !== 'analyze_no_images' || !props.meta.isFieldsForAnalizeFromImages) {
|
|
501
|
+
responseFlag.value[index] = true;
|
|
502
|
+
}
|
|
503
|
+
adminforth.alert({
|
|
504
|
+
message: `Generation action "${actionType.replace('_', ' ')}" failed for record: ${recordId}. Error: ${jobResponse.job?.error || 'Unknown error'}`,
|
|
505
|
+
variant: 'danger',
|
|
506
|
+
timeout: 'unlimited',
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!isAtLeastOneInProgress) {
|
|
511
|
+
isInProgress = false;
|
|
512
|
+
}
|
|
513
|
+
if (jobsIds.length > 0) {
|
|
514
|
+
if (actionType === 'generate_images') {
|
|
515
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.generateImages));
|
|
516
|
+
} else if (actionType === 'analyze') {
|
|
517
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillFieldsFromImages));
|
|
518
|
+
} else if (actionType === 'analyze_no_images') {
|
|
519
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillPlainFields));
|
|
520
|
+
} else {
|
|
521
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
428
524
|
}
|
|
429
525
|
|
|
430
|
-
if (
|
|
526
|
+
if (hasError) {
|
|
431
527
|
adminforth.alert({
|
|
432
|
-
message:
|
|
528
|
+
message: errorMessage,
|
|
433
529
|
variant: 'danger',
|
|
434
530
|
timeout: 'unlimited',
|
|
435
531
|
});
|
|
@@ -437,26 +533,19 @@ async function runAiAction({
|
|
|
437
533
|
if (actionType === 'generate_images') {
|
|
438
534
|
isImageGenerationError.value = true;
|
|
439
535
|
}
|
|
440
|
-
errorMessage.value =
|
|
536
|
+
this.errorMessage.value = errorMessage;
|
|
441
537
|
return;
|
|
442
538
|
}
|
|
443
539
|
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
...item,
|
|
451
|
-
isChecked: true,
|
|
452
|
-
[primaryKey]: pk,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
});
|
|
540
|
+
if (actionType === 'generate_images') {
|
|
541
|
+
isGeneratingImages.value = false;
|
|
542
|
+
} else if (actionType === 'analyze') {
|
|
543
|
+
isAnalizingImages.value = false;
|
|
544
|
+
} else if (actionType === 'analyze_no_images') {
|
|
545
|
+
isAnalizingFields.value = false;
|
|
456
546
|
}
|
|
457
547
|
}
|
|
458
548
|
|
|
459
|
-
|
|
460
549
|
async function uploadImage(imgBlob, id, fieldName) {
|
|
461
550
|
const file = new File([imgBlob], `generated_${fieldName}_${id}.${imgBlob.type.split('/').pop()}`, { type: imgBlob.type });
|
|
462
551
|
const { name, size, type } = file;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
<!-- CHECKBOX CELL TEMPLATE -->
|
|
12
12
|
<template #cell:checkboxes="{ item }">
|
|
13
|
-
<div class="flex items-center justify-center">
|
|
13
|
+
<div class="max-w-[100px] flex items-center justify-center">
|
|
14
14
|
<Checkbox
|
|
15
15
|
v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])].isChecked"
|
|
16
16
|
/>
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
:meta="props.meta"
|
|
100
100
|
:fieldName="n"
|
|
101
101
|
:carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
|
|
102
|
+
:regenerateImagesRefreshRate="regenerateImagesRefreshRate"
|
|
102
103
|
@error="handleError"
|
|
103
104
|
@close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
|
|
104
105
|
@selectImage="updateSelectedImage"
|
|
@@ -139,6 +140,7 @@ const props = defineProps<{
|
|
|
139
140
|
errorMessage: string
|
|
140
141
|
carouselSaveImages: any[]
|
|
141
142
|
carouselImageIndex: any[]
|
|
143
|
+
regenerateImagesRefreshRate: number
|
|
142
144
|
}>();
|
|
143
145
|
const emit = defineEmits(['error']);
|
|
144
146
|
|