@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 CHANGED
@@ -13,5 +13,5 @@ custom/package-lock.json
13
13
  custom/package.json
14
14
  custom/tsconfig.json
15
15
 
16
- sent 99,621 bytes received 172 bytes 199,586.00 bytes/sec
17
- total size is 98,985 speedup is 0.99
16
+ sent 103,773 bytes received 172 bytes 207,890.00 bytes/sec
17
+ total size is 103,137 speedup is 0.99
@@ -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
- class="w-full h-[30px] rounded-2xl bg-gray-200 dark:bg-gray-700 overflow-hidden relative"
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
- if (!checkedRecords.some(record => record.imageGenerationFailed)) {
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
- class="w-full h-[30px] rounded-2xl bg-gray-200 dark:bg-gray-700 overflow-hidden relative"
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
- if (!checkedRecords.some(record => record.imageGenerationFailed)) {
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.22.3",
3
+ "version": "1.23.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },