@adminforth/bulk-ai-flow 1.21.9 → 1.22.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.
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <Table
3
+ ref="tableRef"
3
4
  :columns="tableHeaders"
4
- :data="tableColumns"
5
- :pageSize="6"
5
+ :data="tableDataProvider"
6
+ :pageSize="pageSize"
6
7
  makeHeaderSticky
7
8
  makePaginationSticky
8
9
  >
@@ -14,7 +15,7 @@
14
15
  <template #cell:checkboxes="{ item }">
15
16
  <div class="max-w-[100px] flex items-center justify-center">
16
17
  <Checkbox
17
- v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])].isChecked"
18
+ v-model="item.isChecked"
18
19
  />
19
20
  </div>
20
21
  </template>
@@ -55,8 +56,8 @@
55
56
  </div>
56
57
  </template>
57
58
  <!-- CUSTOM FIELD TEMPLATES -->
58
- <template v-for="n in customFieldNames" :key="n" #[`cell:${n}`]="{ item, column }">
59
- <div v-if="(isAnalyzing(item, n) && !(props.regeneratingFieldsStatus[item[props.primaryKey]] && props.regeneratingFieldsStatus[item[props.primaryKey]][n])) && !isInColumnImage(n)" @mouseenter="(() => { hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = true})" @mouseleave="(() => { hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false})">
59
+ <template v-for="n in customFieldNames" :key="n" #[`cell:${n}`]="{ item }">
60
+ <div v-if="(isAnalyzing(item, n) && !item.regeneratingFieldsStatus?.[n]) && !isInColumnImage(n)" @mouseenter="(() => { setHover(item.id, n, true) })" @mouseleave="(() => { setHover(item.id, n, false) })">
60
61
  <div class="flex gap-1 justify-end">
61
62
  <Tooltip v-if="checkForError(item, n)">
62
63
  <IconExclamationCircleSolid class="my-2 w-5 h-5 text-red-500" />
@@ -75,143 +76,143 @@
75
76
  <Select
76
77
  class="min-w-[150px]"
77
78
  :options="convertColumnEnumToSelectOptions(props.meta.columnEnums, n)"
78
- v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
79
+ v-model="item.data[n]"
79
80
  :teleportToTop="true"
80
81
  :teleportToBody="false"
81
82
  >
82
83
  </Select>
83
84
  <Tooltip>
84
- <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }">
85
+ <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !isHovered(item.id, n) }">
85
86
  <p class="text-sm ">{{ $t('old value') }}</p>
86
87
  </div>
87
88
  <template #tooltip>
88
- {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] ?? "No old value" }}
89
+ {{ item.oldData?.[n] ?? "No old value" }}
89
90
  </template>
90
91
  </Tooltip>
91
92
  </div>
92
- <div v-else-if="typeof selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] === 'string' || typeof selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] === 'object'" class="flex flex-col items-start justify-end min-h-[90px]">
93
+ <div v-else-if="typeof item.data?.[n] === 'string' || typeof item.data?.[n] === 'object'" class="flex flex-col items-start justify-end min-h-[90px]">
93
94
  <Textarea
94
95
  class="min-w-[150px] h-full"
95
96
  type="text"
96
- v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
97
+ v-model="item.data[n]"
97
98
  >
98
99
  </Textarea>
99
100
  <Tooltip>
100
- <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }">
101
+ <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !isHovered(item.id, n) }">
101
102
  <p class="text-sm ">{{ $t('old value') }}</p>
102
103
  </div>
103
104
  <template #tooltip>
104
- <p class="max-w-[200px]">{{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] ?? "No old value" }}</p>
105
+ <p class="max-w-[200px]">{{ item.oldData?.[n] ?? "No old value" }}</p>
105
106
  </template>
106
107
  </Tooltip>
107
108
  </div>
108
- <div v-else-if="typeof selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] === 'boolean'" class="flex flex-col items-center justify-end min-h-[90px]">
109
+ <div v-else-if="typeof item.data?.[n] === 'boolean'" class="flex flex-col items-center justify-end min-h-[90px]">
109
110
  <Toggle
