@adminforth/bulk-ai-flow 1.18.12 → 1.20.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/VisionAction.vue +64 -24
- package/dist/custom/VisionAction.vue +64 -24
- package/dist/index.js +40 -12
- package/index.ts +44 -9
- package/package.json +1 -1
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 95,557 bytes received 172 bytes 191,458.00 bytes/sec
|
|
17
|
+
total size is 94,921 speedup is 0.99
|
package/custom/VisionAction.vue
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex items-end justify-start gap-2 cursor-pointer">
|
|
3
3
|
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
|
|
4
|
-
AI
|
|
4
|
+
{{t('AI')}}
|
|
5
5
|
</div>
|
|
6
6
|
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
<Dialog
|
|
9
9
|
ref="confirmDialog"
|
|
10
|
-
header="Bulk AI
|
|
10
|
+
header="Bulk AI Generation"
|
|
11
11
|
class="[scrollbar-gutter:stable] !max-w-full w-fit h-fit"
|
|
12
12
|
:class="popupMode === 'generation' ? 'lg:w-[1600px] !lg:max-w-[1600px]'
|
|
13
13
|
: popupMode === 'settings' ? 'lg:w-[1000px] !lg:max-w-[1000px]'
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
:beforeCloseFunction="closeDialog"
|
|
16
16
|
:closable="false"
|
|
17
17
|
:askForCloseConfirmation="popupMode === 'generation' ? true : false"
|
|
18
|
-
closeConfirmationText="Are you sure you want to close without saving?"
|
|
18
|
+
:closeConfirmationText="t('Are you sure you want to close without saving?')"
|
|
19
19
|
:buttons="popupMode === 'generation' ? [
|
|
20
20
|
{
|
|
21
|
-
label: checkedCount > 1 ? 'Save fields' : 'Save field',
|
|
21
|
+
label: checkedCount > 1 ? t('Save fields') : t('Save field'),
|
|
22
22
|
options: {
|
|
23
23
|
disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages,
|
|
24
24
|
loader: isLoading, class: 'w-fit'
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
onclick: async (dialog) => { await saveData(); dialog.hide(); }
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
label: 'Cancel',
|
|
29
|
+
label: t('Cancel'),
|
|
30
30
|
options: {
|
|
31
31
|
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'
|
|
32
32
|
},
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
] : popupMode === 'settings' ? [
|
|
36
36
|
{
|
|
37
|
-
label: 'Save settings',
|
|
37
|
+
label: t('Save settings'),
|
|
38
38
|
options: {
|
|
39
39
|
class: 'w-fit'
|
|
40
40
|
},
|
|
@@ -42,18 +42,32 @@
|
|
|
42
42
|
},
|
|
43
43
|
] :
|
|
44
44
|
[
|
|
45
|
+
// {
|
|
46
|
+
// label: t('Edit prompts'),
|
|
47
|
+
// options: {
|
|
48
|
+
// class: 'w-fit ml-auto'
|
|
49
|
+
// },
|
|
50
|
+
// onclick: (dialog) => { clickSettingsButton(); }
|
|
51
|
+
// },
|
|
45
52
|
{
|
|
46
|
-
label: '
|
|
53
|
+
label: t('Cancel'),
|
|
47
54
|
options: {
|
|
48
|
-
class: 'w-
|
|
55
|
+
class: 'w-2/5 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'
|
|
49
56
|
},
|
|
50
|
-
onclick: (dialog) =>
|
|
57
|
+
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
51
58
|
},
|
|
59
|
+
{
|
|
60
|
+
label: t('Start generation'),
|
|
61
|
+
options: {
|
|
62
|
+
class: 'w-3/5 px-5 py-2.5 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'
|
|
63
|
+
},
|
|
64
|
+
onclick: (dialog) => { runAiActions(); }
|
|
65
|
+
}
|
|
52
66
|
]"
|
|
53
67
|
:click-to-close-outside="false"
|
|
54
68
|
>
|
|
55
|
-
<div class="
|
|
56
|
-
<div v-if="records && props.checkboxes.length && popupMode === 'generation'" class="w-full overflow-x-auto">
|
|
69
|
+
<div class="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">
|
|
70
|
+
<div v-if="records && props.checkboxes.length && popupMode === 'generation'" class="[scrollbar-gutter:stable] w-full overflow-x-auto">
|
|
57
71
|
<VisionTable
|
|
58
72
|
:checkbox="props.checkboxes"
|
|
59
73
|
:records="records"
|
|
@@ -65,6 +79,8 @@
|
|
|
65
79
|
:tableColumnsIndexes="tableColumnsIndexes"
|
|
66
80
|
:selected="selected"
|
|
67
81
|
:oldData="oldData"
|
|
82
|
+
:isError="isError"
|
|
83
|
+
:errorMessage="errorMessage"
|
|
68
84
|
:isAiResponseReceivedAnalizeImage="isAiResponseReceivedAnalizeImage"
|
|
69
85
|
:isAiResponseReceivedAnalizeNoImage="isAiResponseReceivedAnalizeNoImage"
|
|
70
86
|
:isAiResponseReceivedImage="isAiResponseReceivedImage"
|
|
@@ -110,7 +126,7 @@
|
|
|
110
126
|
}}</p>
|
|
111
127
|
<div class="grid grid-cols-2 gap-4">
|
|
112
128
|
<div v-for="(prompt, promptKey) in promptsCategory" :key="promptKey">
|
|
113
|
-
{{ formatLabel(promptKey) }} prompt:
|
|
129
|
+
{{ formatLabel(promptKey) }} {{ t('prompt') }}:
|
|
114
130
|
<Textarea
|
|
115
131
|
v-model="generationPrompts[key][promptKey]"
|
|
116
132
|
class="w-full h-64 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
|
|
@@ -120,10 +136,27 @@
|
|
|
120
136
|
</div>
|
|
121
137
|
</div>
|
|
122
138
|
</div>
|
|
123
|
-
<div v-else class="flex flex-col gap-2">
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
<div v-else class="flex flex-col gap-2 mt-2 w-full h-full">
|
|
140
|
+
<div class="flex items-center justify-between mb-2 w-full">
|
|
141
|
+
<div class="flex items-center justify-center gap-2">
|
|
142
|
+
<IconShieldSolid class="w-6 h-6 text-lightPrimary dark:text-darkPrimary" />
|
|
143
|
+
<p class="sm:text-base text-sm">{{ t('Do not overwrite existing values') }}</p>
|
|
144
|
+
<Tooltip>
|
|
145
|
+
<IconInfoCircleSolid class="w-5 h-5 me-2 text-lightPrimary dark:text-darkPrimary"/>
|
|
146
|
+
<template #tooltip>
|
|
147
|
+
<p class="max-w-64">{{ t('When enabled, the AI will skip generating content for fields that already have data. This helps to preserve existing information and avoid overwriting valuable content.') }}</p>
|
|
148
|
+
</template>
|
|
149
|
+
</Tooltip>
|
|
150
|
+
</div>
|
|
151
|
+
<Toggle
|
|
152
|
+
v-model="skipFilledFieldsForGeneration"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
<div :class="skipFilledFieldsForGeneration === false ? 'opacity-100' : 'opacity-0'" class="flex items-center text-yellow-800 bg-yellow-100 p-2 rounded-md border border-yellow-300">
|
|
156
|
+
<IconExclamationTriangle class="w-6 h-6 me-2"/>
|
|
157
|
+
<p class="sm:text-base text-sm">{{ t('Warning: Existing values will be overwritten.') }}</p>
|
|
158
|
+
</div>
|
|
159
|
+
<p class="w-fit flex justify-start text-lightPrimary dark:text-lightPrimary hover:underline cursor-pointer" @click="clickSettingsButton()">{{ t('Configure prompts') }}</p>
|
|
127
160
|
</div>
|
|
128
161
|
</div>
|
|
129
162
|
</Dialog>
|
|
@@ -132,12 +165,15 @@
|
|
|
132
165
|
<script lang="ts" setup>
|
|
133
166
|
import { callAdminForthApi } from '@/utils';
|
|
134
167
|
import { Ref, ref, watch } from 'vue'
|
|
135
|
-
import { Dialog, Button, Textarea } from '@/afcl';
|
|
168
|
+
import { Dialog, Button, Textarea, Toggle, Tooltip } from '@/afcl';
|
|
136
169
|
import VisionTable from './VisionTable.vue'
|
|
137
170
|
import adminforth from '@/adminforth';
|
|
138
171
|
import { useI18n } from 'vue-i18n';
|
|
139
172
|
import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
|
|
140
173
|
import { useCoreStore } from '@/stores/core';
|
|
174
|
+
import { IconShieldSolid, IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
175
|
+
import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
|
|
176
|
+
|
|
141
177
|
|
|
142
178
|
const coreStore = useCoreStore();
|
|
143
179
|
|
|
@@ -147,13 +183,8 @@ const props = defineProps<{
|
|
|
147
183
|
meta: any,
|
|
148
184
|
resource: AdminForthResourceCommon,
|
|
149
185
|
adminUser: AdminUser,
|
|
150
|
-
updateList:
|
|
151
|
-
|
|
152
|
-
required: true
|
|
153
|
-
},
|
|
154
|
-
clearCheckboxes: {
|
|
155
|
-
type: Function
|
|
156
|
-
}
|
|
186
|
+
updateList: () => any,
|
|
187
|
+
clearCheckboxes?: () => any,
|
|
157
188
|
}>();
|
|
158
189
|
|
|
159
190
|
defineExpose({
|
|
@@ -205,6 +236,7 @@ const generationPrompts = ref<any>({});
|
|
|
205
236
|
const isDataSaved = ref(false);
|
|
206
237
|
|
|
207
238
|
const regeneratingFieldsStatus = ref<Record<string, Record<string, boolean>>>({});
|
|
239
|
+
const skipFilledFieldsForGeneration = ref<boolean>(true);
|
|
208
240
|
|
|
209
241
|
const openDialog = async () => {
|
|
210
242
|
window.addEventListener('beforeunload', beforeUnloadHandler);
|
|
@@ -326,6 +358,13 @@ function fillCarouselSaveImages() {
|
|
|
326
358
|
|
|
327
359
|
|
|
328
360
|
function formatLabel(str) {
|
|
361
|
+
const labelsMap = props.meta?.columnLabels || {};
|
|
362
|
+
let labelFromMeta = labelsMap[str];
|
|
363
|
+
if (!labelFromMeta) {
|
|
364
|
+
const match = Object.keys(labelsMap).find(k => k.toLowerCase() === String(str).toLowerCase());
|
|
365
|
+
if (match) labelFromMeta = labelsMap[match];
|
|
366
|
+
}
|
|
367
|
+
if (labelFromMeta) return labelFromMeta;
|
|
329
368
|
return str
|
|
330
369
|
.split('_')
|
|
331
370
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
@@ -629,6 +668,7 @@ async function runAiAction({
|
|
|
629
668
|
actionType: actionType,
|
|
630
669
|
recordId: checkbox,
|
|
631
670
|
...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
|
|
671
|
+
filterFilledFields: skipFilledFieldsForGeneration.value,
|
|
632
672
|
},
|
|
633
673
|
silentError: true,
|
|
634
674
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex items-end justify-start gap-2 cursor-pointer">
|
|
3
3
|
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
|
|
4
|
-
AI
|
|
4
|
+
{{t('AI')}}
|
|
5
5
|
</div>
|
|
6
6
|
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
<Dialog
|
|
9
9
|
ref="confirmDialog"
|
|
10
|
-
header="Bulk AI
|
|
10
|
+
header="Bulk AI Generation"
|
|
11
11
|
class="[scrollbar-gutter:stable] !max-w-full w-fit h-fit"
|
|
12
12
|
:class="popupMode === 'generation' ? 'lg:w-[1600px] !lg:max-w-[1600px]'
|
|
13
13
|
: popupMode === 'settings' ? 'lg:w-[1000px] !lg:max-w-[1000px]'
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
:beforeCloseFunction="closeDialog"
|
|
16
16
|
:closable="false"
|
|
17
17
|
:askForCloseConfirmation="popupMode === 'generation' ? true : false"
|
|
18
|
-
closeConfirmationText="Are you sure you want to close without saving?"
|
|
18
|
+
:closeConfirmationText="t('Are you sure you want to close without saving?')"
|
|
19
19
|
:buttons="popupMode === 'generation' ? [
|
|
20
20
|
{
|
|
21
|
-
label: checkedCount > 1 ? 'Save fields' : 'Save field',
|
|
21
|
+
label: checkedCount > 1 ? t('Save fields') : t('Save field'),
|
|
22
22
|
options: {
|
|
23
23
|
disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages,
|
|
24
24
|
loader: isLoading, class: 'w-fit'
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
onclick: async (dialog) => { await saveData(); dialog.hide(); }
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
label: 'Cancel',
|
|
29
|
+
label: t('Cancel'),
|
|
30
30
|
options: {
|
|
31
31
|
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'
|
|
32
32
|
},
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
] : popupMode === 'settings' ? [
|
|
36
36
|
{
|
|
37
|
-
label: 'Save settings',
|
|
37
|
+
label: t('Save settings'),
|
|
38
38
|
options: {
|
|
39
39
|
class: 'w-fit'
|
|
40
40
|
},
|
|
@@ -42,18 +42,32 @@
|
|
|
42
42
|
},
|
|
43
43
|
] :
|
|
44
44
|
[
|
|
45
|
+
// {
|
|
46
|
+
// label: t('Edit prompts'),
|
|
47
|
+
// options: {
|
|
48
|
+
// class: 'w-fit ml-auto'
|
|
49
|
+
// },
|
|
50
|
+
// onclick: (dialog) => { clickSettingsButton(); }
|
|
51
|
+
// },
|
|
45
52
|
{
|
|
46
|
-
label: '
|
|
53
|
+
label: t('Cancel'),
|
|
47
54
|
options: {
|
|
48
|
-
class: 'w-
|
|
55
|
+
class: 'w-2/5 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'
|
|
49
56
|
},
|
|
50
|
-
onclick: (dialog) =>
|
|
57
|
+
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
51
58
|
},
|
|
59
|
+
{
|
|
60
|
+
label: t('Start generation'),
|
|
61
|
+
options: {
|
|
62
|
+
class: 'w-3/5 px-5 py-2.5 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'
|
|
63
|
+
},
|
|
64
|
+
onclick: (dialog) => { runAiActions(); }
|
|
65
|
+
}
|
|
52
66
|
]"
|
|
53
67
|
:click-to-close-outside="false"
|
|
54
68
|
>
|
|
55
|
-
<div class="
|
|
56
|
-
<div v-if="records && props.checkboxes.length && popupMode === 'generation'" class="w-full overflow-x-auto">
|
|
69
|
+
<div class="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">
|
|
70
|
+
<div v-if="records && props.checkboxes.length && popupMode === 'generation'" class="[scrollbar-gutter:stable] w-full overflow-x-auto">
|
|
57
71
|
<VisionTable
|
|
58
72
|
:checkbox="props.checkboxes"
|
|
59
73
|
:records="records"
|
|
@@ -65,6 +79,8 @@
|
|
|
65
79
|
:tableColumnsIndexes="tableColumnsIndexes"
|
|
66
80
|
:selected="selected"
|
|
67
81
|
:oldData="oldData"
|
|
82
|
+
:isError="isError"
|
|
83
|
+
:errorMessage="errorMessage"
|
|
68
84
|
:isAiResponseReceivedAnalizeImage="isAiResponseReceivedAnalizeImage"
|
|
69
85
|
:isAiResponseReceivedAnalizeNoImage="isAiResponseReceivedAnalizeNoImage"
|
|
70
86
|
:isAiResponseReceivedImage="isAiResponseReceivedImage"
|
|
@@ -110,7 +126,7 @@
|
|
|
110
126
|
}}</p>
|
|
111
127
|
<div class="grid grid-cols-2 gap-4">
|
|
112
128
|
<div v-for="(prompt, promptKey) in promptsCategory" :key="promptKey">
|
|
113
|
-
{{ formatLabel(promptKey) }} prompt:
|
|
129
|
+
{{ formatLabel(promptKey) }} {{ t('prompt') }}:
|
|
114
130
|
<Textarea
|
|
115
131
|
v-model="generationPrompts[key][promptKey]"
|
|
116
132
|
class="w-full h-64 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
|
|
@@ -120,10 +136,27 @@
|
|
|
120
136
|
</div>
|
|
121
137
|
</div>
|
|
122
138
|
</div>
|
|
123
|
-
<div v-else class="flex flex-col gap-2">
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
<div v-else class="flex flex-col gap-2 mt-2 w-full h-full">
|
|
140
|
+
<div class="flex items-center justify-between mb-2 w-full">
|
|
141
|
+
<div class="flex items-center justify-center gap-2">
|
|
142
|
+
<IconShieldSolid class="w-6 h-6 text-lightPrimary dark:text-darkPrimary" />
|
|
143
|
+
<p class="sm:text-base text-sm">{{ t('Do not overwrite existing values') }}</p>
|
|
144
|
+
<Tooltip>
|
|
145
|
+
<IconInfoCircleSolid class="w-5 h-5 me-2 text-lightPrimary dark:text-darkPrimary"/>
|
|
146
|
+
<template #tooltip>
|
|
147
|
+
<p class="max-w-64">{{ t('When enabled, the AI will skip generating content for fields that already have data. This helps to preserve existing information and avoid overwriting valuable content.') }}</p>
|
|
148
|
+
</template>
|
|
149
|
+
</Tooltip>
|
|
150
|
+
</div>
|
|
151
|
+
<Toggle
|
|
152
|
+
v-model="skipFilledFieldsForGeneration"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
<div :class="skipFilledFieldsForGeneration === false ? 'opacity-100' : 'opacity-0'" class="flex items-center text-yellow-800 bg-yellow-100 p-2 rounded-md border border-yellow-300">
|
|
156
|
+
<IconExclamationTriangle class="w-6 h-6 me-2"/>
|
|
157
|
+
<p class="sm:text-base text-sm">{{ t('Warning: Existing values will be overwritten.') }}</p>
|
|
158
|
+
</div>
|
|
159
|
+
<p class="w-fit flex justify-start text-lightPrimary dark:text-lightPrimary hover:underline cursor-pointer" @click="clickSettingsButton()">{{ t('Configure prompts') }}</p>
|
|
127
160
|
</div>
|
|
128
161
|
</div>
|
|
129
162
|
</Dialog>
|
|
@@ -132,12 +165,15 @@
|
|
|
132
165
|
<script lang="ts" setup>
|
|
133
166
|
import { callAdminForthApi } from '@/utils';
|
|
134
167
|
import { Ref, ref, watch } from 'vue'
|
|
135
|
-
import { Dialog, Button, Textarea } from '@/afcl';
|
|
168
|
+
import { Dialog, Button, Textarea, Toggle, Tooltip } from '@/afcl';
|
|
136
169
|
import VisionTable from './VisionTable.vue'
|
|
137
170
|
import adminforth from '@/adminforth';
|
|
138
171
|
import { useI18n } from 'vue-i18n';
|
|
139
172
|
import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
|
|
140
173
|
import { useCoreStore } from '@/stores/core';
|
|
174
|
+
import { IconShieldSolid, IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
175
|
+
import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
|
|
176
|
+
|
|
141
177
|
|
|
142
178
|
const coreStore = useCoreStore();
|
|
143
179
|
|
|
@@ -147,13 +183,8 @@ const props = defineProps<{
|
|
|
147
183
|
meta: any,
|
|
148
184
|
resource: AdminForthResourceCommon,
|
|
149
185
|
adminUser: AdminUser,
|
|
150
|
-
updateList:
|
|
151
|
-
|
|
152
|
-
required: true
|
|
153
|
-
},
|
|
154
|
-
clearCheckboxes: {
|
|
155
|
-
type: Function
|
|
156
|
-
}
|
|
186
|
+
updateList: () => any,
|
|
187
|
+
clearCheckboxes?: () => any,
|
|
157
188
|
}>();
|
|
158
189
|
|
|
159
190
|
defineExpose({
|
|
@@ -205,6 +236,7 @@ const generationPrompts = ref<any>({});
|
|
|
205
236
|
const isDataSaved = ref(false);
|
|
206
237
|
|
|
207
238
|
const regeneratingFieldsStatus = ref<Record<string, Record<string, boolean>>>({});
|
|
239
|
+
const skipFilledFieldsForGeneration = ref<boolean>(true);
|
|
208
240
|
|
|
209
241
|
const openDialog = async () => {
|
|
210
242
|
window.addEventListener('beforeunload', beforeUnloadHandler);
|
|
@@ -326,6 +358,13 @@ function fillCarouselSaveImages() {
|
|
|
326
358
|
|
|
327
359
|
|
|
328
360
|
function formatLabel(str) {
|
|
361
|
+
const labelsMap = props.meta?.columnLabels || {};
|
|
362
|
+
let labelFromMeta = labelsMap[str];
|
|
363
|
+
if (!labelFromMeta) {
|
|
364
|
+
const match = Object.keys(labelsMap).find(k => k.toLowerCase() === String(str).toLowerCase());
|
|
365
|
+
if (match) labelFromMeta = labelsMap[match];
|
|
366
|
+
}
|
|
367
|
+
if (labelFromMeta) return labelFromMeta;
|
|
329
368
|
return str
|
|
330
369
|
.split('_')
|
|
331
370
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
@@ -629,6 +668,7 @@ async function runAiAction({
|
|
|
629
668
|
actionType: actionType,
|
|
630
669
|
recordId: checkbox,
|
|
631
670
|
...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
|
|
671
|
+
filterFilledFields: skipFilledFieldsForGeneration.value,
|
|
632
672
|
},
|
|
633
673
|
silentError: true,
|
|
634
674
|
});
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,18 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
59
59
|
return yield this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt));
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
removeFromPromptFilledFields(compiledOutputFields, record) {
|
|
63
|
+
const newCompiledOutputFields = {};
|
|
64
|
+
for (const [key, value] of Object.entries(record)) {
|
|
65
|
+
if (compiledOutputFields[key]) {
|
|
66
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
newCompiledOutputFields[key] = compiledOutputFields[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return newCompiledOutputFields;
|
|
73
|
+
}
|
|
62
74
|
checkRateLimit(field, fieldNameRateLimit, headers) {
|
|
63
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
64
76
|
if (fieldNameRateLimit) {
|
|
@@ -93,8 +105,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
93
105
|
Do NOT wrap in \`\`\` or \`\`\`json. Do not add any extra text. Do not return prompt in response`;
|
|
94
106
|
return prompt;
|
|
95
107
|
}
|
|
96
|
-
analyze_image(
|
|
97
|
-
return __awaiter(this,
|
|
108
|
+
analyze_image(jobId_1, recordId_1, adminUser_1, headers_1, customPrompt_1) {
|
|
109
|
+
return __awaiter(this, arguments, void 0, function* (jobId, recordId, adminUser, headers, customPrompt, filterFilledFields = true) {
|
|
98
110
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
99
111
|
const selectedId = recordId;
|
|
100
112
|
let isError = false;
|
|
@@ -129,7 +141,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
129
141
|
}
|
|
130
142
|
//create prompt for OpenAI
|
|
131
143
|
const compiledOutputFields = yield this.compileOutputFieldsTemplates(record, customPrompt);
|
|
132
|
-
const
|
|
144
|
+
const filteredCompiledOutputFields = filterFilledFields ? this.removeFromPromptFilledFields(compiledOutputFields, record) : compiledOutputFields;
|
|
145
|
+
if (Object.keys(filteredCompiledOutputFields).length === 0) {
|
|
146
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
147
|
+
return { ok: true };
|
|
148
|
+
}
|
|
149
|
+
const prompt = this.getPromptForImageAnalysis(filteredCompiledOutputFields);
|
|
133
150
|
//send prompt to OpenAI and get response
|
|
134
151
|
let chatResponse;
|
|
135
152
|
try {
|
|
@@ -171,8 +188,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
171
188
|
}
|
|
172
189
|
});
|
|
173
190
|
}
|
|
174
|
-
analyzeNoImages(
|
|
175
|
-
return __awaiter(this,
|
|
191
|
+
analyzeNoImages(jobId_1, recordId_1, adminUser_1, headers_1, customPrompt_1) {
|
|
192
|
+
return __awaiter(this, arguments, void 0, function* (jobId, recordId, adminUser, headers, customPrompt, filterFilledFields = true) {
|
|
176
193
|
const selectedId = recordId;
|
|
177
194
|
let isError = false;
|
|
178
195
|
if (STUB_MODE) {
|
|
@@ -185,7 +202,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
185
202
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
186
203
|
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)]);
|
|
187
204
|
const compiledOutputFields = yield this.compileOutputFieldsTemplatesNoImage(record, customPrompt);
|
|
188
|
-
const
|
|
205
|
+
const filteredCompiledOutputFields = filterFilledFields ? this.removeFromPromptFilledFields(compiledOutputFields, record) : compiledOutputFields;
|
|
206
|
+
if (Object.keys(filteredCompiledOutputFields).length === 0) {
|
|
207
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
208
|
+
return { ok: true };
|
|
209
|
+
}
|
|
210
|
+
const prompt = this.getPromptForPlainFields(filteredCompiledOutputFields);
|
|
189
211
|
//send prompt to OpenAI and get response
|
|
190
212
|
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
191
213
|
let resp;
|
|
@@ -218,8 +240,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
218
240
|
}
|
|
219
241
|
});
|
|
220
242
|
}
|
|
221
|
-
initialImageGenerate(
|
|
222
|
-
return __awaiter(this,
|
|
243
|
+
initialImageGenerate(jobId_1, recordId_1, adminUser_1, headers_1, customPrompt_1) {
|
|
244
|
+
return __awaiter(this, arguments, void 0, function* (jobId, recordId, adminUser, headers, customPrompt, filterFilledFields = true) {
|
|
223
245
|
var _a, _b;
|
|
224
246
|
const selectedId = recordId;
|
|
225
247
|
let isError = false;
|
|
@@ -242,6 +264,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
242
264
|
}
|
|
243
265
|
}
|
|
244
266
|
const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
267
|
+
if (record[key] && filterFilledFields) {
|
|
268
|
+
return { key, images: [] };
|
|
269
|
+
}
|
|
245
270
|
const prompt = (yield this.compileGenerationFieldTemplates(record, customPrompt))[key];
|
|
246
271
|
let images;
|
|
247
272
|
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
@@ -541,6 +566,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
541
566
|
}
|
|
542
567
|
const outputFields = Object.assign(Object.assign(Object.assign({}, this.options.fillFieldsFromImages), this.options.fillPlainFields), (this.options.generateImages || {}));
|
|
543
568
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
569
|
+
// Map of column technical names to their display labels from resource config
|
|
570
|
+
const columnLabels = Object.fromEntries((this.resourceConfig.columns || []).map((c) => [c.name, c.label || c.name]));
|
|
544
571
|
const pageInjection = {
|
|
545
572
|
file: this.componentPath('VisionAction.vue'),
|
|
546
573
|
meta: {
|
|
@@ -548,6 +575,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
548
575
|
outputFields: outputFields,
|
|
549
576
|
actionName: this.options.actionName,
|
|
550
577
|
columnEnums: columnEnums,
|
|
578
|
+
columnLabels: columnLabels,
|
|
551
579
|
outputImageFields: outputImageFields,
|
|
552
580
|
outputFieldsForAnalizeFromImages: outputFieldsForAnalizeFromImages,
|
|
553
581
|
outputPlainFields: outputPlainFields,
|
|
@@ -823,7 +851,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
823
851
|
method: 'POST',
|
|
824
852
|
path: `/plugin/${this.pluginInstanceId}/create-job`,
|
|
825
853
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
826
|
-
const { actionType, recordId, customPrompt } = body;
|
|
854
|
+
const { actionType, recordId, customPrompt, filterFilledFields } = body;
|
|
827
855
|
const jobId = randomUUID();
|
|
828
856
|
jobs.set(jobId, { status: "in_progress" });
|
|
829
857
|
if (!actionType) {
|
|
@@ -837,13 +865,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
837
865
|
else {
|
|
838
866
|
switch (actionType) {
|
|
839
867
|
case 'generate_images':
|
|
840
|
-
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
|
|
868
|
+
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
841
869
|
break;
|
|
842
870
|
case 'analyze_no_images':
|
|
843
|
-
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
|
|
871
|
+
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
844
872
|
break;
|
|
845
873
|
case 'analyze':
|
|
846
|
-
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
|
|
874
|
+
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
847
875
|
break;
|
|
848
876
|
case 'regenerate_images':
|
|
849
877
|
if (!body.prompt || !body.fieldName) {
|
package/index.ts
CHANGED
|
@@ -59,6 +59,19 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
59
59
|
return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
private removeFromPromptFilledFields(compiledOutputFields: Record<string, string>, record: Record<string, any>): Record<string, string> {
|
|
63
|
+
const newCompiledOutputFields: Record<string, string> = {};
|
|
64
|
+
for (const [key, value] of Object.entries(record)) {
|
|
65
|
+
if (compiledOutputFields[key]) {
|
|
66
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
newCompiledOutputFields[key] = compiledOutputFields[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return newCompiledOutputFields;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
private async checkRateLimit(field: string, fieldNameRateLimit: string | undefined, headers: Record<string, string | string[] | undefined>): Promise<void | { error?: string; }> {
|
|
63
76
|
if (fieldNameRateLimit) {
|
|
64
77
|
// rate limit
|
|
@@ -94,7 +107,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
94
107
|
return prompt;
|
|
95
108
|
}
|
|
96
109
|
|
|
97
|
-
private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
110
|
+
private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string, filterFilledFields: boolean = true) {
|
|
98
111
|
const selectedId = recordId;
|
|
99
112
|
let isError = false;
|
|
100
113
|
// Fetch the record using the provided ID
|
|
@@ -125,8 +138,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
125
138
|
}
|
|
126
139
|
//create prompt for OpenAI
|
|
127
140
|
const compiledOutputFields = await this.compileOutputFieldsTemplates(record, customPrompt);
|
|
128
|
-
const
|
|
141
|
+
const filteredCompiledOutputFields = filterFilledFields ? this.removeFromPromptFilledFields(compiledOutputFields, record) : compiledOutputFields;
|
|
142
|
+
|
|
143
|
+
if (Object.keys(filteredCompiledOutputFields).length === 0) {
|
|
144
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
145
|
+
return { ok: true };
|
|
146
|
+
}
|
|
129
147
|
|
|
148
|
+
const prompt = this.getPromptForImageAnalysis(filteredCompiledOutputFields);
|
|
149
|
+
|
|
130
150
|
//send prompt to OpenAI and get response
|
|
131
151
|
let chatResponse;
|
|
132
152
|
try {
|
|
@@ -168,7 +188,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
168
188
|
|
|
169
189
|
}
|
|
170
190
|
|
|
171
|
-
private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
191
|
+
private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string, filterFilledFields: boolean = true) {
|
|
172
192
|
const selectedId = recordId;
|
|
173
193
|
let isError = false;
|
|
174
194
|
if (STUB_MODE) {
|
|
@@ -181,7 +201,14 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
181
201
|
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] );
|
|
182
202
|
|
|
183
203
|
const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record, customPrompt);
|
|
184
|
-
|
|
204
|
+
|
|
205
|
+
const filteredCompiledOutputFields = filterFilledFields ? this.removeFromPromptFilledFields(compiledOutputFields, record) : compiledOutputFields;
|
|
206
|
+
|
|
207
|
+
if (Object.keys(filteredCompiledOutputFields).length === 0) {
|
|
208
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
209
|
+
return { ok: true };
|
|
210
|
+
}
|
|
211
|
+
const prompt = this.getPromptForPlainFields(filteredCompiledOutputFields);
|
|
185
212
|
//send prompt to OpenAI and get response
|
|
186
213
|
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
187
214
|
let resp: any;
|
|
@@ -212,7 +239,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
212
239
|
}
|
|
213
240
|
}
|
|
214
241
|
|
|
215
|
-
private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string) {
|
|
242
|
+
private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>, customPrompt? : string, filterFilledFields: boolean = true) {
|
|
216
243
|
const selectedId = recordId;
|
|
217
244
|
let isError = false;
|
|
218
245
|
const start = +new Date();
|
|
@@ -232,6 +259,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
232
259
|
}
|
|
233
260
|
}
|
|
234
261
|
const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
|
|
262
|
+
if ( record[key] && filterFilledFields ) {
|
|
263
|
+
return { key, images: [] };
|
|
264
|
+
}
|
|
235
265
|
const prompt = (await this.compileGenerationFieldTemplates(record, customPrompt))[key];
|
|
236
266
|
let images;
|
|
237
267
|
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
@@ -533,6 +563,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
533
563
|
};
|
|
534
564
|
|
|
535
565
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
566
|
+
// Map of column technical names to their display labels from resource config
|
|
567
|
+
const columnLabels: Record<string, string> = Object.fromEntries(
|
|
568
|
+
(this.resourceConfig.columns || []).map((c: any) => [c.name, c.label || c.name])
|
|
569
|
+
);
|
|
536
570
|
|
|
537
571
|
const pageInjection = {
|
|
538
572
|
file: this.componentPath('VisionAction.vue'),
|
|
@@ -541,6 +575,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
541
575
|
outputFields: outputFields,
|
|
542
576
|
actionName: this.options.actionName,
|
|
543
577
|
columnEnums: columnEnums,
|
|
578
|
+
columnLabels: columnLabels,
|
|
544
579
|
outputImageFields: outputImageFields,
|
|
545
580
|
outputFieldsForAnalizeFromImages: outputFieldsForAnalizeFromImages,
|
|
546
581
|
outputPlainFields: outputPlainFields,
|
|
@@ -837,7 +872,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
837
872
|
method: 'POST',
|
|
838
873
|
path: `/plugin/${this.pluginInstanceId}/create-job`,
|
|
839
874
|
handler: async ({ body, adminUser, headers }) => {
|
|
840
|
-
const { actionType, recordId, customPrompt } = body;
|
|
875
|
+
const { actionType, recordId, customPrompt, filterFilledFields } = body;
|
|
841
876
|
const jobId = randomUUID();
|
|
842
877
|
jobs.set(jobId, { status: "in_progress" });
|
|
843
878
|
if (!actionType) {
|
|
@@ -850,13 +885,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
850
885
|
} else {
|
|
851
886
|
switch(actionType) {
|
|
852
887
|
case 'generate_images':
|
|
853
|
-
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
|
|
888
|
+
this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
854
889
|
break;
|
|
855
890
|
case 'analyze_no_images':
|
|
856
|
-
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
|
|
891
|
+
this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
857
892
|
break;
|
|
858
893
|
case 'analyze':
|
|
859
|
-
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
|
|
894
|
+
this.analyze_image(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
|
|
860
895
|
break;
|
|
861
896
|
case 'regenerate_images':
|
|
862
897
|
if (!body.prompt || !body.fieldName) {
|