@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 +2 -2
- package/custom/ImageCompare.vue +5 -5
- package/custom/ImageGenerationCarousel.vue +13 -5
- package/custom/VisionAction.vue +183 -26
- package/custom/VisionTable.vue +11 -9
- package/custom/tsconfig.json +14 -3
- package/dist/custom/ImageCompare.vue +5 -5
- package/dist/custom/ImageGenerationCarousel.vue +13 -5
- package/dist/custom/VisionAction.vue +183 -26
- package/dist/custom/VisionTable.vue +11 -9
- package/dist/custom/tsconfig.json +14 -3
- package/dist/index.js +49 -30
- package/index.ts +34 -24
- package/package.json +1 -1
- package/types.ts +12 -2
package/build.log
CHANGED
|
@@ -13,5 +13,5 @@ custom/package-lock.json
|
|
|
13
13
|
custom/package.json
|
|
14
14
|
custom/tsconfig.json
|
|
15
15
|
|
|
16
|
-
sent
|
|
17
|
-
total size is
|
|
16
|
+
sent 80,944 bytes received 172 bytes 162,232.00 bytes/sec
|
|
17
|
+
total size is 80,312 speedup is 0.99
|
package/custom/ImageCompare.vue
CHANGED
|
@@ -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[
|
|
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
|
-
|
|
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}/
|
|
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?.
|
|
237
|
+
return resp?.prompt || null;
|
|
230
238
|
} catch (e) {
|
|
231
239
|
emit('error', {
|
|
232
240
|
isError: true,
|
package/custom/VisionAction.vue
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
15
|
-
|
|
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-[
|
|
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
|
|
50
|
-
|
|
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 (
|
|
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>
|
package/custom/VisionTable.vue
CHANGED
|
@@ -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
|
|
package/custom/tsconfig.json
CHANGED
|
@@ -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[
|
|
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
|
-
|
|
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}/
|
|
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?.
|
|
237
|
+
return resp?.prompt || null;
|
|
230
238
|
} catch (e) {
|
|
231
239
|
emit('error', {
|
|
232
240
|
isError: true,
|