110
111
  class="p-2"
111
- v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
112
+ v-model="item.data[n]"
112
113
  >
113
114
  </Toggle>
114
115
  <Tooltip>
115
- <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }">
116
+ <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !isHovered(item.id, n) }">
116
117
  <p class="text-sm ">{{ $t('old value') }}</p>
117
118
  </div>
118
119
  <template #tooltip>
119
- {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] ?? "No old value" }}
120
+ {{ item.oldData?.[n] ?? "No old value" }}
120
121
  </template>
121
122
  </Tooltip>
122
123
  </div>
123
124
  <div v-else class="flex flex-col items-start justify-end min-h-[90px]">
124
125
  <Input
125
126
  type="number"
126
- v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
127
+ v-model="item.data[n]"
127
128
  class="w-full min-w-[80px]"
128
129
  :fullWidth="true"
129
130
  />
130
131
  <Tooltip>
131
- <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }">
132
+ <div class="mt-2 flex items-center justify-start gap-1 hover:text-blue-500" :class="{ 'opacity-0': !isHovered(item.id, n) }">
132
133
  <p class="text-sm ">{{ $t('old value') }}</p>
133
134
  </div>
134
135
  <template #tooltip>
135
- {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] ?? "No old value" }}
136
+ {{ item.oldData?.[n] ?? "No old value" }}
136
137
  </template>
137
138
  </Tooltip>
138
139
  </div>
139
140
  </div>
140
141
 
141
- <div v-if="isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])]" @mouseenter="(() => { hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = true})" @mouseleave="(() => { hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false})">
142
+ <div v-if="item.aiStatus.generatedImages" @mouseenter="(() => { setHover(item.id, n, true) })" @mouseleave="(() => { setHover(item.id, n, false) })">
142
143
  <div v-if="isInColumnImage(n)">
143
144
  <div class="mt-2 mb-2 flex items-center justify-start gap-2">
144
- <div v-if="isValidUrl(selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n])" class="flex flex-col items-center relative">
145
+ <div v-if="isValidUrl(item.data?.[n])" class="flex flex-col items-center relative">
145
146
  <img
146
- :src="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
147
+ :src="item.data?.[n]"
147
148
  class="w-20 h-20 object-cover rounded cursor-pointer border hover:border-blue-500 transition"
148
- @click="() => {openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = true}"
149
+ @click="() => {item.openGenerationCarousel[n] = true}"
149
150
  />
150
151
  <p
151
152
  v-if="isImageHasPreviewUrl[n]"
152
153
  class="absolute mt-20 text-sm hover:text-blue-500 hover:underline hover:cursor-pointer flex items-center gap-1"
153
- :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }"
154
- @click="() => {openImageCompare[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = true}"
154
+ :class="{ 'opacity-0': !isHovered(item.id, n) }"
155
+ @click="() => {item.openImageCompare[n] = true}"
155
156
  >
156
157
  {{ $t('old image') }}
157
158
  </p>
158
159
  </div>
159
160
  <div v-else class="flex items-center justify-center text-center w-20 h-20">
160
- <Tooltip v-if="imageGenerationErrorMessage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] === 'No source images found'">
161
+ <Tooltip v-if="item.imageGenerationErrorMessage === 'No source images found'">
161
162
  <p
162
163
  >
163
164
  {{ $t("Can't generate image.") }}
164
165
  </p>
165
166
  <template #tooltip>
166
- {{ imageGenerationErrorMessage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] }}
167
+ {{ item.imageGenerationErrorMessage }}
167
168
  </template>
168
169
  </Tooltip>
169
170
  <Tooltip v-else>
170
171
  <div>
171
172
  <IconRefreshOutline
172
- @click="() => {regenerateImages(item[primaryKey])}"
173
+ @click="() => {regenerateImages(item.id)}"
173
174
  class="w-20 h-20 hover:text-blue-500 cursor-pointer transition hover:scale-105"
174
175
  />
175
176
  </div>
176
177
  <template #tooltip>
177
- {{ imageGenerationErrorMessage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] + '. Click to retry.' }}
178
+ {{ item.imageGenerationErrorMessage + '. Click to retry.' }}
178
179
  </template>
179
180
  </Tooltip>
180
181
  </div>
181
182
  </div>
182
183
  <div>
183
184
  <GenerationCarousel
184
- v-if="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
185
- :images="carouselSaveImages[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
186
- :recordId="item[primaryKey]"
185
+ v-if="item.openGenerationCarousel[n]"
186
+ :images="item.carouselSaveImages[n]"
187
+ :recordId="item.id"
187
188
  :meta="props.meta"
188
189
  :fieldName="n"
189
- :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
190
+ :carouselImageIndex="item.carouselImageIndex[n]"
190
191
  :regenerateImagesRefreshRate="regenerateImagesRefreshRate"
191
192
  :sourceImage="item.images && item.images.length ? item.images : null"
192
193
  :imageGenerationPrompts="imageGenerationPrompts[n]"
193
194
  @error="handleError"
194
- @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
195
+ @close="item.openGenerationCarousel[n] = false"
195
196
  @selectImage="updateSelectedImage"
196
197
  @updateCarouselIndex="updateActiveIndex"
197
198
  />
198
199
  <ImageCompare
199
- v-if="openImageCompare[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
200
+ v-if="item.openImageCompare[n]"
200
201
  :meta="props.meta"
201
202
  :columnName="n"
202
- :oldImage="oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
203
- :newImage="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
204
- @close="openImageCompare[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
203
+ :oldImage="item.oldData?.[n]"
204
+ :newImage="item.data?.[n]"
205
+ @close="item.openImageCompare[n] = false"
205
206
  />
206
207
  </div>
207
208
  </div>
208
209
  </div>
209
210
 
210
- <div v-if="!isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && isInColumnImage(n)">
211
+ <div v-if="!item.aiStatus.generatedImages && isInColumnImage(n)">
211
212
  <Skeleton type="image" class="w-20 h-20" />
212
213
  </div>
213
214
 
214
- <div v-if="(!isAnalyzing(item, n) || (props.regeneratingFieldsStatus[item[props.primaryKey]] && props.regeneratingFieldsStatus[item[props.primaryKey]][n])) && !isInColumnImage(n)">
215
+ <div v-if="(!isAnalyzing(item, n) || item.regeneratingFieldsStatus?.[n]) && !isInColumnImage(n)">
215
216
  <Skeleton class="w-full h-6" />
216
217
  </div>
217
218
  </template>
@@ -219,7 +220,7 @@
219
220
  </template>
220
221
 
221
222
  <script lang="ts" setup>
222
- import { ref, watch, onMounted } from 'vue'
223
+ import { ref, watch, computed, reactive } from 'vue'
223
224
  import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle, Tooltip } from '@/afcl'
224
225
  import GenerationCarousel from './ImageGenerationCarousel.vue'
225
226
  import ImageCompare from './ImageCompare.vue';
@@ -228,48 +229,47 @@ import { IconRefreshOutline, IconExclamationCircleSolid } from '@iconify-prerend
228
229
  const props = defineProps<{
229
230
  meta: any,
230
231
  tableHeaders: any,
231
- tableColumns: any,
232
+ records: any[],
232
233
  customFieldNames: any,
233
- tableColumnsIndexes: any,
234
- selected: any,
235
- isAiResponseReceivedAnalizeImage: boolean[],
236
- isAiResponseReceivedAnalizeNoImage: boolean[],
237
- isAiResponseReceivedImage: boolean[],
238
- primaryKey: any,
239
- openGenerationCarousel: any,
240
- openImageCompare: any,
241
234
  isError: boolean,
242
235
  errorMessage: string
243
- carouselSaveImages: any[]
244
- carouselImageIndex: any[]
245
236
  regenerateImagesRefreshRate: number
246
- isAiGenerationError: boolean[],
247
- aiGenerationErrorMessage: string[],
248
- isAiImageGenerationError: boolean[],
249
- imageGenerationErrorMessage: string[],
250
- oldData: any[],
251
237
  isImageHasPreviewUrl: Record<string, boolean>
252
238
  imageGenerationPrompts: Record<string, any>
253
- isImageToTextGenerationError: boolean[],
254
- imageToTextErrorMessages: Record<string, string>[],
255
- isTextToTextGenerationError: boolean[],
256
- textToTextErrorMessages: Record<string, string>[],
257
239
  outputImageFields: string[],
258
240
  outputFieldsForAnalizeFromImages: string[],
259
241
  outputPlainFields: string[],
260
- regeneratingFieldsStatus: Record<string, Record<string, boolean>>
261
242
  }>();
262
243
  const emit = defineEmits(['error', 'regenerateImages', 'regenerateCell']);
263
244
 
264
-
245
+ const pageSize = 6;
246
+ const pagination = reactive({ offset: 0, limit: pageSize });
265
247
  const zoomedImage = ref(null);
266
- const hovers = ref<{ [key: string]: boolean }[]>([]);
248
+ const hovers = ref<Record<string, Record<string, boolean>>>({});
249
+ const tableRef = ref(null);
267
250
 
268
- watch(() => props.tableColumnsIndexes, (newVal) => {
269
- if (newVal) {
270
- hovers.value = newVal.map(() => ({}));
251
+ defineExpose({
252
+ refresh() {
253
+ if (tableRef.value) {
254
+ tableRef.value.refreshTable();
255
+ }
271
256
  }
272
- }, { immediate: true });
257
+ });
258
+
259
+ const paginatedRecords = computed(() => props.records.slice(pagination.offset, pagination.offset + pagination.limit));
260
+
261
+ const tableDataProvider = async ({ offset, limit }) => {
262
+ pagination.offset = offset;
263
+ pagination.limit = limit;
264
+ return {
265
+ data: paginatedRecords.value,
266
+ total: props.records.length,
267
+ };
268
+ };
269
+
270
+ watch(() => props.records, (newVal) => {
271
+ hovers.value = Object.fromEntries((newVal || []).map(record => [String(record.id), {}]));
272
+ }, { immediate: true, deep: true });
273
273
 
274
274
  function zoomImage(img) {
275
275
  zoomedImage.value = img
@@ -301,7 +301,10 @@ function convertColumnEnumToSelectOptions(columnEnumArray: any[], key: string) {
301
301
  }
302
302
 
303
303
  function updateSelectedImage(image: string, id: any, fieldName: string) {
304
- props.selected[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = image;
304
+ const record = props.records.find(rec => String(rec.id) === String(id));
305
+ if (record) {
306
+ record.data[fieldName] = image;
307
+ }
305
308
  }
306
309
 
307
310
  function handleError({ isError, errorMessage }) {
@@ -311,14 +314,15 @@ function handleError({ isError, errorMessage }) {
311
314
  });
312
315
  }
313
316
 
314
- function regenerateImages(recordInfo: any) {
315
- emit('regenerateImages', {
316
- recordInfo
317
- });
317
+ function regenerateImages(recordId: any) {
318
+ emit('regenerateImages', { recordId });
318
319
  }
319
320
 
320
321
  function updateActiveIndex(newIndex: number, id: any, fieldName: string) {
321
- props.carouselImageIndex[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = newIndex;
322
+ const record = props.records.find(rec => String(rec.id) === String(id));
323
+ if (record) {
324
+ record.carouselImageIndex[fieldName] = newIndex;
325
+ }
322
326
  }
323
327
 
324
328
  function isValidUrl(str: string): boolean {
@@ -335,14 +339,14 @@ function regerenerateFieldIconClick(item, name) {
335
339
  return;
336
340
  }
337
341
  emit('regenerateCell', {
338
- recordId: item[props.primaryKey],
342
+ recordId: item.id,
339
343
  fieldName: name
340
344
  });
341
345
  };
342
346
 
343
347
  function shouldDisableRegenerateFieldIcon(item, name) {
344
348
  if (props.outputFieldsForAnalizeFromImages.findIndex( el => el === name) !== -1 &&
345
- props.imageToTextErrorMessages[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === item[props.primaryKey])]?.[name] === 'No source images found') {
349
+ item.imageToTextErrorMessages?.[name] === 'No source images found') {
346
350
  return true;
347
351
  }
348
352
  return false;
@@ -350,13 +354,13 @@ function shouldDisableRegenerateFieldIcon(item, name) {
350
354
 
351
355
  function checkForError(item, name) {
352
356
  if (props.outputFieldsForAnalizeFromImages.findIndex( el => el === name) !== -1) {
353
- const errorMessage = props.imageToTextErrorMessages?.[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === item[props.primaryKey])]?.[name];
357
+ const errorMessage = item.imageToTextErrorMessages?.[name];
354
358
  if (errorMessage) {
355
359
  return errorMessage;
356
360
  }
357
361
  }
358
362
  if (props.outputPlainFields.findIndex( el => el === name) !== -1) {
359
- const errorMessage = props.textToTextErrorMessages?.[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === item[props.primaryKey])]?.[name];
363
+ const errorMessage = item.textToTextErrorMessages?.[name];
360
364
  if (errorMessage) {
361
365
  return errorMessage;
362
366
  }
@@ -376,13 +380,23 @@ function isAnalyzing(item, name) {
376
380
  }
377
381
 
378
382
  function isImagesAnalyzing(item) {
379
- const isImagesAnalyzing = props.isAiResponseReceivedAnalizeImage[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === item[props.primaryKey])]
380
- return isImagesAnalyzing;
383
+ return item.aiStatus.analyzedImages;
381
384
  }
382
385
 
383
386
  function isNoImageAnalyzing(item) {
384
- const isNoImageAnalyzing = props.isAiResponseReceivedAnalizeNoImage[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === item[props.primaryKey])]
385
- return isNoImageAnalyzing;
387
+ return item.aiStatus.analyzedNoImages;
388
+ }
389
+
390
+ function setHover(recordId: string | number, field: string, value: boolean) {
391
+ const key = String(recordId);
392
+ if (!hovers.value[key]) {
393
+ hovers.value[key] = {};
394
+ }
395
+ hovers.value[key][field] = value;
396
+ }
397
+
398
+ function isHovered(recordId: string | number, field: string) {
399
+ return Boolean(hovers.value?.[String(recordId)]?.[field]);
386
400
  }
387
401
 
388
402
 
@@ -11,6 +11,7 @@
11
11
  "dependencies": {
12
12
  "@iconify-prerendered/vue-mdi": "^0.25.1718880438",
13
13
  "medium-zoom": "^1.1.0",
14
+ "p-limit": "^4.0.0",
14
15
  "swiper": "^11.2.10"
15
16
  }
16
17
  },
@@ -254,6 +255,21 @@
254
255
  "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
255
256
  }
256
257
  },
