@adminforth/bulk-ai-flow 1.5.5 → 1.6.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 CHANGED
@@ -11,5 +11,5 @@ custom/tsconfig.json
11
11
  custom/visionAction.vue
12
12
  custom/visionTable.vue
13
13
 
14
- sent 182,651 bytes received 134 bytes 365,570.00 bytes/sec
15
- total size is 182,116 speedup is 1.00
14
+ sent 180,778 bytes received 134 bytes 361,824.00 bytes/sec
15
+ total size is 180,250 speedup is 1.00
@@ -183,7 +183,7 @@
183
183
 
184
184
  <script setup lang="ts">
185
185
 
186
- import { ref, onMounted, nextTick, Ref, h, computed, watch, reactive } from 'vue'
186
+ import { ref, onMounted, nextTick, Ref, watch } from 'vue'
187
187
  import { Carousel } from 'flowbite';
188
188
  import { callAdminForthApi } from '@/utils';
189
189
  import { useI18n } from 'vue-i18n';
@@ -267,9 +267,8 @@ onMounted(async () => {
267
267
  return context[field.trim()] || '';
268
268
  });
269
269
 
270
- if (props.record[props.record[props.meta.recorPkFieldName]]) {
271
270
  const recordId = props.record[props.meta.recorPkFieldName];
272
- } else {
271
+ if (!recordId) {
273
272
  emit('error', {
274
273
  isError: true,
275
274
  errorMessage: 'Record ID not found, cannot generate images'
@@ -1,19 +1,19 @@
1
1
  <template>
2
- <div class="flex items-end justify-start gap-2" @click="openDialog">
2
+ <div class="flex items-end justify-start gap-2 cursor-pointer" @click="openDialog">
3
3
  <div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
4
4
  AI
5
5
  </div>
6
- <p class="text-justify max-h-[18px]">{{ props.meta.actionName }}</p>
6
+ <p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
7
7
  </div>
8
8
  <Dialog ref="confirmDialog">
9
9
  <div
10
10
  class="fixed inset-0 z-20 flex items-center justify-center bg-black/40"
11
11
  >
12
12
  <div
13
- class="bulk-vision-dialog flex items-center justify-center relative max-w-[95vw] min-w-[640px] max-h-[90vh] bg-white dark:bg-gray-900 rounded-md shadow-2xl overflow-hidden"
13
+ class="bulk-vision-dialog flex items-center justify-center relative w-[100vw] h-[100vh] max-h-[100vh] md:w-auto md:max-w-[95vw] md:min-w-[640px] md:h-auto md:max-h-[90vh] bg-white dark:bg-gray-900 rounded-none md:rounded-md shadow-2xl overflow-hidden"
14
14
  @click.stop
15
15
  >
16
- <div class="bulk-vision-table flex flex-col items-center justify-evenly gap-4 w-full h-full p-6 overflow-y-auto">
16
+ <div class="bulk-vision-table flex flex-col items-center justify-evenly gap-3 md:gap-4 w-full h-full p-4 md:p-6 overflow-y-auto overflow-x-auto">
17
17
  <button type="button"
18
18
  @click="closeDialog"
19
19
  class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
@@ -22,35 +22,36 @@
22
22
  </svg>
23
23
  </button>
24
24
 
25
- <VisionTable
26
- v-if="records && props.checkboxes.length"
27
- :checkbox="props.checkboxes"
28
- :records="records"
29
- :index="0"
30
- :meta="props.meta"
31
- :images="images"
32
- :tableHeaders="tableHeaders"
33
- :tableColumns="tableColumns"
34
- :customFieldNames="customFieldNames"
35
- :tableColumnsIndexes="tableColumnsIndexes"
36
- :selected="selected"
37
- :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
38
- :isAiResponseReceivedImage="isAiResponseReceivedImage"
39
- :primaryKey="primaryKey"
40
- :openGenerationCarousel="openGenerationCarousel"
41
- @error="handleTableError"
42
- />
43
- <div class="flex w-full items-end justify-end gap-4">
44
- <div class="h-full text-red-600 font-semibold flex items-center justify-center mb-2">
25
+ <div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
26
+ <VisionTable
27
+ :checkbox="props.checkboxes"
28
+ :records="records"
29
+ :index="0"
30
+ :meta="props.meta"
31
+ :images="images"
32
+ :tableHeaders="tableHeaders"
33
+ :tableColumns="tableColumns"
34
+ :customFieldNames="customFieldNames"
35
+ :tableColumnsIndexes="tableColumnsIndexes"
36
+ :selected="selected"
37
+ :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
38
+ :isAiResponseReceivedImage="isAiResponseReceivedImage"
39
+ :primaryKey="primaryKey"
40
+ :openGenerationCarousel="openGenerationCarousel"
41
+ @error="handleTableError"
42
+ />
43
+ </div>
44
+ <div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
45
+ <div class="h-full text-red-600 font-semibold flex items-center justify-center md:mb-2">
45
46
  <p v-if="isError === true">{{ errorMessage }}</p>
46
47
  </div>
47
- <button type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
48
+ <button type="button" class="w-full md:w-auto py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
48
49
  @click="closeDialog"
49
50
  >
50
51
  {{'Cancel'}}
51
52
  </button>
52
53
  <Button
53
- class="w-64"
54
+ class="w-full md:w-64"
54
55
  @click="saveData"
55
56
  :disabled="isLoading || checkedCount < 1 || isCriticalError"
56
57
  :loader="isLoading"
@@ -66,7 +67,7 @@
66
67
 
67
68
  <script lang="ts" setup>
68
69
  import { callAdminForthApi } from '@/utils';
69
- import { ref, watch } from 'vue'
70
+ import { Ref, ref, watch } from 'vue'
70
71
  import { Dialog, Button } from '@/afcl';
71
72
  import VisionTable from './visionTable.vue'
72
73
  import adminforth from '@/adminforth';
@@ -131,13 +132,25 @@ const openDialog = async () => {
131
132
  isLoading.value = true;
132
133
  const tasks = [];
133
134
  if (props.meta.isFieldsForAnalizeFromImages) {
134
- tasks.push(analyzeFields());
135
+ tasks.push(runAiAction({
136
+ endpoint: 'analyze',
137
+ actionType: 'analyze',
138
+ responseFlag: isAiResponseReceivedAnalize,
139
+ }));
135
140
  }
136
141
  if (props.meta.isFieldsForAnalizePlain) {
137
- tasks.push(analyzeFieldsNoImages());
142
+ tasks.push(runAiAction({
143
+ endpoint: 'analyze_no_images',
144
+ actionType: 'analyze_no_images',
145
+ responseFlag: isAiResponseReceivedAnalize,
146
+ }));
138
147
  }
139
148
  if (props.meta.isImageGeneration) {
140
- tasks.push(generateImages());
149
+ tasks.push(runAiAction({
150
+ endpoint: 'initial_image_generate',
151
+ actionType: 'generate_images',
152
+ responseFlag: isAiResponseReceivedImage,
153
+ }));
141
154
  }
142
155
  await Promise.all(tasks);
143
156
  isLoading.value = false;
@@ -160,6 +173,7 @@ const closeDialog = () => {
160
173
  tableColumnsIndexes.value = [];
161
174
  isError.value = false;
162
175
  isCriticalError.value = false;
176
+ isImageGenerationError.value = false;
163
177
  errorMessage.value = '';
164
178
  }
165
179
 
@@ -329,111 +343,6 @@ async function convertImages(fieldName, img) {
329
343
  }
330
344
 
331
345
 
332
- async function analyzeFields() {
333
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
334
- try {
335
- const res = await callAdminForthApi({
336
- path: `/plugin/${props.meta.pluginInstanceId}/analyze`,
337
- method: 'POST',
338
- body: {
339
- selectedIds: props.checkboxes,
340
- },
341
- });
342
-
343
- if (res?.error) {
344
- adminforth.alert({
345
- message: res.error,
346
- variant: 'danger',
347
- timeout: 'unlimited',
348
- });
349
-
350
- console.error('Failed to analyze image(s):', res.error);
351
- isError.value = true;
352
- //isCriticalError.value = true;
353
- errorMessage.value = `Failed to fetch analyze image(s). Please, try to re-run the action.`;
354
- } else {
355
- res.result.forEach((item, idx) => {
356
- const pk = selected.value[idx]?.[primaryKey]
357
-
358
- if (pk) {
359
- selected.value[idx] = {
360
- ...selected.value[idx],
361
- ...item,
362
- isChecked: true,
363
- [primaryKey]: pk
364
- }
365
- }
366
- })
367
- }
368
- } catch (error) {
369
- adminforth.alert({
370
- message: error,
371
- variant: 'danger',
372
- timeout: 'unlimited',
373
- });
374
-
375
- console.error('Failed to analyze image(s):', error);
376
- isError.value = true;
377
- //isCriticalError.value = true;
378
- errorMessage.value = res.error;
379
- }
380
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
381
- }
382
-
383
-
384
- async function analyzeFieldsNoImages() {
385
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
386
- try {
387
- const res = await callAdminForthApi({
388
- path: `/plugin/${props.meta.pluginInstanceId}/analyze_no_images`,
389
- method: 'POST',
390
- body: {
391
- selectedIds: props.checkboxes,
392
- },
393
- });
394
- if(res?.error) {
395
- adminforth.alert({
396
- message: res.error,
397
- variant: 'danger',
398
- timeout: 'unlimited',
399
- });
400
- console.error('Failed to analyze fields:', res.error);
401
- isError.value = true;
402
- //isCriticalError.value = true;
403
- errorMessage.value = res.error;
404
- } else {
405
- res.result.forEach((item, idx) => {
406
- const pk = selected.value[idx]?.[primaryKey]
407
-
408
- if (pk) {
409
- selected.value[idx] = {
410
- ...selected.value[idx],
411
- ...item,
412
- isChecked: true,
413
- [primaryKey]: pk
414
- }
415
- }
416
- })
417
- }
418
- } catch (error) {
419
- adminforth.alert({
420
- message: error,
421
- variant: 'danger',
422
- timeout: 'unlimited',
423
- });
424
- console.error('Failed to analyze fields:', error);
425
- isError.value = true;
426
- //isCriticalError.value = true;
427
- errorMessage.value = `Failed to analyze fields. Please, try to re-run the action.`;
428
- }
429
- if(!props.meta.isFieldsForAnalizeFromImages) {
430
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
431
- }
432
- }
433
-
434
-
435
-
436
-
437
346
  async function saveData() {
438
347
  if (!selected.value?.length) {
439
348
  adminforth.alert({ message: 'No items selected', variant: 'warning' });
@@ -496,57 +405,73 @@ async function saveData() {
496
405
  }
497
406
  }
498
407
 
499
- async function generateImages() {
500
- isAiResponseReceivedImage.value = props.checkboxes.map(() => false);
501
- let res;
502
- let error = null;
408
+
409
+ async function runAiAction({
410
+ endpoint,
411
+ actionType,
412
+ responseFlag,
413
+ updateOnSuccess = true,
414
+ }: {
415
+ endpoint: string;
416
+ actionType: 'analyze' | 'analyze_no_images' | 'generate_images';
417
+ responseFlag: Ref<boolean[]>;
418
+ updateOnSuccess?: boolean;
419
+ }) {
420
+ let res: any;
421
+ let error: any = null;
503
422
 
504
423
  try {
424
+ responseFlag.value = props.checkboxes.map(() => false);
425
+
505
426
  res = await callAdminForthApi({
506
- path: `/plugin/${props.meta.pluginInstanceId}/initial_image_generate`,
427
+ path: `/plugin/${props.meta.pluginInstanceId}/${endpoint}`,
507
428
  method: 'POST',
508
429
  body: {
509
430
  selectedIds: props.checkboxes,
510
431
  },
511
432
  });
433
+
434
+ if (actionType !== 'analyze_no_images' || !props.meta.isFieldsForAnalizeFromImages) {
435
+ responseFlag.value = props.checkboxes.map(() => true);
436
+ }
512
437
  } catch (e) {
513
- console.error('Error generating images:', e);
514
- isError.value = true;
515
- isImageGenerationError.value = true;
516
- errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
438
+ console.error(`Error during ${actionType}:`, e);
439
+ error = `Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`;
517
440
  }
518
- isAiResponseReceivedImage.value = props.checkboxes.map(() => true);
519
441
 
520
442
  if (res?.error) {
521
443
  error = res.error;
522
444
  }
523
- if (!res) {
524
- error = 'Error generating images, something went wrong';
525
- isError.value = true;
526
- isImageGenerationError.value = true;
527
- errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
445
+ if (!res && !error) {
446
+ error = `Error: ${actionType} request returned empty response.`;
528
447
  }
529
448
 
530
- if (error) {
449
+ if (error) {
531
450
  adminforth.alert({
532
451
  message: error,
533
452
  variant: 'danger',
534
453
  timeout: 'unlimited',
535
454
  });
536
455
  isError.value = true;
537
- isImageGenerationError.value = true;
456
+ if (actionType === 'generate_images') {
457
+ isImageGenerationError.value = true;
458
+ }
538
459
  errorMessage.value = error;
539
- } else {
540
- res.result.forEach((item, idx) => {
541
- const pk = selected.value[idx]?.[primaryKey]
460
+ return;
461
+ }
462
+
463
+ if (updateOnSuccess) {
464
+ res.result.forEach((item: any, idx: number) => {
465
+ const pk = selected.value[idx]?.[primaryKey];
542
466
  if (pk) {
543
467
  selected.value[idx] = {
544
468
  ...selected.value[idx],
545
469
  ...item,
546
- [primaryKey]: pk
547
- }
470
+ isChecked: true,
471
+ [primaryKey]: pk,
472
+ };
548
473
  }
549
- })
474
+ });
550
475
  }
551
476
  }
552
477
 
@@ -47,6 +47,7 @@
47
47
  <div v-if="isAiResponseReceivedAnalize[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && !isInColumnImage(n)">
48
48
  <div v-if="isInColumnEnum(n)">
49
49
  <Select
50
+ class="min-w-[150px] "
50
51
  :options="convertColumnEnumToSelectOptions(props.meta.columnEnums, n)"
51
52
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
52
53
  :teleportToTop="true"
@@ -56,7 +57,7 @@
56
57
  </div>
57
58
  <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'">
58
59
  <Textarea
59
- class="w-full h-full"
60
+ class="min-w-[150px] w-full h-full"
60
61
  type="text"
61
62
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
62
63
  >
@@ -183,7 +183,7 @@
183
183
 
184
184
  <script setup lang="ts">
185
185
 
186
- import { ref, onMounted, nextTick, Ref, h, computed, watch, reactive } from 'vue'
186
+ import { ref, onMounted, nextTick, Ref, watch } from 'vue'
187
187
  import { Carousel } from 'flowbite';
188
188
  import { callAdminForthApi } from '@/utils';
189
189
  import { useI18n } from 'vue-i18n';
@@ -267,9 +267,8 @@ onMounted(async () => {
267
267
  return context[field.trim()] || '';
268
268
  });
269
269
 
270
- if (props.record[props.record[props.meta.recorPkFieldName]]) {
271
270
  const recordId = props.record[props.meta.recorPkFieldName];
272
- } else {
271
+ if (!recordId) {
273
272
  emit('error', {
274
273
  isError: true,
275
274
  errorMessage: 'Record ID not found, cannot generate images'
@@ -1,19 +1,19 @@
1
1
  <template>
2
- <div class="flex items-end justify-start gap-2" @click="openDialog">
2
+ <div class="flex items-end justify-start gap-2 cursor-pointer" @click="openDialog">
3
3
  <div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
4
4
  AI
5
5
  </div>
6
- <p class="text-justify max-h-[18px]">{{ props.meta.actionName }}</p>
6
+ <p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
7
7
  </div>
8
8
  <Dialog ref="confirmDialog">
9
9
  <div
10
10
  class="fixed inset-0 z-20 flex items-center justify-center bg-black/40"
11
11
  >
12
12
  <div
13
- class="bulk-vision-dialog flex items-center justify-center relative max-w-[95vw] min-w-[640px] max-h-[90vh] bg-white dark:bg-gray-900 rounded-md shadow-2xl overflow-hidden"
13
+ class="bulk-vision-dialog flex items-center justify-center relative w-[100vw] h-[100vh] max-h-[100vh] md:w-auto md:max-w-[95vw] md:min-w-[640px] md:h-auto md:max-h-[90vh] bg-white dark:bg-gray-900 rounded-none md:rounded-md shadow-2xl overflow-hidden"
14
14
  @click.stop
15
15
  >
16
- <div class="bulk-vision-table flex flex-col items-center justify-evenly gap-4 w-full h-full p-6 overflow-y-auto">
16
+ <div class="bulk-vision-table flex flex-col items-center justify-evenly gap-3 md:gap-4 w-full h-full p-4 md:p-6 overflow-y-auto overflow-x-auto">
17
17
  <button type="button"
18
18
  @click="closeDialog"
19
19
  class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
@@ -22,35 +22,36 @@
22
22
  </svg>
23
23
  </button>
24
24
 
25
- <VisionTable
26
- v-if="records && props.checkboxes.length"
27
- :checkbox="props.checkboxes"
28
- :records="records"
29
- :index="0"
30
- :meta="props.meta"
31
- :images="images"
32
- :tableHeaders="tableHeaders"
33
- :tableColumns="tableColumns"
34
- :customFieldNames="customFieldNames"
35
- :tableColumnsIndexes="tableColumnsIndexes"
36
- :selected="selected"
37
- :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
38
- :isAiResponseReceivedImage="isAiResponseReceivedImage"
39
- :primaryKey="primaryKey"
40
- :openGenerationCarousel="openGenerationCarousel"
41
- @error="handleTableError"
42
- />
43
- <div class="flex w-full items-end justify-end gap-4">
44
- <div class="h-full text-red-600 font-semibold flex items-center justify-center mb-2">
25
+ <div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
26
+ <VisionTable
27
+ :checkbox="props.checkboxes"
28
+ :records="records"
29
+ :index="0"
30
+ :meta="props.meta"
31
+ :images="images"
32
+ :tableHeaders="tableHeaders"
33
+ :tableColumns="tableColumns"
34
+ :customFieldNames="customFieldNames"
35
+ :tableColumnsIndexes="tableColumnsIndexes"
36
+ :selected="selected"
37
+ :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
38
+ :isAiResponseReceivedImage="isAiResponseReceivedImage"
39
+ :primaryKey="primaryKey"
40
+ :openGenerationCarousel="openGenerationCarousel"
41
+ @error="handleTableError"
42
+ />
43
+ </div>
44
+ <div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
45
+ <div class="h-full text-red-600 font-semibold flex items-center justify-center md:mb-2">
45
46
  <p v-if="isError === true">{{ errorMessage }}</p>
46
47
  </div>
47
- <button type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
48
+ <button type="button" class="w-full md:w-auto py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
48
49
  @click="closeDialog"
49
50
  >
50
51
  {{'Cancel'}}
51
52
  </button>
52
53
  <Button
53
- class="w-64"
54
+ class="w-full md:w-64"
54
55
  @click="saveData"
55
56
  :disabled="isLoading || checkedCount < 1 || isCriticalError"
56
57
  :loader="isLoading"
@@ -66,7 +67,7 @@
66
67
 
67
68
  <script lang="ts" setup>
68
69
  import { callAdminForthApi } from '@/utils';
69
- import { ref, watch } from 'vue'
70
+ import { Ref, ref, watch } from 'vue'
70
71
  import { Dialog, Button } from '@/afcl';
71
72
  import VisionTable from './visionTable.vue'
72
73
  import adminforth from '@/adminforth';
@@ -131,13 +132,25 @@ const openDialog = async () => {
131
132
  isLoading.value = true;
132
133
  const tasks = [];
133
134
  if (props.meta.isFieldsForAnalizeFromImages) {
134
- tasks.push(analyzeFields());
135
+ tasks.push(runAiAction({
136
+ endpoint: 'analyze',
137
+ actionType: 'analyze',
138
+ responseFlag: isAiResponseReceivedAnalize,
139
+ }));
135
140
  }
136
141
  if (props.meta.isFieldsForAnalizePlain) {
137
- tasks.push(analyzeFieldsNoImages());
142
+ tasks.push(runAiAction({
143
+ endpoint: 'analyze_no_images',
144
+ actionType: 'analyze_no_images',
145
+ responseFlag: isAiResponseReceivedAnalize,
146
+ }));
138
147
  }
139
148
  if (props.meta.isImageGeneration) {
140
- tasks.push(generateImages());
149
+ tasks.push(runAiAction({
150
+ endpoint: 'initial_image_generate',
151
+ actionType: 'generate_images',
152
+ responseFlag: isAiResponseReceivedImage,
153
+ }));
141
154
  }
142
155
  await Promise.all(tasks);
143
156
  isLoading.value = false;
@@ -160,6 +173,7 @@ const closeDialog = () => {
160
173
  tableColumnsIndexes.value = [];
161
174
  isError.value = false;
162
175
  isCriticalError.value = false;
176
+ isImageGenerationError.value = false;
163
177
  errorMessage.value = '';
164
178
  }
165
179
 
@@ -329,111 +343,6 @@ async function convertImages(fieldName, img) {
329
343
  }
330
344
 
331
345
 
332
- async function analyzeFields() {
333
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
334
- try {
335
- const res = await callAdminForthApi({
336
- path: `/plugin/${props.meta.pluginInstanceId}/analyze`,
337
- method: 'POST',
338
- body: {
339
- selectedIds: props.checkboxes,
340
- },
341
- });
342
-
343
- if (res?.error) {
344
- adminforth.alert({
345
- message: res.error,
346
- variant: 'danger',
347
- timeout: 'unlimited',
348
- });
349
-
350
- console.error('Failed to analyze image(s):', res.error);
351
- isError.value = true;
352
- //isCriticalError.value = true;
353
- errorMessage.value = `Failed to fetch analyze image(s). Please, try to re-run the action.`;
354
- } else {
355
- res.result.forEach((item, idx) => {
356
- const pk = selected.value[idx]?.[primaryKey]
357
-
358
- if (pk) {
359
- selected.value[idx] = {
360
- ...selected.value[idx],
361
- ...item,
362
- isChecked: true,
363
- [primaryKey]: pk
364
- }
365
- }
366
- })
367
- }
368
- } catch (error) {
369
- adminforth.alert({
370
- message: error,
371
- variant: 'danger',
372
- timeout: 'unlimited',
373
- });
374
-
375
- console.error('Failed to analyze image(s):', error);
376
- isError.value = true;
377
- //isCriticalError.value = true;
378
- errorMessage.value = res.error;
379
- }
380
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
381
- }
382
-
383
-
384
- async function analyzeFieldsNoImages() {
385
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
386
- try {
387
- const res = await callAdminForthApi({
388
- path: `/plugin/${props.meta.pluginInstanceId}/analyze_no_images`,
389
- method: 'POST',
390
- body: {
391
- selectedIds: props.checkboxes,
392
- },
393
- });
394
- if(res?.error) {
395
- adminforth.alert({
396
- message: res.error,
397
- variant: 'danger',
398
- timeout: 'unlimited',
399
- });
400
- console.error('Failed to analyze fields:', res.error);
401
- isError.value = true;
402
- //isCriticalError.value = true;
403
- errorMessage.value = res.error;
404
- } else {
405
- res.result.forEach((item, idx) => {
406
- const pk = selected.value[idx]?.[primaryKey]
407
-
408
- if (pk) {
409
- selected.value[idx] = {
410
- ...selected.value[idx],
411
- ...item,
412
- isChecked: true,
413
- [primaryKey]: pk
414
- }
415
- }
416
- })
417
- }
418
- } catch (error) {
419
- adminforth.alert({
420
- message: error,
421
- variant: 'danger',
422
- timeout: 'unlimited',
423
- });
424
- console.error('Failed to analyze fields:', error);
425
- isError.value = true;
426
- //isCriticalError.value = true;
427
- errorMessage.value = `Failed to analyze fields. Please, try to re-run the action.`;
428
- }
429
- if(!props.meta.isFieldsForAnalizeFromImages) {
430
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
431
- }
432
- }
433
-
434
-
435
-
436
-
437
346
  async function saveData() {
438
347
  if (!selected.value?.length) {
439
348
  adminforth.alert({ message: 'No items selected', variant: 'warning' });
@@ -496,57 +405,73 @@ async function saveData() {
496
405
  }
497
406
  }
498
407
 
499
- async function generateImages() {
500
- isAiResponseReceivedImage.value = props.checkboxes.map(() => false);
501
- let res;
502
- let error = null;
408
+
409
+ async function runAiAction({
410
+ endpoint,
411
+ actionType,
412
+ responseFlag,
413
+ updateOnSuccess = true,
414
+ }: {
415
+ endpoint: string;
416
+ actionType: 'analyze' | 'analyze_no_images' | 'generate_images';
417
+ responseFlag: Ref<boolean[]>;
418
+ updateOnSuccess?: boolean;
419
+ }) {
420
+ let res: any;
421
+ let error: any = null;
503
422
 
504
423
  try {
424
+ responseFlag.value = props.checkboxes.map(() => false);
425
+
505
426
  res = await callAdminForthApi({
506
- path: `/plugin/${props.meta.pluginInstanceId}/initial_image_generate`,
427
+ path: `/plugin/${props.meta.pluginInstanceId}/${endpoint}`,
507
428
  method: 'POST',
508
429
  body: {
509
430
  selectedIds: props.checkboxes,
510
431
  },
511
432
  });
433
+
434
+ if (actionType !== 'analyze_no_images' || !props.meta.isFieldsForAnalizeFromImages) {
435
+ responseFlag.value = props.checkboxes.map(() => true);
436
+ }
512
437
  } catch (e) {
513
- console.error('Error generating images:', e);
514
- isError.value = true;
515
- isImageGenerationError.value = true;
516
- errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
438
+ console.error(`Error during ${actionType}:`, e);
439
+ error = `Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`;
517
440
  }
518
- isAiResponseReceivedImage.value = props.checkboxes.map(() => true);
519
441
 
520
442
  if (res?.error) {
521
443
  error = res.error;
522
444
  }
523
- if (!res) {
524
- error = 'Error generating images, something went wrong';
525
- isError.value = true;
526
- isImageGenerationError.value = true;
527
- errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
445
+ if (!res && !error) {
446
+ error = `Error: ${actionType} request returned empty response.`;
528
447
  }
529
448
 
530
- if (error) {
449
+ if (error) {
531
450
  adminforth.alert({
532
451
  message: error,
533
452
  variant: 'danger',
534
453
  timeout: 'unlimited',
535
454
  });
536
455
  isError.value = true;
537
- isImageGenerationError.value = true;
456
+ if (actionType === 'generate_images') {
457
+ isImageGenerationError.value = true;
458
+ }
538
459
  errorMessage.value = error;
539
- } else {
540
- res.result.forEach((item, idx) => {
541
- const pk = selected.value[idx]?.[primaryKey]
460
+ return;
461
+ }
462
+
463
+ if (updateOnSuccess) {
464
+ res.result.forEach((item: any, idx: number) => {
465
+ const pk = selected.value[idx]?.[primaryKey];
542
466
  if (pk) {
543
467
  selected.value[idx] = {
544
468
  ...selected.value[idx],
545
469
  ...item,
546
- [primaryKey]: pk
547
- }
470
+ isChecked: true,
471
+ [primaryKey]: pk,
472
+ };
548
473
  }
549
- })
474
+ });
550
475
  }
551
476
  }
552
477
 
@@ -47,6 +47,7 @@
47
47
  <div v-if="isAiResponseReceivedAnalize[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && !isInColumnImage(n)">
48
48
  <div v-if="isInColumnEnum(n)">
49
49
  <Select
50
+ class="min-w-[150px] "
50
51
  :options="convertColumnEnumToSelectOptions(props.meta.columnEnums, n)"
51
52
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
52
53
  :teleportToTop="true"
@@ -56,7 +57,7 @@
56
57
  </div>
57
58
  <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'">
58
59
  <Textarea
59
- class="w-full h-full"
60
+ class="min-w-[150px] w-full h-full"
60
61
  type="text"
61
62
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
62
63
  >
package/dist/index.js CHANGED
@@ -19,44 +19,28 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
19
19
  this.totalDuration = 0;
20
20
  }
21
21
  // Compile Handlebars templates in outputFields using record fields as context
22
- compileOutputFieldsTemplates(record) {
22
+ compileTemplates(source, record, valueSelector) {
23
23
  const compiled = {};
24
- for (const [key, templateStr] of Object.entries(this.options.fillFieldsFromImages)) {
24
+ for (const [key, value] of Object.entries(source)) {
25
+ const templateStr = valueSelector(value);
25
26
  try {
26
- const tpl = Handlebars.compile(String(templateStr));
27
+ const tpl = Handlebars.compile(templateStr);
27
28
  compiled[key] = tpl(record);
28
29
  }
29
30
  catch (_a) {
30
- compiled[key] = String(templateStr);
31
+ compiled[key] = templateStr;
31
32
  }
32
33
  }
33
34
  return compiled;
34
35
  }
36
+ compileOutputFieldsTemplates(record) {
37
+ return this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v));
38
+ }
35
39
  compileOutputFieldsTemplatesNoImage(record) {
36
- const compiled = {};
37
- for (const [key, templateStr] of Object.entries(this.options.fillPlainFields)) {
38
- try {
39
- const tpl = Handlebars.compile(String(templateStr));
40
- compiled[key] = tpl(record);
41
- }
42
- catch (_a) {
43
- compiled[key] = String(templateStr);
44
- }
45
- }
46
- return compiled;
40
+ return this.compileTemplates(this.options.fillPlainFields, record, v => String(v));
47
41
  }
48
42
  compileGenerationFieldTemplates(record) {
49
- const compiled = {};
50
- for (const key in this.options.generateImages) {
51
- try {
52
- const tpl = Handlebars.compile(String(this.options.generateImages[key].prompt));
53
- compiled[key] = tpl(record);
54
- }
55
- catch (_a) {
56
- compiled[key] = String(this.options.generateImages[key].prompt);
57
- }
58
- }
59
- return compiled;
43
+ return this.compileTemplates(this.options.generateImages, record, v => String(v.prompt));
60
44
  }
61
45
  checkRateLimit(field, fieldNameRateLimit, headers) {
62
46
  if (fieldNameRateLimit) {
@@ -261,7 +245,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
261
245
  Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
262
246
  If it's number field - return only number.`;
263
247
  //send prompt to OpenAI and get response
264
- const { content: chatResponse, finishReason } = yield this.options.textCompleteAdapter.complete(prompt, [], 500);
248
+ const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], 500);
265
249
  const resp = chatResponse.response;
266
250
  const topLevelError = chatResponse.error;
267
251
  if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
@@ -383,6 +367,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
383
367
  attachmentFiles = yield this.options.attachFiles({ record });
384
368
  }
385
369
  const images = yield Promise.all((new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
370
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
371
+ return null;
372
+ }
386
373
  if (STUB_MODE) {
387
374
  yield new Promise((resolve) => setTimeout(resolve, 2000));
388
375
  return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
@@ -448,7 +435,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
448
435
  }
449
436
  else {
450
437
  generationAdapter = this.options.imageGenerationAdapter;
451
- ``;
452
438
  }
453
439
  const resp = yield generationAdapter.generate({
454
440
  prompt,
package/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { AdminForthPlugin, Filters } from "adminforth";
2
- import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource } from "adminforth";
2
+ import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResource } from "adminforth";
3
3
  import type { PluginOptions } from './types.js';
4
- import { json } from "stream/consumers";
5
- import Handlebars, { compile } from 'handlebars';
4
+ import Handlebars from 'handlebars';
6
5
  import { RateLimiter } from "adminforth";
7
6
 
8
7
 
@@ -22,43 +21,34 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
22
21
  }
23
22
 
24
23
  // Compile Handlebars templates in outputFields using record fields as context
25
- private compileOutputFieldsTemplates(record: any): Record<string, string> {
24
+ private compileTemplates<T extends Record<string, any>>(
25
+ source: T,
26
+ record: any,
27
+ valueSelector: (value: T[keyof T]) => string
28
+ ): Record<string, string> {
26
29
  const compiled: Record<string, string> = {};
27
- for (const [key, templateStr] of Object.entries(this.options.fillFieldsFromImages)) {
30
+ for (const [key, value] of Object.entries(source)) {
31
+ const templateStr = valueSelector(value);
28
32
  try {
29
- const tpl = Handlebars.compile(String(templateStr));
33
+ const tpl = Handlebars.compile(templateStr);
30
34
  compiled[key] = tpl(record);
31
35
  } catch {
32
- compiled[key] = String(templateStr);
36
+ compiled[key] = templateStr;
33
37
  }
34
38
  }
35
39
  return compiled;
36
40
  }
37
41
 
38
- private compileOutputFieldsTemplatesNoImage(record: any): Record<string, string> {
39
- const compiled: Record<string, string> = {};
40
- for (const [key, templateStr] of Object.entries(this.options.fillPlainFields)) {
41
- try {
42
- const tpl = Handlebars.compile(String(templateStr));
43
- compiled[key] = tpl(record);
44
- } catch {
45
- compiled[key] = String(templateStr);
46
- }
47
- }
48
- return compiled;
42
+ private compileOutputFieldsTemplates(record: any) {
43
+ return this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v));
44
+ }
45
+
46
+ private compileOutputFieldsTemplatesNoImage(record: any) {
47
+ return this.compileTemplates(this.options.fillPlainFields, record, v => String(v));
49
48
  }
50
49
 
51
50
  private compileGenerationFieldTemplates(record: any) {
52
- const compiled: Record<string, any> = {};
53
- for (const key in this.options.generateImages) {
54
- try {
55
- const tpl = Handlebars.compile(String(this.options.generateImages[key].prompt));
56
- compiled[key] = tpl(record);
57
- } catch {
58
- compiled[key] = String(this.options.generateImages[key].prompt);
59
- }
60
- }
61
- return compiled;
51
+ return this.compileTemplates(this.options.generateImages, record, v => String(v.prompt));
62
52
  }
63
53
 
64
54
  private checkRateLimit(field: string,fieldNameRateLimit: string | undefined, headers: Record<string, string | string[] | undefined>): { error?: string } | void {
@@ -294,7 +284,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
294
284
  Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
295
285
  If it's number field - return only number.`;
296
286
  //send prompt to OpenAI and get response
297
- const { content: chatResponse, finishReason } = await this.options.textCompleteAdapter.complete(prompt, [], 500);
287
+ const { content: chatResponse } = await this.options.textCompleteAdapter.complete(prompt, [], 500);
298
288
 
299
289
  const resp: any = (chatResponse as any).response;
300
290
  const topLevelError = (chatResponse as any).error;
@@ -428,7 +418,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
428
418
  }
429
419
  const images = await Promise.all(
430
420
  (new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(async () => {
431
-
421
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
422
+ return null;
423
+ }
432
424
  if (STUB_MODE) {
433
425
  await new Promise((resolve) => setTimeout(resolve, 2000));
434
426
  return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
@@ -492,7 +484,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
492
484
  if (this.options.generateImages[key].adapter) {
493
485
  generationAdapter = this.options.generateImages[key].adapter;
494
486
  } else {
495
- generationAdapter = this.options.imageGenerationAdapter;``
487
+ generationAdapter = this.options.imageGenerationAdapter;
496
488
  }
497
489
  const resp = await generationAdapter.generate(
498
490
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.5.5",
3
+ "version": "1.6.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },