@adminforth/bulk-ai-flow 1.22.2 → 1.23.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/VisionAction.vue +157 -34
- package/dist/custom/VisionAction.vue +157 -34
- package/package.json +1 -1
package/build.log
CHANGED
|
@@ -13,5 +13,5 @@ custom/package-lock.json
|
|
|
13
13
|
custom/package.json
|
|
14
14
|
custom/tsconfig.json
|
|
15
15
|
|
|
16
|
-
sent
|
|
17
|
-
total size is
|
|
16
|
+
sent 103,527 bytes received 172 bytes 207,398.00 bytes/sec
|
|
17
|
+
total size is 102,891 speedup is 0.99
|
package/custom/VisionAction.vue
CHANGED
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
},
|
|
26
26
|
onclick: async (dialog) => { await saveData(); dialog.hide(); }
|
|
27
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
|
+
},
|
|
28
36
|
{
|
|
29
37
|
label: t('Cancel'),
|
|
30
38
|
options: {
|
|
@@ -68,20 +76,18 @@
|
|
|
68
76
|
>
|
|
69
77
|
<div class="bulk-vision-table flex flex-col items-center gap-3 md:gap-4 overflow-y-auto">
|
|
70
78
|
<template v-if="recordsList.length && popupMode === 'generation'" >
|
|
71
|
-
|
|
72
|
-
|
|
73
79
|
<div class="w-full">
|
|
80
|
+
<p :class="isGenerationPaused ? '' : 'opacity-0'" class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
74
81
|
<div v-if="isGenerationPaused" class="flex flex-col gap-2 mb-2">
|
|
75
|
-
<p class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
76
82
|
<div class="flex items-center gap-2">
|
|
77
83
|
<button
|
|
78
|
-
class="px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 text-white"
|
|
84
|
+
class="h-8 px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 text-white"
|
|
79
85
|
@click="resumeGeneration"
|
|
80
86
|
>
|
|
81
87
|
{{ t('Resume generation') }}
|
|
82
88
|
</button>
|
|
83
89
|
<button
|
|
84
|
-
class="px-3 py-1.5 text-sm rounded-md bg-white hover:bg-gray-100 text-gray-900 border border-gray-200"
|
|
90
|
+
class="h-8 px-3 py-1.5 text-sm rounded-md bg-white hover:bg-gray-100 text-gray-900 border border-gray-200"
|
|
85
91
|
@click="cancelGeneration"
|
|
86
92
|
>
|
|
87
93
|
{{ t('Cancel generation') }}
|
|
@@ -89,7 +95,8 @@
|
|
|
89
95
|
</div>
|
|
90
96
|
</div>
|
|
91
97
|
<div
|
|
92
|
-
|
|
98
|
+
v-if="!isGenerationPaused"
|
|
99
|
+
class="w-full h-8 rounded-md bg-gray-200 dark:bg-gray-700 overflow-hidden relative"
|
|
93
100
|
:class="isGenerationPaused ? 'opacity-80' : ''"
|
|
94
101
|
role="progressbar"
|
|
95
102
|
:aria-valuenow="displayedProcessedCount"
|
|
@@ -138,6 +145,9 @@
|
|
|
138
145
|
<p v-if="isError === true">{{ errorMessage }}</p>
|
|
139
146
|
</div>
|
|
140
147
|
</template>
|
|
148
|
+
<template v-else-if="!recordsList.length && popupMode === 'generation'">
|
|
149
|
+
<p>No data to save. Feel free to click "Cancel"</p>
|
|
150
|
+
</template>
|
|
141
151
|
<div
|
|
142
152
|
v-else-if="popupMode === 'settings'"
|
|
143
153
|
v-for="(promptsCategory, key) in generationPrompts"
|
|
@@ -316,7 +326,7 @@ const recordsList = computed(() => {
|
|
|
316
326
|
: recordIds.value;
|
|
317
327
|
return ids.map(id => getOrCreateRecord(id));
|
|
318
328
|
});
|
|
319
|
-
|
|
329
|
+
const isSavingCurrent = ref(false);
|
|
320
330
|
function checkIfDialogOpen() {
|
|
321
331
|
return isDialogOpen.value === true;
|
|
322
332
|
}
|
|
@@ -382,6 +392,8 @@ async function runAiActions() {
|
|
|
382
392
|
isActiveGeneration.value = true;
|
|
383
393
|
completedRecordIds.value = new Set();
|
|
384
394
|
startedRecordCount.value = 0;
|
|
395
|
+
await nextTick();
|
|
396
|
+
tableRef.value?.refresh();
|
|
385
397
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
386
398
|
const tasks = recordIds.value
|
|
387
399
|
.map(id => limit(() => processOneRecord(String(id))));
|
|
@@ -397,6 +409,9 @@ const closeDialog = () => {
|
|
|
397
409
|
isDialogOpen.value = false;
|
|
398
410
|
popupMode.value = 'confirmation';
|
|
399
411
|
isDataSaved.value = false;
|
|
412
|
+
if (isGenerationPaused.value || isGenerationCancelled.value) {
|
|
413
|
+
resolvePause();
|
|
414
|
+
}
|
|
400
415
|
}
|
|
401
416
|
|
|
402
417
|
async function initializeGlobalState() {
|
|
@@ -949,6 +964,31 @@ async function prepareDataForSave() {
|
|
|
949
964
|
return [checkedItems, checkedRecords] as const;
|
|
950
965
|
}
|
|
951
966
|
|
|
967
|
+
async function prepareDataForRecords(records: RecordState[]) {
|
|
968
|
+
const checkedItems = records.map(record => ({
|
|
969
|
+
...record.data,
|
|
970
|
+
[primaryKey]: record.id,
|
|
971
|
+
}));
|
|
972
|
+
|
|
973
|
+
const promises: Promise<void>[] = [];
|
|
974
|
+
records.forEach((record, index) => {
|
|
975
|
+
if (record.imageGenerationFailed) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
for (const [key, value] of Object.entries(checkedItems[index])) {
|
|
979
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
980
|
+
const p = convertImages(key, value).then(result => {
|
|
981
|
+
checkedItems[index][key] = result;
|
|
982
|
+
});
|
|
983
|
+
promises.push(p);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
await Promise.all(promises);
|
|
989
|
+
return [checkedItems, records] as const;
|
|
990
|
+
}
|
|
991
|
+
|
|
952
992
|
async function convertImages(fieldName, img) {
|
|
953
993
|
let imgBlob;
|
|
954
994
|
if (typeof img === 'string' && img.startsWith('data:')) {
|
|
@@ -969,6 +1009,37 @@ async function convertImages(fieldName, img) {
|
|
|
969
1009
|
return imgBlob;
|
|
970
1010
|
}
|
|
971
1011
|
|
|
1012
|
+
async function uploadImagesForRecords(reqData: Record<string, any>[], checkedRecords: RecordState[]) {
|
|
1013
|
+
if (checkedRecords.some(record => record.imageGenerationFailed)) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const imagesToUpload = [];
|
|
1017
|
+
for (let index = 0; index < reqData.length; index++) {
|
|
1018
|
+
const item = reqData[index];
|
|
1019
|
+
const record = checkedRecords[index];
|
|
1020
|
+
for (const [key, value] of Object.entries(item)) {
|
|
1021
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
1022
|
+
if (!value) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (!overwriteExistingValues.value) {
|
|
1026
|
+
const imageURL = record.data[key];
|
|
1027
|
+
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
1028
|
+
if (originalImageUrl === imageURL) {
|
|
1029
|
+
reqData[index][key] = undefined;
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const p = uploadImage(value, record.id, key).then(result => {
|
|
1034
|
+
reqData[index][key] = result;
|
|
1035
|
+
});
|
|
1036
|
+
imagesToUpload.push(p);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
await Promise.all(imagesToUpload);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
972
1043
|
|
|
973
1044
|
async function saveData() {
|
|
974
1045
|
const errorText = 'Failed to save some records. Not all data may be saved'
|
|
@@ -979,33 +1050,7 @@ async function saveData() {
|
|
|
979
1050
|
adminforth.alert({ message: t('No items selected'), variant: 'warning' });
|
|
980
1051
|
return;
|
|
981
1052
|
}
|
|
982
|
-
|
|
983
|
-
const imagesToUpload = [];
|
|
984
|
-
for (let index = 0; index < reqData.length; index++) {
|
|
985
|
-
const item = reqData[index];
|
|
986
|
-
const record = checkedRecords[index];
|
|
987
|
-
for (const [key, value] of Object.entries(item)) {
|
|
988
|
-
if (props.meta.outputImageFields?.includes(key)) {
|
|
989
|
-
if (!value) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
if (!overwriteExistingValues.value) {
|
|
993
|
-
const imageURL = record.data[key];
|
|
994
|
-
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
995
|
-
if (originalImageUrl === imageURL) {
|
|
996
|
-
reqData[index][key] = undefined;
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const p = uploadImage(value, record.id, key).then(result => {
|
|
1001
|
-
reqData[index][key] = result;
|
|
1002
|
-
});
|
|
1003
|
-
imagesToUpload.push(p);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
await Promise.all(imagesToUpload);
|
|
1008
|
-
}
|
|
1053
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1009
1054
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1010
1055
|
const saveTasks = checkedRecords.map((record, index) =>
|
|
1011
1056
|
limit(async () => {
|
|
@@ -1396,4 +1441,82 @@ const beforeUnloadHandler = (event) => {
|
|
|
1396
1441
|
event.returnValue = '';
|
|
1397
1442
|
};
|
|
1398
1443
|
|
|
1444
|
+
async function saveCurrentGenerated() {
|
|
1445
|
+
const errorText = 'Failed to save some records. Not all data may be saved';
|
|
1446
|
+
const generatedIds = new Set(completedRecordIds.value);
|
|
1447
|
+
if (generatedIds.size < 1) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const recordsToSave = Array.from(generatedIds)
|
|
1451
|
+
.map(id => recordsById.get(String(id)))
|
|
1452
|
+
.filter((record): record is RecordState => Boolean(record))
|
|
1453
|
+
.filter(record => record.isChecked === true);
|
|
1454
|
+
if (recordsToSave.length < 1) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
isSavingCurrent.value = true;
|
|
1459
|
+
const [reqData, checkedRecords] = await prepareDataForRecords(recordsToSave);
|
|
1460
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1461
|
+
|
|
1462
|
+
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1463
|
+
const saveTasks = checkedRecords.map((record, index) =>
|
|
1464
|
+
limit(async () => {
|
|
1465
|
+
const res = await callAdminForthApi({
|
|
1466
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update_fields`,
|
|
1467
|
+
method: 'POST',
|
|
1468
|
+
body: {
|
|
1469
|
+
selectedIds: [record.id],
|
|
1470
|
+
fields: [reqData[index]],
|
|
1471
|
+
saveImages: !record.imageGenerationFailed,
|
|
1472
|
+
},
|
|
1473
|
+
});
|
|
1474
|
+
return { recordId: String(record.id), res };
|
|
1475
|
+
})
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
const saveResults = await Promise.all(saveTasks);
|
|
1479
|
+
const failedResults = saveResults.filter(item => item.res?.ok === false || item.res?.error);
|
|
1480
|
+
const savedIds = saveResults
|
|
1481
|
+
.filter(item => item.res?.ok !== false && !item.res?.error)
|
|
1482
|
+
.map(item => item.recordId);
|
|
1483
|
+
|
|
1484
|
+
if (failedResults.length > 0) {
|
|
1485
|
+
failedResults.forEach(item => {
|
|
1486
|
+
adminforth.alert({
|
|
1487
|
+
message: item.res?.error || t(errorText),
|
|
1488
|
+
variant: 'danger',
|
|
1489
|
+
timeout: 'unlimited',
|
|
1490
|
+
});
|
|
1491
|
+
});
|
|
1492
|
+
isError.value = true;
|
|
1493
|
+
errorMessage.value = t(errorText);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (savedIds.length > 0) {
|
|
1497
|
+
recordIds.value = recordIds.value.filter(id => !savedIds.includes(String(id)));
|
|
1498
|
+
for (let index = 0; index < savedIds.length; index++) {
|
|
1499
|
+
const id = savedIds[index];
|
|
1500
|
+
recordsById.delete(id);
|
|
1501
|
+
uncheckedRecordIds.delete(id);
|
|
1502
|
+
completedRecordIds.value.delete(id);
|
|
1503
|
+
}
|
|
1504
|
+
adminforth.alert({
|
|
1505
|
+
message: t('Successfully saved generated data for {count} records', { count: savedIds.length }),
|
|
1506
|
+
variant: 'success',
|
|
1507
|
+
});
|
|
1508
|
+
touchRecords();
|
|
1509
|
+
}
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
console.error('Error saving data:', error);
|
|
1512
|
+
isError.value = true;
|
|
1513
|
+
errorMessage.value = t(errorText);
|
|
1514
|
+
} finally {
|
|
1515
|
+
isSavingCurrent.value = false;
|
|
1516
|
+
}
|
|
1517
|
+
await nextTick();
|
|
1518
|
+
tableRef.value.refresh();
|
|
1519
|
+
props.updateList();
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1399
1522
|
</script>
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
},
|
|
26
26
|
onclick: async (dialog) => { await saveData(); dialog.hide(); }
|
|
27
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
|
+
},
|
|
28
36
|
{
|
|
29
37
|
label: t('Cancel'),
|
|
30
38
|
options: {
|
|
@@ -68,20 +76,18 @@
|
|
|
68
76
|
>
|
|
69
77
|
<div class="bulk-vision-table flex flex-col items-center gap-3 md:gap-4 overflow-y-auto">
|
|
70
78
|
<template v-if="recordsList.length && popupMode === 'generation'" >
|
|
71
|
-
|
|
72
|
-
|
|
73
79
|
<div class="w-full">
|
|
80
|
+
<p :class="isGenerationPaused ? '' : 'opacity-0'" class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
74
81
|
<div v-if="isGenerationPaused" class="flex flex-col gap-2 mb-2">
|
|
75
|
-
<p class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
76
82
|
<div class="flex items-center gap-2">
|
|
77
83
|
<button
|
|
78
|
-
class="px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 text-white"
|
|
84
|
+
class="h-8 px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 text-white"
|
|
79
85
|
@click="resumeGeneration"
|
|
80
86
|
>
|
|
81
87
|
{{ t('Resume generation') }}
|
|
82
88
|
</button>
|
|
83
89
|
<button
|
|
84
|
-
class="px-3 py-1.5 text-sm rounded-md bg-white hover:bg-gray-100 text-gray-900 border border-gray-200"
|
|
90
|
+
class="h-8 px-3 py-1.5 text-sm rounded-md bg-white hover:bg-gray-100 text-gray-900 border border-gray-200"
|
|
85
91
|
@click="cancelGeneration"
|
|
86
92
|
>
|
|
87
93
|
{{ t('Cancel generation') }}
|
|
@@ -89,7 +95,8 @@
|
|
|
89
95
|
</div>
|
|
90
96
|
</div>
|
|
91
97
|
<div
|
|
92
|
-
|
|
98
|
+
v-if="!isGenerationPaused"
|
|
99
|
+
class="w-full h-8 rounded-md bg-gray-200 dark:bg-gray-700 overflow-hidden relative"
|
|
93
100
|
:class="isGenerationPaused ? 'opacity-80' : ''"
|
|
94
101
|
role="progressbar"
|
|
95
102
|
:aria-valuenow="displayedProcessedCount"
|
|
@@ -138,6 +145,9 @@
|
|
|
138
145
|
<p v-if="isError === true">{{ errorMessage }}</p>
|
|
139
146
|
</div>
|
|
140
147
|
</template>
|
|
148
|
+
<template v-else-if="!recordsList.length && popupMode === 'generation'">
|
|
149
|
+
<p>No data to save. Feel free to click "Cancel"</p>
|
|
150
|
+
</template>
|
|
141
151
|
<div
|
|
142
152
|
v-else-if="popupMode === 'settings'"
|
|
143
153
|
v-for="(promptsCategory, key) in generationPrompts"
|
|
@@ -316,7 +326,7 @@ const recordsList = computed(() => {
|
|
|
316
326
|
: recordIds.value;
|
|
317
327
|
return ids.map(id => getOrCreateRecord(id));
|
|
318
328
|
});
|
|
319
|
-
|
|
329
|
+
const isSavingCurrent = ref(false);
|
|
320
330
|
function checkIfDialogOpen() {
|
|
321
331
|
return isDialogOpen.value === true;
|
|
322
332
|
}
|
|
@@ -382,6 +392,8 @@ async function runAiActions() {
|
|
|
382
392
|
isActiveGeneration.value = true;
|
|
383
393
|
completedRecordIds.value = new Set();
|
|
384
394
|
startedRecordCount.value = 0;
|
|
395
|
+
await nextTick();
|
|
396
|
+
tableRef.value?.refresh();
|
|
385
397
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
386
398
|
const tasks = recordIds.value
|
|
387
399
|
.map(id => limit(() => processOneRecord(String(id))));
|
|
@@ -397,6 +409,9 @@ const closeDialog = () => {
|
|
|
397
409
|
isDialogOpen.value = false;
|
|
398
410
|
popupMode.value = 'confirmation';
|
|
399
411
|
isDataSaved.value = false;
|
|
412
|
+
if (isGenerationPaused.value || isGenerationCancelled.value) {
|
|
413
|
+
resolvePause();
|
|
414
|
+
}
|
|
400
415
|
}
|
|
401
416
|
|
|
402
417
|
async function initializeGlobalState() {
|
|
@@ -949,6 +964,31 @@ async function prepareDataForSave() {
|
|
|
949
964
|
return [checkedItems, checkedRecords] as const;
|
|
950
965
|
}
|
|
951
966
|
|
|
967
|
+
async function prepareDataForRecords(records: RecordState[]) {
|
|
968
|
+
const checkedItems = records.map(record => ({
|
|
969
|
+
...record.data,
|
|
970
|
+
[primaryKey]: record.id,
|
|
971
|
+
}));
|
|
972
|
+
|
|
973
|
+
const promises: Promise<void>[] = [];
|
|
974
|
+
records.forEach((record, index) => {
|
|
975
|
+
if (record.imageGenerationFailed) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
for (const [key, value] of Object.entries(checkedItems[index])) {
|
|
979
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
980
|
+
const p = convertImages(key, value).then(result => {
|
|
981
|
+
checkedItems[index][key] = result;
|
|
982
|
+
});
|
|
983
|
+
promises.push(p);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
await Promise.all(promises);
|
|
989
|
+
return [checkedItems, records] as const;
|
|
990
|
+
}
|
|
991
|
+
|
|
952
992
|
async function convertImages(fieldName, img) {
|
|
953
993
|
let imgBlob;
|
|
954
994
|
if (typeof img === 'string' && img.startsWith('data:')) {
|
|
@@ -969,6 +1009,37 @@ async function convertImages(fieldName, img) {
|
|
|
969
1009
|
return imgBlob;
|
|
970
1010
|
}
|
|
971
1011
|
|
|
1012
|
+
async function uploadImagesForRecords(reqData: Record<string, any>[], checkedRecords: RecordState[]) {
|
|
1013
|
+
if (checkedRecords.some(record => record.imageGenerationFailed)) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const imagesToUpload = [];
|
|
1017
|
+
for (let index = 0; index < reqData.length; index++) {
|
|
1018
|
+
const item = reqData[index];
|
|
1019
|
+
const record = checkedRecords[index];
|
|
1020
|
+
for (const [key, value] of Object.entries(item)) {
|
|
1021
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
1022
|
+
if (!value) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (!overwriteExistingValues.value) {
|
|
1026
|
+
const imageURL = record.data[key];
|
|
1027
|
+
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
1028
|
+
if (originalImageUrl === imageURL) {
|
|
1029
|
+
reqData[index][key] = undefined;
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const p = uploadImage(value, record.id, key).then(result => {
|
|
1034
|
+
reqData[index][key] = result;
|
|
1035
|
+
});
|
|
1036
|
+
imagesToUpload.push(p);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
await Promise.all(imagesToUpload);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
972
1043
|
|
|
973
1044
|
async function saveData() {
|
|
974
1045
|
const errorText = 'Failed to save some records. Not all data may be saved'
|
|
@@ -979,33 +1050,7 @@ async function saveData() {
|
|
|
979
1050
|
adminforth.alert({ message: t('No items selected'), variant: 'warning' });
|
|
980
1051
|
return;
|
|
981
1052
|
}
|
|
982
|
-
|
|
983
|
-
const imagesToUpload = [];
|
|
984
|
-
for (let index = 0; index < reqData.length; index++) {
|
|
985
|
-
const item = reqData[index];
|
|
986
|
-
const record = checkedRecords[index];
|
|
987
|
-
for (const [key, value] of Object.entries(item)) {
|
|
988
|
-
if (props.meta.outputImageFields?.includes(key)) {
|
|
989
|
-
if (!value) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
if (!overwriteExistingValues.value) {
|
|
993
|
-
const imageURL = record.data[key];
|
|
994
|
-
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
995
|
-
if (originalImageUrl === imageURL) {
|
|
996
|
-
reqData[index][key] = undefined;
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const p = uploadImage(value, record.id, key).then(result => {
|
|
1001
|
-
reqData[index][key] = result;
|
|
1002
|
-
});
|
|
1003
|
-
imagesToUpload.push(p);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
await Promise.all(imagesToUpload);
|
|
1008
|
-
}
|
|
1053
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1009
1054
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1010
1055
|
const saveTasks = checkedRecords.map((record, index) =>
|
|
1011
1056
|
limit(async () => {
|
|
@@ -1396,4 +1441,82 @@ const beforeUnloadHandler = (event) => {
|
|
|
1396
1441
|
event.returnValue = '';
|
|
1397
1442
|
};
|
|
1398
1443
|
|
|
1444
|
+
async function saveCurrentGenerated() {
|
|
1445
|
+
const errorText = 'Failed to save some records. Not all data may be saved';
|
|
1446
|
+
const generatedIds = new Set(completedRecordIds.value);
|
|
1447
|
+
if (generatedIds.size < 1) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const recordsToSave = Array.from(generatedIds)
|
|
1451
|
+
.map(id => recordsById.get(String(id)))
|
|
1452
|
+
.filter((record): record is RecordState => Boolean(record))
|
|
1453
|
+
.filter(record => record.isChecked === true);
|
|
1454
|
+
if (recordsToSave.length < 1) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
isSavingCurrent.value = true;
|
|
1459
|
+
const [reqData, checkedRecords] = await prepareDataForRecords(recordsToSave);
|
|
1460
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1461
|
+
|
|
1462
|
+
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1463
|
+
const saveTasks = checkedRecords.map((record, index) =>
|
|
1464
|
+
limit(async () => {
|
|
1465
|
+
const res = await callAdminForthApi({
|
|
1466
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update_fields`,
|
|
1467
|
+
method: 'POST',
|
|
1468
|
+
body: {
|
|
1469
|
+
selectedIds: [record.id],
|
|
1470
|
+
fields: [reqData[index]],
|
|
1471
|
+
saveImages: !record.imageGenerationFailed,
|
|
1472
|
+
},
|
|
1473
|
+
});
|
|
1474
|
+
return { recordId: String(record.id), res };
|
|
1475
|
+
})
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
const saveResults = await Promise.all(saveTasks);
|
|
1479
|
+
const failedResults = saveResults.filter(item => item.res?.ok === false || item.res?.error);
|
|
1480
|
+
const savedIds = saveResults
|
|
1481
|
+
.filter(item => item.res?.ok !== false && !item.res?.error)
|
|
1482
|
+
.map(item => item.recordId);
|
|
1483
|
+
|
|
1484
|
+
if (failedResults.length > 0) {
|
|
1485
|
+
failedResults.forEach(item => {
|
|
1486
|
+
adminforth.alert({
|
|
1487
|
+
message: item.res?.error || t(errorText),
|
|
1488
|
+
variant: 'danger',
|
|
1489
|
+
timeout: 'unlimited',
|
|
1490
|
+
});
|
|
1491
|
+
});
|
|
1492
|
+
isError.value = true;
|
|
1493
|
+
errorMessage.value = t(errorText);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (savedIds.length > 0) {
|
|
1497
|
+
recordIds.value = recordIds.value.filter(id => !savedIds.includes(String(id)));
|
|
1498
|
+
for (let index = 0; index < savedIds.length; index++) {
|
|
1499
|
+
const id = savedIds[index];
|
|
1500
|
+
recordsById.delete(id);
|
|
1501
|
+
uncheckedRecordIds.delete(id);
|
|
1502
|
+
completedRecordIds.value.delete(id);
|
|
1503
|
+
}
|
|
1504
|
+
adminforth.alert({
|
|
1505
|
+
message: t('Successfully saved generated data for {count} records', { count: savedIds.length }),
|
|
1506
|
+
variant: 'success',
|
|
1507
|
+
});
|
|
1508
|
+
touchRecords();
|
|
1509
|
+
}
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
console.error('Error saving data:', error);
|
|
1512
|
+
isError.value = true;
|
|
1513
|
+
errorMessage.value = t(errorText);
|
|
1514
|
+
} finally {
|
|
1515
|
+
isSavingCurrent.value = false;
|
|
1516
|
+
}
|
|
1517
|
+
await nextTick();
|
|
1518
|
+
tableRef.value.refresh();
|
|
1519
|
+
props.updateList();
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1399
1522
|
</script>
|