258
+ "node_modules/p-limit": {
259
+ "version": "4.0.0",
260
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
261
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
262
+ "license": "MIT",
263
+ "dependencies": {
264
+ "yocto-queue": "^1.0.0"
265
+ },
266
+ "engines": {
267
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
268
+ },
269
+ "funding": {
270
+ "url": "https://github.com/sponsors/sindresorhus"
271
+ }
272
+ },
257
273
  "node_modules/picocolors": {
258
274
  "version": "1.1.1",
259
275
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -340,6 +356,18 @@
340
356
  "optional": true
341
357
  }
342
358
  }
359
+ },
360
+ "node_modules/yocto-queue": {
361
+ "version": "1.2.2",
362
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
363
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
364
+ "license": "MIT",
365
+ "engines": {
366
+ "node": ">=12.20"
367
+ },
368
+ "funding": {
369
+ "url": "https://github.com/sponsors/sindresorhus"
370
+ }
343
371
  }
344
372
  }
345
373
  }
@@ -12,6 +12,7 @@
12
12
  "dependencies": {
13
13
  "@iconify-prerendered/vue-mdi": "^0.25.1718880438",
14
14
  "medium-zoom": "^1.1.0",
15
- "swiper": "^11.2.10"
15
+ "swiper": "^11.2.10",
16
+ "p-limit": "^4.0.0"
16
17
  }
17
18
  }
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { AdminForthPlugin, Filters } from "adminforth";
10
+ import { AdminForthFilterOperators, AdminForthPlugin, Filters } from "adminforth";
11
11
  import { suggestIfTypo } from "adminforth";
12
12
  import Handlebars from 'handlebars';
13
13
  import { RateLimiter } from "adminforth";
@@ -193,10 +193,16 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
193
193
  const selectedId = recordId;
194
194
  let isError = false;
195
195
  if (STUB_MODE) {
196
- yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
197
- jobs.set(jobId, { status: 'completed', result: {} });
198
- jobs.set(jobId, { status: 'failed', error: `ERROR: test error` });
199
- return { ok: false, error: 'test error' };
196
+ const fakeError = Math.random() < 0.005; // 0.05% chance of error
197
+ // await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
198
+ if (fakeError) {
199
+ jobs.set(jobId, { status: 'failed', error: `ERROR: test error` });
200
+ return { ok: false, error: 'test error' };
201
+ }
202
+ else {
203
+ jobs.set(jobId, { status: 'completed', result: { description: 'test description', price: 99999999, engine_power: 999 } });
204
+ return { ok: true };
205
+ }
200
206
  }
201
207
  else {
202
208
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
@@ -599,7 +605,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
599
605
  isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
600
606
  isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false,
601
607
  isAttachFiles: this.options.attachFiles ? true : false,
602
- disabledWhenNoCheckboxes: true,
608
+ disabledWhenNoCheckboxes: this.options.recordSelector === 'filtered' ? false : true,
603
609
  refreshRates: {
604
610
  fillFieldsFromImages: ((_a = this.options.refreshRates) === null || _a === void 0 ? void 0 : _a.fillFieldsFromImages) || 2000,
605
611
  fillPlainFields: ((_b = this.options.refreshRates) === null || _b === void 0 ? void 0 : _b.fillPlainFields) || 1000,
@@ -607,6 +613,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
607
613
  regenerateImages: ((_d = this.options.refreshRates) === null || _d === void 0 ? void 0 : _d.regenerateImages) || 5000,
608
614
  },
609
615
  askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false,
616
+ concurrencyLimit: this.options.concurrencyLimit || 10,
617
+ recordSelector: this.options.recordSelector || 'checkbox',
618
+ askConfirmation: this.options.askConfirmation || [],
610
619
  generationPrompts: {
611
620
  plainFieldsPrompts: this.options.fillPlainFields || {},
612
621
  imageFieldsPrompts: this.options.fillFieldsFromImages || {},
@@ -750,6 +759,24 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
750
759
  };
751
760
  })
752
761
  });
762
+ server.endpoint({
763
+ method: 'POST',
764
+ path: `/plugin/${this.pluginInstanceId}/get_old_data`,
765
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
766
+ const recordId = body.recordId;
767
+ if (recordId === undefined || recordId === null) {
768
+ return { ok: false, error: "Missing recordId" };
769
+ }
770
+ const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
771
+ const record = yield this.adminforth.resource(this.resourceConfig.resourceId)
772
+ .get([Filters.EQ(primaryKeyColumn.name, recordId)]);
773
+ if (!record) {
774
+ return { ok: false, error: "Record not found" };
775
+ }
776
+ record._label = this.resourceConfig.recordLabel(record);
777
+ return { ok: true, record };
778
+ })
779
+ });
753
780
  server.endpoint({
754
781
  method: 'POST',
755
782
  path: `/plugin/${this.pluginInstanceId}/get_images`,
@@ -787,6 +814,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
787
814
  }
788
815
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
789
816
  const decimalFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'decimal').map(c => c.name);
