@adminforth/bulk-ai-flow 1.14.5 → 1.15.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
@@ -13,5 +13,5 @@ custom/package-lock.json
13
13
  custom/package.json
14
14
  custom/tsconfig.json
15
15
 
16
- sent 74,113 bytes received 172 bytes 148,570.00 bytes/sec
17
- total size is 73,472 speedup is 0.99
16
+ sent 80,944 bytes received 172 bytes 162,232.00 bytes/sec
17
+ total size is 80,312 speedup is 0.99
@@ -10,12 +10,12 @@
10
10
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
11
11
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
12
12
  </svg>
13
- <span class="sr-only">Close modal</span>
13
+ <span class="sr-only">{{ $t('Close modal') }}</span>
14
14
  </button>
15
15
  </div>
16
16
  <div class="flex gap-4 items-start justify-between">
17
- <h3 class="text-sm font-medium text-gray-700 mb-2">Old Image</h3>
18
- <h3 class="text-sm font-medium text-gray-700 mb-2">New Image</h3>
17
+ <h3 class="text-sm font-medium text-gray-700 mb-2">{{ $t('Old Image') }}</h3>
18
+ <h3 class="text-sm font-medium text-gray-700 mb-2">{{ $t('New Image') }}</h3>
19
19
  </div>
20
20
  <div class="flex gap-4 items-center">
21
21
  <!-- Old Image -->
@@ -29,7 +29,7 @@
29
29
  class="w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200"
30
30
  />
31
31
  <div v-else class="w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center">
32
- <p class="text-gray-500">No old image</p>
32
+ <p class="text-gray-500">{{ $t('No old image') }}</p>
33
33
  </div>
34
34
  </div>
35
35
  </div>
@@ -54,7 +54,7 @@
54
54
  class="w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200"
55
55
  />
56
56
  <div v-else class="w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center">
57
- <p class="text-gray-500">No new image</p>
57
+ <p class="text-gray-500">{{ $t('No new image') }}</p>
58
58
  </div>
59
59
  </div>
60
60
  </div>
@@ -143,7 +143,7 @@ const sliderRef = ref(null)
143
143
 
144
144
  const prompt = ref('');
145
145
  const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
146
- const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage']);
146
+ const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage', 'imageGenerationPrompts']);
147
147
  const images = ref([]);
148
148
  const loading = ref(false);
149
149
  const attachmentFiles = ref<string[]>([])
