@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
|
@@ -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>
|
|
@@ -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
|
}
|
package/dist/index.js
CHANGED
|
@@ -25,27 +25,39 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
25
25
|
}
|
|
26
26
|
// Compile Handlebars templates in outputFields using record fields as context
|
|
27
27
|
compileTemplates(source, record, valueSelector) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const tpl = Handlebars.compile(templateStr);
|
|
33
|
-
compiled[key] = tpl(record);
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
if (this.options.provideAdditionalContextForRecord) {
|
|
30
|
+
const additionalFields = yield this.options.provideAdditionalContextForRecord({ record, adminUser: null, resource: this.resourceConfig });
|
|
31
|
+
record = Object.assign(Object.assign({}, record), additionalFields);
|
|
34
32
|
}
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const compiled = {};
|
|
34
|
+
for (const [key, value] of Object.entries(source)) {
|
|
35
|
+
const templateStr = valueSelector(value);
|
|
36
|
+
try {
|
|
37
|
+
const tpl = Handlebars.compile(templateStr);
|
|
38
|
+
compiled[key] = tpl(record);
|
|
39
|
+
}
|
|
40
|
+
catch (_a) {
|
|
41
|
+
compiled[key] = templateStr;
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
return compiled;
|
|
45
|
+
});
|
|
40
46
|
}
|
|
41
|
-
compileOutputFieldsTemplates(record) {
|
|
42
|
-
return
|
|
47
|
+
compileOutputFieldsTemplates(record, customPrompt) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
return yield this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.fillFieldsFromImages, record, v => String(v));
|
|
50
|
+
});
|
|
43
51
|
}
|
|
44
|
-
compileOutputFieldsTemplatesNoImage(record) {
|
|
45
|
-
return
|
|
52
|
+
compileOutputFieldsTemplatesNoImage(record, customPrompt) {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
return yield this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.fillPlainFields, record, v => String(v));
|
|
55
|
+
});
|
|
46
56
|
}
|
|
47
|
-
compileGenerationFieldTemplates(record) {
|
|
48
|
-
return
|
|
57
|
+
compileGenerationFieldTemplates(record, customPrompt) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
return yield this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt));
|
|
60
|
+
});
|
|
49
61
|
}
|
|
50
62
|
checkRateLimit(field, fieldNameRateLimit, headers) {
|
|
51
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -65,7 +77,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
65
77
|
}
|
|
66
78
|
});
|
|
67
79
|
}
|
|
68
|
-
analyze_image(jobId, recordId, adminUser, headers) {
|
|
80
|
+
analyze_image(jobId, recordId, adminUser, headers, customPrompt) {
|
|
69
81
|
return __awaiter(this, void 0, void 0, function* () {
|
|
70
82
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
71
83
|
const selectedId = recordId;
|
|
@@ -100,7 +112,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
100
112
|
return { ok: false, error: 'One of the image URLs is not valid' };
|
|
101
113
|
}
|
|
102
114
|
//create prompt for OpenAI
|
|
103
|
-
const compiledOutputFields = this.compileOutputFieldsTemplates(record);
|
|
115
|
+
const compiledOutputFields = yield this.compileOutputFieldsTemplates(record, customPrompt);
|
|
104
116
|
const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
105
117
|
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
106
118
|
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
|
|
@@ -145,7 +157,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
145
157
|
}
|
|
146
158
|
});
|
|
147
159
|
}
|
|
148
|
-
analyzeNoImages(jobId, recordId, adminUser, headers) {
|
|
160
|
+
analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt) {
|
|
149
161
|
return __awaiter(this, void 0, void 0, function* () {
|
|
150
162
|
const selectedId = recordId;
|
|
151
163
|
let isError = false;
|
|
@@ -157,7 +169,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
157
169
|
else {
|
|
158
170
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
159
171
|
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)]);
|
|
160
|
-
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
172
|
+
const compiledOutputFields = yield this.compileOutputFieldsTemplatesNoImage(record, customPrompt);
|
|
161
173
|
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
162
174
|
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
163
175
|
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
@@ -187,7 +199,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
187
199
|
}
|
|
188
200
|
});
|
|
189
201
|
}
|
|
190
|
-
initialImageGenerate(jobId, recordId, adminUser, headers) {
|
|
202
|
+
initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt) {
|
|
191
203
|
return __awaiter(this, void 0, void 0, function* () {
|
|
192
204
|
var _a, _b;
|
|
193
205
|
const selectedId = recordId;
|
|
@@ -211,7 +223,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
214
|
-
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
226
|
+
const prompt = (yield this.compileGenerationFieldTemplates(record, customPrompt))[key];
|
|
215
227
|
let images;
|
|
216
228
|
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
217
229
|
isError = true;
|
|
@@ -409,6 +421,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
409
421
|
fillPlainFields: ((_b = this.options.refreshRates) === null || _b === void 0 ? void 0 : _b.fillPlainFields) || 1000,
|
|
410
422
|
generateImages: ((_c = this.options.refreshRates) === null || _c === void 0 ? void 0 : _c.generateImages) || 5000,
|
|
411
423
|
regenerateImages: ((_d = this.options.refreshRates) === null || _d === void 0 ? void 0 : _d.regenerateImages) || 5000,
|
|
424
|
+
},
|
|
425
|
+
askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false,
|
|
426
|
+
generationPrompts: {
|
|
427
|
+
plainFieldsPrompts: this.options.fillPlainFields || {},
|
|
428
|
+
imageFieldsPrompts: this.options.fillFieldsFromImages || {},
|
|
429
|
+
imageGenerationPrompts: this.options.generateImages || {},
|
|
412
430
|
}
|
|
413
431
|
}
|
|
414
432
|
};
|
|
@@ -480,7 +498,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
480
498
|
}
|
|
481
499
|
}
|
|
482
500
|
}
|
|
483
|
-
if (this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) {
|
|
501
|
+
if ((this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) && !this.options.provideAdditionalContextForRecord) {
|
|
484
502
|
let matches = [];
|
|
485
503
|
const regex = /{{(.*?)}}/g;
|
|
486
504
|
if (this.options.fillFieldsFromImages) {
|
|
@@ -628,13 +646,14 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
628
646
|
});
|
|
629
647
|
server.endpoint({
|
|
630
648
|
method: 'POST',
|
|
631
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
649
|
+
path: `/plugin/${this.pluginInstanceId}/get_image_generation_prompts`,
|
|
632
650
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, headers }) {
|
|
633
651
|
var _b;
|
|
634
652
|
const Id = body.recordId || [];
|
|
653
|
+
const customPrompt = body.customPrompt || null;
|
|
635
654
|
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_b = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _b === void 0 ? void 0 : _b.name, Id)]);
|
|
636
|
-
const compiledGenerationOptions = this.compileGenerationFieldTemplates(record);
|
|
637
|
-
return
|
|
655
|
+
const compiledGenerationOptions = yield this.compileGenerationFieldTemplates(record, JSON.stringify({ "prompt": customPrompt }));
|
|
656
|
+
return compiledGenerationOptions;
|
|
638
657
|
})
|
|
639
658
|
});
|
|
640
659
|
server.endpoint({
|
|
@@ -652,7 +671,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
652
671
|
method: 'POST',
|
|
653
672
|
path: `/plugin/${this.pluginInstanceId}/create-job`,
|
|
654
673
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
655
|
-
const { actionType, recordId } = body;
|
|
674
|
+
const { actionType, recordId, customPrompt } = body;
|
|
656
675
|
const jobId = randomUUID();
|
|
657
676
|
jobs.set(jobId, { status: "in_progress" });
|
|
658
677
|
if (!actionType) {
|
|
@@ -666,13 +685,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
666
685
|
else {
|
|
667
686
|
switch (actionType) {
|
|
668
687
|
case 'generate_images':
|
|
669
|
-
this.initialImageGenerate(jobId, recordId, adminUser, headers);
|
|
688
|
+
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
|
|
670
689
|
break;
|
|
671
690
|
case 'analyze_no_images':
|
|
672
|
-
this.analyzeNoImages(jobId, recordId, adminUser, headers);
|
|
691
|
+
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
|
|
673
692
|
break;
|
|
674
693
|
case 'analyze':
|
|
675
|
-
this.analyze_image(jobId, recordId, adminUser, headers);
|
|
694
|
+
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
|
|
676
695
|
break;
|
|
677
696
|
case 'regenerate_images':
|
|
678
697
|
if (!body.prompt || !body.fieldName) {
|