817
+ const integerFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'integer').map(c => c.name);
818
+ const floatFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'float').map(c => c.name);
790
819
  const updates = selectedIds.map((ID, idx) => __awaiter(this, void 0, void 0, function* () {
791
820
  const oldRecord = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
792
821
  for (const [key, value] of Object.entries(outputImageFields)) {
@@ -830,16 +859,35 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
830
859
  }
831
860
  }
832
861
  }
862
+ if (integerFieldsArray.length > 0) {
863
+ for (const fieldName of integerFieldsArray) {
864
+ if (fieldsToUpdate[idx].hasOwnProperty(fieldName)) {
865
+ fieldsToUpdate[idx][fieldName] = parseInt(fieldsToUpdate[idx][fieldName], 10);
866
+ }
867
+ }
868
+ }
869
+ if (floatFieldsArray.length > 0) {
870
+ for (const fieldName of floatFieldsArray) {
871
+ if (fieldsToUpdate[idx].hasOwnProperty(fieldName)) {
872
+ fieldsToUpdate[idx][fieldName] = parseFloat(fieldsToUpdate[idx][fieldName]);
873
+ }
874
+ }
875
+ }
833
876
  const newRecord = Object.assign(Object.assign({}, oldRecord), fieldsToUpdate[idx]);
834
877
  return this.adminforth.updateResourceRecord({
835
878
  resource: this.resourceConfig,
836
879
  recordId: ID,
837
880
  oldRecord: oldRecord,
838
- record: newRecord,
881
+ updates: newRecord,
839
882
  adminUser: adminUser,
840
883
  });
841
884
  }));
842
- yield Promise.all(updates);
885
+ try {
886
+ yield Promise.all(updates);
887
+ }
888
+ catch (error) {
889
+ return { ok: false, error: `Error updating records, because of unprocesseble data for record ID ${selectedIds}` };
890
+ }
843
891
  return { ok: true };
844
892
  }
845
893
  else {
@@ -881,7 +929,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
881
929
  jobs.set(jobId, { status: "failed", error: "Missing action type" });
882
930
  //return { error: "Missing action type" };
883
931
  }
884
- else if (!recordId) {
932
+ else if (!recordId && typeof recordId !== 'number') {
885
933
  jobs.set(jobId, { status: "failed", error: "Missing record id" });
886
934
  //return { error: "Missing record id" };
887
935
  }
@@ -984,5 +1032,42 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
984
1032
  }
985
1033
  })
986
1034
  });
1035
+ server.endpoint({
1036
+ method: 'POST',
1037
+ path: `/plugin/${this.pluginInstanceId}/get_filtered_ids`,
1038
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
1039
+ const filters = body.filters;
1040
+ const normalizedFilters = { operator: AdminForthFilterOperators.AND, subFilters: [] };
1041
+ if (filters) {
1042
+ if (typeof filters !== 'object') {
1043
+ throw new Error(`Filter should be an array or an object`);
1044
+ }
1045
+ if (Array.isArray(filters)) {
1046
+ // if filters are an array, they will be connected with "AND" operator by default
1047
+ normalizedFilters.subFilters = filters;
1048
+ }
1049
+ else if (filters.field) {
1050
+ // assume filter is a SingleFilter
1051
+ normalizedFilters.subFilters = [filters];
1052
+ }
1053
+ else if (filters.subFilters) {
1054
+ // assume filter is a AndOr filter
1055
+ normalizedFilters.operator = filters.operator;
1056
+ normalizedFilters.subFilters = filters.subFilters;
1057
+ }
1058
+ else {
1059
+ // wrong filter
1060
+ throw new Error(`Wrong filter object value: ${JSON.stringify(filters)}`);
1061
+ }
1062
+ }
1063
+ const records = yield this.adminforth.resource(this.resourceConfig.resourceId).list(normalizedFilters);
1064
+ if (!records) {
1065
+ return { ok: true, recordIds: [] };
1066
+ }
1067
+ const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
1068
+ const recordIds = records.map(record => record[primaryKeyColumn.name]);
1069
+ return { ok: true, recordIds };
1070
+ })
1071
+ });
987
1072
  }
988
1073
  }