@adminforth/bulk-ai-flow 1.22.3 → 1.23.1
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 +154 -36
- package/dist/custom/VisionAction.vue +154 -36
- 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,773 bytes received 172 bytes 207,890.00 bytes/sec
|
|
17
|
+
total size is 103,137 speedup is 0.99
|
package/custom/VisionAction.vue
CHANGED
|
@@ -25,10 +25,18 @@
|
|
|
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: {
|
|
31
|
-
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'
|
|
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'
|
|
32
40
|
},
|
|
33
41
|
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
34
42
|
},
|
|
@@ -52,7 +60,7 @@
|
|
|
52
60
|
{
|
|
53
61
|
label: t('Cancel'),
|
|
54
62
|
options: {
|
|
55
|
-
class: 'w-2/5 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'
|
|
63
|
+
class: 'w-2/5 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'
|
|
56
64
|
},
|
|
57
65
|
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
58
66
|
},
|
|
@@ -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 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 border-none ml-2"
|
|
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 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700 dark:border-gray-600"
|
|
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
|
}
|
|
@@ -954,6 +964,31 @@ async function prepareDataForSave() {
|
|
|
954
964
|
return [checkedItems, checkedRecords] as const;
|
|
955
965
|
}
|
|
956
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
|
+
|
|
957
992
|
async function convertImages(fieldName, img) {
|
|
958
993
|
let imgBlob;
|
|
959
994
|
if (typeof img === 'string' && img.startsWith('data:')) {
|
|
@@ -974,6 +1009,37 @@ async function convertImages(fieldName, img) {
|
|
|
974
1009
|
return imgBlob;
|
|
975
1010
|
}
|
|
976
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
|
+
|
|
977
1043
|
|
|
978
1044
|
async function saveData() {
|
|
979
1045
|
const errorText = 'Failed to save some records. Not all data may be saved'
|
|
@@ -984,33 +1050,7 @@ async function saveData() {
|
|
|
984
1050
|
adminforth.alert({ message: t('No items selected'), variant: 'warning' });
|
|
985
1051
|
return;
|
|
986
1052
|
}
|
|
987
|
-
|
|
988
|
-
const imagesToUpload = [];
|
|
989
|
-
for (let index = 0; index < reqData.length; index++) {
|
|
990
|
-
const item = reqData[index];
|
|
991
|
-
const record = checkedRecords[index];
|
|
992
|
-
for (const [key, value] of Object.entries(item)) {
|
|
993
|
-
if (props.meta.outputImageFields?.includes(key)) {
|
|
994
|
-
if (!value) {
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
if (!overwriteExistingValues.value) {
|
|
998
|
-
const imageURL = record.data[key];
|
|
999
|
-
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
1000
|
-
if (originalImageUrl === imageURL) {
|
|
1001
|
-
reqData[index][key] = undefined;
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
const p = uploadImage(value, record.id, key).then(result => {
|
|
1006
|
-
reqData[index][key] = result;
|
|
1007
|
-
});
|
|
1008
|
-
imagesToUpload.push(p);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
await Promise.all(imagesToUpload);
|
|
1013
|
-
}
|
|
1053
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1014
1054
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1015
1055
|
const saveTasks = checkedRecords.map((record, index) =>
|
|
1016
1056
|
limit(async () => {
|
|
@@ -1401,4 +1441,82 @@ const beforeUnloadHandler = (event) => {
|
|
|
1401
1441
|
event.returnValue = '';
|
|
1402
1442
|
};
|
|
1403
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
|
+
|
|
1404
1522
|
</script>
|
|
@@ -25,10 +25,18 @@
|
|
|
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: {
|
|
31
|
-
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'
|
|
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'
|
|
32
40
|
},
|
|
33
41
|
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
34
42
|
},
|
|
@@ -52,7 +60,7 @@
|
|
|
52
60
|
{
|
|
53
61
|
label: t('Cancel'),
|
|
54
62
|
options: {
|
|
55
|
-
class: 'w-2/5 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'
|
|
63
|
+
class: 'w-2/5 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'
|
|
56
64
|
},
|
|
57
65
|
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
58
66
|
},
|
|
@@ -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 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 border-none ml-2"
|
|
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 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700 dark:border-gray-600"
|
|
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
|
}
|
|
@@ -954,6 +964,31 @@ async function prepareDataForSave() {
|
|
|
954
964
|
return [checkedItems, checkedRecords] as const;
|
|
955
965
|
}
|
|
956
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
|
+
|
|
957
992
|
async function convertImages(fieldName, img) {
|
|
958
993
|
let imgBlob;
|
|
959
994
|
if (typeof img === 'string' && img.startsWith('data:')) {
|
|
@@ -974,6 +1009,37 @@ async function convertImages(fieldName, img) {
|
|
|
974
1009
|
return imgBlob;
|
|
975
1010
|
}
|
|
976
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
|
+
|
|
977
1043
|
|
|
978
1044
|
async function saveData() {
|
|
979
1045
|
const errorText = 'Failed to save some records. Not all data may be saved'
|
|
@@ -984,33 +1050,7 @@ async function saveData() {
|
|
|
984
1050
|
adminforth.alert({ message: t('No items selected'), variant: 'warning' });
|
|
985
1051
|
return;
|
|
986
1052
|
}
|
|
987
|
-
|
|
988
|
-
const imagesToUpload = [];
|
|
989
|
-
for (let index = 0; index < reqData.length; index++) {
|
|
990
|
-
const item = reqData[index];
|
|
991
|
-
const record = checkedRecords[index];
|
|
992
|
-
for (const [key, value] of Object.entries(item)) {
|
|
993
|
-
if (props.meta.outputImageFields?.includes(key)) {
|
|
994
|
-
if (!value) {
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
if (!overwriteExistingValues.value) {
|
|
998
|
-
const imageURL = record.data[key];
|
|
999
|
-
const originalImageUrl = record.listOfImageNotGenerated?.[key]?.originalImage || '';
|
|
1000
|
-
if (originalImageUrl === imageURL) {
|
|
1001
|
-
reqData[index][key] = undefined;
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
const p = uploadImage(value, record.id, key).then(result => {
|
|
1006
|
-
reqData[index][key] = result;
|
|
1007
|
-
});
|
|
1008
|
-
imagesToUpload.push(p);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
await Promise.all(imagesToUpload);
|
|
1013
|
-
}
|
|
1053
|
+
await uploadImagesForRecords(reqData, checkedRecords);
|
|
1014
1054
|
const limit = pLimit(props.meta.concurrencyLimit || 10);
|
|
1015
1055
|
const saveTasks = checkedRecords.map((record, index) =>
|
|
1016
1056
|
limit(async () => {
|
|
@@ -1401,4 +1441,82 @@ const beforeUnloadHandler = (event) => {
|
|
|
1401
1441
|
event.returnValue = '';
|
|
1402
1442
|
};
|
|
1403
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
|
+
|
|
1404
1522
|
</script>
|