@@ -154,7 +154,7 @@ onMounted(async () => {
154
154
  }
155
155
  const temp = await getGenerationPrompt() || '';
156
156
  attachmentFiles.value = props.sourceImage || [];
157
- prompt.value = temp[props.fieldName];
157
+ prompt.value = Object.keys(JSON.parse(temp))[0];
158
158
  await nextTick();
159
159
 
160
160
  const currentIndex = props.carouselImageIndex || 0;
@@ -212,12 +212,20 @@ async function getHistoricalAverage() {
212
212
  }
213
213
 
214
214
  async function getGenerationPrompt() {
215
- try{
215
+ const [key, ...rest] = props.imageGenerationPrompts.split(":");
216
+ const value = rest.join(":").trim();
217
+
218
+ const json = {
219
+ [key.trim()]: value
220
+ };
221
+
222
+ try {
216
223
  const resp = await callAdminForthApi({
217
- path: `/plugin/${props.meta.pluginInstanceId}/get_generation_prompts`,
224
+ path: `/plugin/${props.meta.pluginInstanceId}/get_image_generation_prompts`,
218
225
  method: 'POST',
219
226
  body: {
220
227
  recordId: props.recordId,
228
+ customPrompt: JSON.stringify(json) || {},
221
229
  },
222
230
  });
223
231
  if(!resp) {
@@ -226,7 +234,7 @@ async function getGenerationPrompt() {
226
234
  errorMessage: "Error getting generation prompts."
227
235
  });
228
236
  }
229
- return resp?.generationOptions || null;
237
+ return resp?.prompt || null;
230
238
  } catch (e) {
231
239
  emit('error', {
232
240
  isError: true,
@@ -8,16 +8,49 @@
8
8
  <Dialog
9
9
  ref="confirmDialog"
10
10
  header="Bulk AI Flow"
11
- class="[scrollbar-gutter:stable] !max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
11
+ class="[scrollbar-gutter:stable] !max-w-full w-fit h-fit"
12
+ :class="popupMode === 'generation' ? 'lg:w-[1600px] !lg:max-w-[1600px]'
13
+ : popupMode === 'settings' ? 'lg:w-[1000px] !lg:max-w-[1000px]'
14
+ : 'lg:w-[500px] !lg:max-w-[500px]'"
12
15
  :beforeCloseFunction="closeDialog"
13
- :buttons="[
14
- { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages, loader: isLoading, class: 'w-fit' }, onclick: async (dialog) => { await saveData(); dialog.hide(); } },
15
- { label: 'Cancel', options: {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'}, onclick: (dialog) => dialog.hide() },
16
- ]"
16
+ :buttons="popupMode === 'generation' ? [
17
+ {
18
+ label: checkedCount > 1 ? 'Save fields' : 'Save field',
19
+ options: {
20
+ disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages,
21
+ loader: isLoading, class: 'w-fit'
22
+ },
23
+ onclick: async (dialog) => { await saveData(); dialog.hide(); }
24
+ },
25
+ {
26
+ label: 'Cancel',
27
+ options: {
28
+ 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'
29
+ },
30
+ onclick: (dialog) => dialog.hide()
31
+ },
32
+ ] : popupMode === 'settings' ? [
33
+ {
34
+ label: 'Save settings',
35
+ options: {
36
+ class: 'w-fit'
37
+ },
38
+ onclick: (dialog) => { saveSettings(); }
39
+ },
40
+ ] :
41
+ [
42
+ {
43
+ label: 'Edit prompts',
44
+ options: {
45
+ class: 'w-fit ml-auto'
46
+ },
47
+ onclick: (dialog) => { clickSettingsButton(); }
48
+ },
49
+ ]"
17
50
  :click-to-close-outside="false"
18
51
  >
19
- <div class="[scrollbar-gutter:stable] bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[90vh] gap-3 md:gap-4 w-full h-full overflow-y-auto">
20
- <div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
52
+ <div class="[scrollbar-gutter:stable] bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[75vh] gap-3 md:gap-4 w-full h-full overflow-y-auto">
53
+ <div v-if="records && props.checkboxes.length && popupMode === 'generation'" class="w-full overflow-x-auto">
21
54
  <VisionTable
22
55
  :checkbox="props.checkboxes"
23
56
  :records="records"
@@ -44,10 +77,40 @@
44
77
  :imageGenerationErrorMessage="imageGenerationErrorMessage"
45
78
  @regenerate-images="regenerateImages"
46
79
  :isImageHasPreviewUrl="isImageHasPreviewUrl"
80
+ :imageGenerationPrompts="generationPrompts.generateImages"
47
81
  />
82
+ <div class="text-red-600 flex items-center w-full">
83
+ <p v-if="isError === true">{{ errorMessage }}</p>
84
+ </div>
48
85
  </div>
49
- <div class="text-red-600 flex items-center w-full">
50
- <p v-if="isError === true">{{ errorMessage }}</p>
86
+ <div
87
+ v-else-if="popupMode === 'settings'"
88
+ v-for="(promptsCategory, key) in generationPrompts"
89
+ :key="key"
90
+ class="w-full"
91
+ >
92
+ <div v-if="Object.keys(promptsCategory).length > 0" class="gap-4 mb-6 ml-1">
93
+ <p class="text-start w-full text-xl font-bold mb-2">{{
94
+ key === "plainFieldsPrompts" ? "Prompts for non-image fields"
95
+ : key === "generateImages" ? "Prompts for image fields"
96
+ : "Prompts for image analysis"
97
+ }}</p>
98
+ <div class="grid grid-cols-2 gap-4">
99
+ <div v-for="(prompt, promptKey) in promptsCategory" :key="promptKey">
100
+ {{ formatLabel(promptKey) }} prompt:
101
+ <Textarea
102
+ v-model="generationPrompts[key][promptKey]"
103
+ class="w-full h-32 p-2 border border-gray-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-purple-500"
104
+ ></Textarea>
105
+ <p class="text-red-500 hover:underline hover:cursor-pointer mt-2" @click="resetPromptToDefault(key, promptKey)">reset to default</p>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ <div v-else class="flex flex-col gap-2">
111
+ <Button @click="runAiActions" class="px-5 py-2.5 my-20 bg-gradient-to-r 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 rounded-md text-white border-none">
112
+ Start generation
113
+ </Button>
51
114
  </div>
52
115
  </div>
53
116
  </Dialog>
@@ -56,11 +119,11 @@
56
119
  <script lang="ts" setup>
57
120
  import { callAdminForthApi } from '@/utils';
58
121
  import { Ref, ref, watch } from 'vue'
59
- import { Dialog, Button } from '@/afcl';
122
+ import { Dialog, Button, Textarea } from '@/afcl';
60
123
  import VisionTable from './VisionTable.vue'
61
124
  import adminforth from '@/adminforth';
62
125
  import { useI18n } from 'vue-i18n';
63
- import { AdminUser, type AdminForthResourceCommon } from '@/types';
126
+ import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
64
127
  import { run } from 'node:test';
65
128
 
66
129
  const { t } = useI18n();
@@ -114,6 +177,8 @@ const aiGenerationErrorMessage = ref<string[]>([]);
114
177
  const isAiImageGenerationError = ref<boolean[]>([false]);
115
178
  const imageGenerationErrorMessage = ref<string[]>([]);
116
179
  const isImageHasPreviewUrl = ref<Record<string, boolean>>({});
180
+ const popupMode = ref<'generation' | 'confirmation' | 'settings'>('confirmation');
181
+ const generationPrompts = ref<any>({});
117
182
 
118
183
  const openDialog = async () => {
119
184
  isDialogOpen.value = true;
@@ -144,8 +209,19 @@ const openDialog = async () => {
144
209
  },{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
145
210
  }
146
211
  isFetchingRecords.value = false;
147
-
148
- if (props.meta.isImageGeneration) {
212
+ // Ensure prompts are loaded before any automatic AI action run
213
+ if (!generationPrompts.value || Object.keys(generationPrompts.value).length === 0) {
214
+ await getGenerationPrompts();
215
+ }
216
+ if (!props.meta.askConfirmationBeforeGenerating) {
217
+ runAiActions();
218
+ }
219
+ }
220
+
221
+
222
+ function runAiActions() {
223
+ popupMode.value = 'generation';
224
+ if (props.meta.isImageGeneration) {
149
225
  isGeneratingImages.value = true;
150
226
  runAiAction({
151
227
  endpoint: 'initial_image_generate',
@@ -170,9 +246,8 @@ const openDialog = async () => {
170
246
  });
171
247
  }
172
248
  }
173
-
249
+
174
250
  const closeDialog = () => {
175
- confirmDialog.value.close();
176
251
  isAiResponseReceivedAnalize.value = [];
177
252
  isAiResponseReceivedImage.value = [];
178
253
 
@@ -186,10 +261,10 @@ const closeDialog = () => {
186
261
  isImageGenerationError.value = false;
187
262
  errorMessage.value = '';
188
263
  isDialogOpen.value = false;
264
+ popupMode.value = 'confirmation';
189
265
  }
190
266
 
191
267
  watch(selected, (val) => {
192
- //console.log('Selected changed:', val);
193
268
  checkedCount.value = val.filter(item => item.isChecked === true).length;
194
269
  }, { deep: true });
195
270
 
@@ -302,7 +377,7 @@ async function getRecords() {
302
377
  } catch (error) {
303
378
  console.error('Failed to get records:', error);
304
379
  isError.value = true;
305
- errorMessage.value = `Failed to fetch records. Please, try to re-run the action.`;
380
+ errorMessage.value = t(`Failed to fetch records. Please, try to re-run the action.`);
306
381
  }
307
382
  }
308
383
 
@@ -319,7 +394,7 @@ async function getImages() {
319
394
  } catch (error) {
320
395
  console.error('Failed to get images:', error);
321
396
  isError.value = true;
322
- errorMessage.value = `Failed to fetch images. Please, try to re-run the action.`;
397
+ errorMessage.value = t(`Failed to fetch images. Please, try to re-run the action.`);
323
398
  }
324
399
  }
325
400
 
@@ -375,7 +450,7 @@ async function convertImages(fieldName, img) {
375
450
 
376
451
  async function saveData() {
377
452
  if (!selected.value?.length) {
378
- adminforth.alert({ message: 'No items selected', variant: 'warning' });
453
+ adminforth.alert({ message: t('No items selected'), variant: 'warning' });
379
454
  return;
380
455
  }
381
456
  try {
@@ -420,16 +495,16 @@ async function saveData() {
420
495
  timeout: 'unlimited',
421
496
  });
422
497
  isError.value = true;
423
- errorMessage.value = `Failed to save data. You are not allowed to save.`;
498
+ errorMessage.value = t(`Failed to save data. You are not allowed to save.`);
424
499
  } else {
425
500
  console.error('Error saving data:', res);
426
501
  isError.value = true;
427
- errorMessage.value = `Failed to save data. Please, try to re-run the action.`;
502
+ errorMessage.value = t(`Failed to save data. Please, try to re-run the action.`);
428
503
  }
429
504
  } catch (error) {
430
505
  console.error('Error saving data:', error);
431
506
  isError.value = true;
432
- errorMessage.value = `Failed to save data. Please, try to re-run the action.`;
507
+ errorMessage.value = t(`Failed to save data. Please, try to re-run the action.`);
433
508
  } finally {
434
509
  isLoading.value = false;
435
510
  }
@@ -474,7 +549,7 @@ async function runAiAction({
474
549
  if (rateLimitRes?.error) {
475
550
  isRateLimitExceeded = true;
476
551
  adminforth.alert({
477
- message: `Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`,
552
+ message: t(`Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`),
478
553
  variant: 'danger',
479
554
  timeout: 'unlimited',
480
555
  });
@@ -482,7 +557,7 @@ async function runAiAction({
482
557
  }
483
558
  } catch (e) {
484
559
  adminforth.alert({
485
- message: `Error checking rate limit for "${actionType.replace('_', ' ')}" action.`,
560
+ message: t(`Error checking rate limit for "${actionType.replace('_', ' ')}" action.`),
486
561
  variant: 'danger',
487
562
  timeout: 'unlimited',
488
563
  });
@@ -492,6 +567,15 @@ async function runAiAction({
492
567
  return;
493
568
  };
494
569
  }
570
+
571
+ let customPrompt;
572
+ if (actionType === 'generate_images') {
573
+ customPrompt = generationPrompts.value.imageGenerationPrompts || generationPrompts.value.generateImages;
574
+ } else if (actionType === 'analyze') {
575
+ customPrompt = generationPrompts.value.imageFieldsPrompts;
576
+ } else if (actionType === 'analyze_no_images') {
577
+ customPrompt = generationPrompts.value.plainFieldsPrompts;
578
+ }
495
579
  //creating jobs
496
580
  const tasks = recordsIds.map(async (checkbox, i) => {
497
581
  try {
@@ -501,6 +585,7 @@ async function runAiAction({
501
585
  body: {
502
586
  actionType: actionType,
503
587
  recordId: checkbox,
588
+ ...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
504
589
  },
505
590
  });
506
591
 
@@ -516,7 +601,7 @@ async function runAiAction({
516
601
  } catch (e) {
517
602
  console.error(`Error during ${actionType} for item ${i}:`, e);
518
603
  hasError = true;
519
- errorMessage = `Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`;
604
+ errorMessage = t(`Failed to ${actionType.replace('_', ' ')}. Please, try to re-run the action.`);
520
605
  return { success: false, index: i, error: e };
521
606
  }
522
607
  });
@@ -591,7 +676,7 @@ async function runAiAction({
591
676
  }
592
677
  isAtLeastOneInProgress = true;
593
678
  adminforth.alert({
594
- message: `Generation action "${actionType.replace('_', ' ')}" failed for record: ${recordId}. Error: ${jobResponse.job?.error || 'Unknown error'}`,
679
+ message: t(`Generation action "${actionType.replace('_', ' ')}" failed for record: ${recordId}. Error: ${jobResponse.job?.error || 'Unknown error'}`),
595
680
  variant: 'danger',
596
681
  timeout: 'unlimited',
597
682
  });
@@ -753,4 +838,76 @@ function click() {
753
838
  openDialog();
754
839
  }
755
840
 
841
+ function saveSettings() {
842
+ popupMode.value = 'confirmation';
843
+ localStorage.setItem(`bulkAiFlowGenerationPrompts_${props.meta.pluginInstanceId}`, JSON.stringify(generationPrompts.value));
844
+ }
845
+
846
+ async function getGenerationPrompts() {
847
+ const calculatedGenerationPrompts: any = {};
848
+ const savedPrompts = localStorage.getItem(`bulkAiFlowGenerationPrompts_${props.meta.pluginInstanceId}`);
849
+ if (props.meta.generationPrompts.plainFieldsPrompts) {
850
+ calculatedGenerationPrompts.plainFieldsPrompts = props.meta.generationPrompts.plainFieldsPrompts;
851
+ }
852
+ if (props.meta.generationPrompts.imageFieldsPrompts) {
853
+ calculatedGenerationPrompts.imageFieldsPrompts = props.meta.generationPrompts.imageFieldsPrompts;
854
+ }
855
+ if (props.meta.generationPrompts.imageGenerationPrompts) {
856
+ let imageFields = {};
857
+ for (const [key, value] of Object.entries(props.meta.generationPrompts.imageGenerationPrompts)) {
858
+ // value might be typed as unknown; cast to any to access prompt safely
859
+ imageFields[key] = (value as any).prompt;
860
+ }
861
+ calculatedGenerationPrompts.generateImages = imageFields;
862
+ }
863
+ if (savedPrompts && props.meta.askConfirmationBeforeGenerating) {
864
+
865
+ generationPrompts.value = checkAndAddNewFieldsToPrompts(JSON.parse(savedPrompts), calculatedGenerationPrompts);
866
+
867
+ return;
868
+ }
869
+ generationPrompts.value = calculatedGenerationPrompts;
870
+ }
871
+
872
+ function resetPromptToDefault(categoryKey, promptKey) {
873
+ if (categoryKey === 'generateImages') {
874
+ generationPrompts.value[categoryKey][promptKey] = props.meta.generationPrompts.imageGenerationPrompts[promptKey].prompt;
875
+ return;
876
+ }
877
+ generationPrompts.value[categoryKey][promptKey] = props.meta.generationPrompts[categoryKey][promptKey];
878
+ }
879
+
880
+ function clickSettingsButton() {
881
+ getGenerationPrompts();
882
+ popupMode.value = 'settings';
883
+ }
884
+
885
+
886
+ function checkAndAddNewFieldsToPrompts(savedPrompts, defaultPrompts) {
887
+ for (const categoryKey in defaultPrompts) {
888
+ if (!savedPrompts.hasOwnProperty(categoryKey)) {
889
+ savedPrompts[categoryKey] = defaultPrompts[categoryKey];
890
+ } else {
891
+ for (const promptKey in defaultPrompts[categoryKey]) {
892
+ if (!savedPrompts[categoryKey].hasOwnProperty(promptKey)) {
893
+ savedPrompts[categoryKey][promptKey] = defaultPrompts[categoryKey][promptKey];
894
+ }
895
+ }
896
+ }
897
+ }
898
+ //remove deprecated fields
899
+ for (const categoryKey in savedPrompts) {
900
+ if (!defaultPrompts.hasOwnProperty(categoryKey)) {
901
+ delete savedPrompts[categoryKey];
902
+ } else {
903
+ for (const promptKey in savedPrompts[categoryKey]) {
904
+ if (!defaultPrompts[categoryKey].hasOwnProperty(promptKey)) {
905
+ delete savedPrompts[categoryKey][promptKey];
906
+ }
907
+ }
908
+ }
909
+ }
910
+ return savedPrompts;
911
+ }
912
+
756
913
  </script>
@@ -6,7 +6,7 @@
6
6
  >
7
7
  <!-- HEADER TEMPLATE -->
8
8
  <template #header:checkboxes="{ item }">
9
- MARK FOR SAVE
9
+ {{ $t('MARK FOR SAVE') }}
10
10
  </template>
11
11
  <!-- CHECKBOX CELL TEMPLATE -->
12
12
  <template #cell:checkboxes="{ item }">
@@ -28,12 +28,12 @@
28
28
  @click="zoomImage(image)"
29
29
  />
30
30
  <div v-else class="w-20 h-20">
31
- <p>Invalid source image</p>
31
+ <p>{{ $t('Invalid source image') }}</p>
32
32
  </div>
33
33
  </div>
34
34
  </div>
35
35
  <div class="flex items-center justify-center text-center w-20 h-20" v-else>
36
- <p>No images found</p>
36
+ <p>{{ $t('No images found') }}</p>
37
37
  </div>
38
38
  <transition name="fade">
39
39
  <div
@@ -66,7 +66,7 @@
66
66
  </Select>
67
67
  <Tooltip>
68
68
  <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] }">
69
- <p class="text-sm ">old value</p>
69
+ <p class="text-sm ">{{ $t('old value') }}</p>
70
70
  </div>
71
71
  <template #tooltip>
72
72
  {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }}
@@ -82,7 +82,7 @@
82
82
  </Textarea>
83
83
  <Tooltip>
84
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
- <p class="text-sm ">old value</p>
85
+ <p class="text-sm ">{{ $t('old value') }}</p>
86
86
  </div>
87
87
  <template #tooltip>
88
88
  <p class="max-w-[200px]">{{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }}</p>
@@ -97,7 +97,7 @@
97
97
  </Toggle>
98
98
  <Tooltip>
99
99
  <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] }">
100
- <p class="text-sm ">old value</p>
100
+ <p class="text-sm ">{{ $t('old value') }}</p>
101
101
  </div>
102
102
  <template #tooltip>
103
103
  {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }}
@@ -113,7 +113,7 @@
113
113
  />
114
114
  <Tooltip>
115
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
- <p class="text-sm ">old value</p>
116
+ <p class="text-sm ">{{ $t('old value') }}</p>
117
117
  </div>
118
118
  <template #tooltip>
119
119
  {{ oldData[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }}
@@ -137,14 +137,14 @@
137
137
  :class="{ 'opacity-0': !hovers[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] }"
138
138
  @click="() => {openImageCompare[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = true}"
139
139
  >
140
- old image
140
+ {{ $t('old image') }}
141
141
  </p>
142
142
  </div>
143
143
  <div v-else class="flex items-center justify-center text-center w-20 h-20">
144
144
  <Tooltip v-if="imageGenerationErrorMessage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] === 'No source images found'">
145
145
  <p
146
146
  >
147
- Can't generate image.
147
+ {{ $t("Can't generate image.") }}
148
148
  </p>
149
149
  <template #tooltip>
150
150
  {{ imageGenerationErrorMessage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] }}
@@ -173,6 +173,7 @@
173
173
  :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
174
174
  :regenerateImagesRefreshRate="regenerateImagesRefreshRate"
175
175
  :sourceImage="item.images && item.images.length ? item.images : null"
176
+ :imageGenerationPrompts="imageGenerationPrompts[n]"
176
177
  @error="handleError"
177
178
  @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
178
179
  @selectImage="updateSelectedImage"
@@ -231,6 +232,7 @@ const props = defineProps<{
231
232
  imageGenerationErrorMessage: string[],
232
233
  oldData: any[],
233
234
  isImageHasPreviewUrl: Record<string, boolean>
235
+ imageGenerationPrompts: Record<string, any>
234
236
  }>();
235
237
  const emit = defineEmits(['error', 'regenerateImages']);
236
238
 
@@ -4,16 +4,27 @@
4
4
  "paths": {
5
5
  "@/*": [
6
6
  // "node_modules/adminforth/dist/spa/src/*"
7
- "../../../spa/src/*"
7
+ "../../../adminforth/spa/src/*"
8
8
  ],
9
9
  "*": [
10
10
  // "node_modules/adminforth/dist/spa/node_modules/*"
11
- "../../../spa/node_modules/*"
11
+ "../../../adminforth/spa/node_modules/*"
12
12
  ],
13
13
  "@@/*": [
14
14
  // "node_modules/adminforth/dist/spa/src/*"
15
15
  "."
16
16
  ]
17
17
  }
18
- }
18
+ },
19
+ "include": [
20
+ "./**/*.ts",
21
+ "./**/*.tsx",
22
+ "./**/*.vue",
23
+ "../**/*.ts",
24
+ "../**/*.tsx",
25
+ "../**/*.vue",
26
+ "../*.vue",
27
+ "../*.ts",
28
+ "../*.tsx"
29
+ ]
19
30
  }
@@ -10,12 +10,12 @@
10
10
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
11
11
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
12
12
  </svg>
13
- <span class="sr-only">Close modal</span>
13
+ <span class="sr-only">{{ $t('Close modal') }}</span>
14
14
  </button>
15
15
  </div>
16
16
  <div class="flex gap-4 items-start justify-between">
17
- <h3 class="text-sm font-medium text-gray-700 mb-2">Old Image</h3>
18
- <h3 class="text-sm font-medium text-gray-700 mb-2">New Image</h3>
17
+ <h3 class="text-sm font-medium text-gray-700 mb-2">{{ $t('Old Image') }}</h3>
18
+ <h3 class="text-sm font-medium text-gray-700 mb-2">{{ $t('New Image') }}</h3>
19
19
  </div>
20
20
  <div class="flex gap-4 items-center">
21
21
  <!-- Old Image -->
@@ -29,7 +29,7 @@
29
29
  class="w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200"
30
30
  />
31
31
  <div v-else class="w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center">
32
- <p class="text-gray-500">No old image</p>
32
+ <p class="text-gray-500">{{ $t('No old image') }}</p>
33
33
  </div>
34
34
  </div>
35
35
  </div>
@@ -54,7 +54,7 @@
54
54
  class="w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200"
55
55
  />
56
56
  <div v-else class="w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center">
57
- <p class="text-gray-500">No new image</p>
57
+ <p class="text-gray-500">{{ $t('No new image') }}</p>
58
58
  </div>
59
59
  </div>
60
60
  </div>
@@ -143,7 +143,7 @@ const sliderRef = ref(null)
143
143
 
144
144
  const prompt = ref('');
145
145
  const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
146
- const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage']);
146
+ const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage', 'imageGenerationPrompts']);
147
147
  const images = ref([]);
148
148
  const loading = ref(false);
149
149
  const attachmentFiles = ref<string[]>([])
@@ -154,7 +154,7 @@ onMounted(async () => {
154
154
  }
155
155
  const temp = await getGenerationPrompt() || '';
156
156
  attachmentFiles.value = props.sourceImage || [];
157
- prompt.value = temp[props.fieldName];
157
+ prompt.value = Object.keys(JSON.parse(temp))[0];
158
158
  await nextTick();
159
159
 
160
160
  const currentIndex = props.carouselImageIndex || 0;
@@ -212,12 +212,20 @@ async function getHistoricalAverage() {
212
212
  }
213
213
 
214
214
  async function getGenerationPrompt() {
215
- try{
215
+ const [key, ...rest] = props.imageGenerationPrompts.split(":");
216
+ const value = rest.join(":").trim();
217
+
218
+ const json = {
219
+ [key.trim()]: value
220
+ };
221
+
222
+ try {
216
223
  const resp = await callAdminForthApi({
217
- path: `/plugin/${props.meta.pluginInstanceId}/get_generation_prompts`,
224
+ path: `/plugin/${props.meta.pluginInstanceId}/get_image_generation_prompts`,
218
225
  method: 'POST',
219
226
  body: {
220
227
  recordId: props.recordId,
228
+ customPrompt: JSON.stringify(json) || {},
221
229
  },
222
230
  });
223
231
  if(!resp) {
@@ -226,7 +234,7 @@ async function getGenerationPrompt() {
226
234
  errorMessage: "Error getting generation prompts."
227
235
  });
228
236
  }
229
- return resp?.generationOptions || null;
237
+ return resp?.prompt || null;
230
238
  } catch (e) {
231
239
  emit('error', {
232
240
  isError: true,