@adminforth/bulk-ai-flow 1.14.6 → 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/ImageGenerationCarousel.vue +13 -5
- package/custom/VisionAction.vue +173 -16
- package/custom/VisionTable.vue +2 -0
- package/custom/tsconfig.json +14 -3
- package/dist/custom/ImageGenerationCarousel.vue +13 -5
- package/dist/custom/VisionAction.vue +173 -16
- package/dist/custom/VisionTable.vue +2 -0
- 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
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
@@ -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
|
}
|
|
@@ -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,
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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>
|
|
@@ -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) {
|
package/index.ts
CHANGED
|
@@ -25,11 +25,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Compile Handlebars templates in outputFields using record fields as context
|
|
28
|
-
private compileTemplates<T extends Record<string, any>>(
|
|
28
|
+
private async compileTemplates<T extends Record<string, any>>(
|
|
29
29
|
source: T,
|
|
30
30
|
record: any,
|
|
31
31
|
valueSelector: (value: T[keyof T]) => string
|
|
32
|
-
): Record<string, string
|
|
32
|
+
): Promise<Record<string, string>> {
|
|
33
|
+
if (this.options.provideAdditionalContextForRecord) {
|
|
34
|
+
const additionalFields = await this.options.provideAdditionalContextForRecord({ record, adminUser: null, resource: this.resourceConfig });
|
|
35
|
+
record = { ...record, ...additionalFields };
|
|
36
|
+
}
|
|
33
37
|
const compiled: Record<string, string> = {};
|
|
34
38
|
for (const [key, value] of Object.entries(source)) {
|
|
35
39
|
const templateStr = valueSelector(value);
|
|
@@ -43,16 +47,16 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
43
47
|
return compiled;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
private compileOutputFieldsTemplates(record: any) {
|
|
47
|
-
return this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v));
|
|
50
|
+
private async compileOutputFieldsTemplates(record: any, customPrompt? : string) {
|
|
51
|
+
return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) :this.options.fillFieldsFromImages, record, v => String(v));
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
private compileOutputFieldsTemplatesNoImage(record: any) {
|
|
51
|
-
return this.compileTemplates(this.options.fillPlainFields, record, v => String(v));
|
|
54
|
+
private async compileOutputFieldsTemplatesNoImage(record: any, customPrompt? : string) {
|
|
55
|
+
return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.fillPlainFields, record, v => String(v));
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
private compileGenerationFieldTemplates(record: any) {
|
|
55
|
-
return this.compileTemplates(this.options.generateImages, record, v => String(v.prompt));
|
|
58
|
+
private async compileGenerationFieldTemplates(record: any, customPrompt? : string) {
|
|
59
|
+
return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt));
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
private async checkRateLimit(field: string, fieldNameRateLimit: string | undefined, headers: Record<string, string | string[] | undefined>): Promise<void | { error?: string; }> {
|
|
@@ -72,7 +76,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined
|
|
79
|
+
private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
76
80
|
const selectedId = recordId;
|
|
77
81
|
let isError = false;
|
|
78
82
|
// Fetch the record using the provided ID
|
|
@@ -102,7 +106,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
102
106
|
return { ok: false, error: 'One of the image URLs is not valid' };
|
|
103
107
|
}
|
|
104
108
|
//create prompt for OpenAI
|
|
105
|
-
const compiledOutputFields = this.compileOutputFieldsTemplates(record);
|
|
109
|
+
const compiledOutputFields = await this.compileOutputFieldsTemplates(record, customPrompt);
|
|
106
110
|
const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
107
111
|
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
108
112
|
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
|
|
@@ -148,7 +152,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
148
152
|
|
|
149
153
|
}
|
|
150
154
|
|
|
151
|
-
private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined
|
|
155
|
+
private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
152
156
|
const selectedId = recordId;
|
|
153
157
|
let isError = false;
|
|
154
158
|
if (STUB_MODE) {
|
|
@@ -159,7 +163,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
159
163
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
160
164
|
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] );
|
|
161
165
|
|
|
162
|
-
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
166
|
+
const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record, customPrompt);
|
|
163
167
|
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
164
168
|
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
165
169
|
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
@@ -188,7 +192,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined
|
|
195
|
+
private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
192
196
|
const selectedId = recordId;
|
|
193
197
|
let isError = false;
|
|
194
198
|
const start = +new Date();
|
|
@@ -208,7 +212,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
|
|
211
|
-
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
215
|
+
const prompt = (await this.compileGenerationFieldTemplates(record, customPrompt))[key];
|
|
212
216
|
let images;
|
|
213
217
|
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
214
218
|
isError = true;
|
|
@@ -391,7 +395,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
391
395
|
};
|
|
392
396
|
|
|
393
397
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
394
|
-
|
|
398
|
+
|
|
395
399
|
const pageInjection = {
|
|
396
400
|
file: this.componentPath('VisionAction.vue'),
|
|
397
401
|
meta: {
|
|
@@ -413,6 +417,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
413
417
|
fillPlainFields: this.options.refreshRates?.fillPlainFields || 1_000,
|
|
414
418
|
generateImages: this.options.refreshRates?.generateImages || 5_000,
|
|
415
419
|
regenerateImages: this.options.refreshRates?.regenerateImages || 5_000,
|
|
420
|
+
},
|
|
421
|
+
askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false,
|
|
422
|
+
generationPrompts: {
|
|
423
|
+
plainFieldsPrompts: this.options.fillPlainFields || {},
|
|
424
|
+
imageFieldsPrompts: this.options.fillFieldsFromImages || {},
|
|
425
|
+
imageGenerationPrompts: this.options.generateImages || {},
|
|
416
426
|
}
|
|
417
427
|
}
|
|
418
428
|
}
|
|
@@ -489,7 +499,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
489
499
|
}
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
|
-
if (this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) {
|
|
502
|
+
if ((this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) && !this.options.provideAdditionalContextForRecord) {
|
|
493
503
|
let matches: string[] = [];
|
|
494
504
|
const regex = /{{(.*?)}}/g;
|
|
495
505
|
|
|
@@ -652,12 +662,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
652
662
|
|
|
653
663
|
server.endpoint({
|
|
654
664
|
method: 'POST',
|
|
655
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
665
|
+
path: `/plugin/${this.pluginInstanceId}/get_image_generation_prompts`,
|
|
656
666
|
handler: async ({ body, headers }) => {
|
|
657
667
|
const Id = body.recordId || [];
|
|
668
|
+
const customPrompt = body.customPrompt || null;
|
|
658
669
|
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]);
|
|
659
|
-
const compiledGenerationOptions = this.compileGenerationFieldTemplates(record);
|
|
660
|
-
return
|
|
670
|
+
const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record, JSON.stringify({"prompt": customPrompt}));
|
|
671
|
+
return compiledGenerationOptions;
|
|
661
672
|
}
|
|
662
673
|
});
|
|
663
674
|
|
|
@@ -679,10 +690,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
679
690
|
method: 'POST',
|
|
680
691
|
path: `/plugin/${this.pluginInstanceId}/create-job`,
|
|
681
692
|
handler: async ({ body, adminUser, headers }) => {
|
|
682
|
-
const { actionType, recordId } = body;
|
|
693
|
+
const { actionType, recordId, customPrompt } = body;
|
|
683
694
|
const jobId = randomUUID();
|
|
684
695
|
jobs.set(jobId, { status: "in_progress" });
|
|
685
|
-
|
|
686
696
|
if (!actionType) {
|
|
687
697
|
jobs.set(jobId, { status: "failed", error: "Missing action type" });
|
|
688
698
|
//return { error: "Missing action type" };
|
|
@@ -693,13 +703,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
693
703
|
} else {
|
|
694
704
|
switch(actionType) {
|
|
695
705
|
case 'generate_images':
|
|
696
|
-
this.initialImageGenerate(jobId, recordId, adminUser, headers);
|
|
706
|
+
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
|
|
697
707
|
break;
|
|
698
708
|
case 'analyze_no_images':
|
|
699
|
-
this.analyzeNoImages(jobId, recordId, adminUser, headers);
|
|
709
|
+
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
|
|
700
710
|
break;
|
|
701
711
|
case 'analyze':
|
|
702
|
-
this.analyze_image(jobId, recordId, adminUser, headers);
|
|
712
|
+
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
|
|
703
713
|
break;
|
|
704
714
|
case 'regenerate_images':
|
|
705
715
|
if (!body.prompt || !body.fieldName) {
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ImageVisionAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth";
|
|
2
|
-
|
|
1
|
+
import AdminForth, { ImageVisionAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth";
|
|
3
2
|
|
|
4
3
|
export interface PluginOptions {
|
|
5
4
|
/**
|
|
@@ -109,4 +108,15 @@ export interface PluginOptions {
|
|
|
109
108
|
ok: boolean;
|
|
110
109
|
error?: undefined;
|
|
111
110
|
}>
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Custom message for the context shown to the user when performing the action
|
|
114
|
+
*/
|
|
115
|
+
provideAdditionalContextForRecord?: ({record, adminUser, resource}: {
|
|
116
|
+
record: any;
|
|
117
|
+
adminUser: any;
|
|
118
|
+
resource: any;
|
|
119
|
+
}) => Record<string, any> | Promise<Record<string, any>>;
|
|
120
|
+
|
|
121
|
+
askConfirmationBeforeGenerating?: boolean;
|
|
112
122
|
}
|