@adminforth/bulk-ai-flow 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build.log CHANGED
@@ -13,5 +13,5 @@ custom/package-lock.json
13
13
  custom/package.json
14
14
  custom/tsconfig.json
15
15
 
16
- sent 93,308 bytes received 172 bytes 186,960.00 bytes/sec
17
- total size is 92,672 speedup is 0.99
16
+ sent 95,541 bytes received 172 bytes 191,426.00 bytes/sec
17
+ total size is 94,896 speedup is 0.99
@@ -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 Flow"
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: 'Edit prompts',
53
+ label: t('Cancel'),
47
54
  options: {
48
- class: 'w-fit ml-auto'
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) => { clickSettingsButton(); }
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="[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">
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"
@@ -112,7 +126,7 @@
112
126
  }}</p>
113
127
  <div class="grid grid-cols-2 gap-4">
114
128
  <div v-for="(prompt, promptKey) in promptsCategory" :key="promptKey">
115
- {{ formatLabel(promptKey) }} prompt:
129
+ {{ formatLabel(promptKey) }} {{ t('prompt') }}:
116
130
  <Textarea
117
131
  v-model="generationPrompts[key][promptKey]"
118
132
  class="w-full h-64 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
@@ -122,10 +136,27 @@
122
136
  </div>
123
137
  </div>
124
138
  </div>
125
- <div v-else class="flex flex-col gap-2">
126
- <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">
127
- Start generation
128
- </Button>
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('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 overwrite content for fields that already have data. When off - this helps to preserve existing information and avoid overwriting valuable content.') }}</p>
148
+ </template>
149
+ </Tooltip>
150
+ </div>
151
+ <Toggle
152
+ v-model="overwriteExistingValues"
153
+ />
154
+ </div>
155
+ <div :class="overwriteExistingValues === true ? '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>
129
160
  </div>
130
161
  </div>
131
162
  </Dialog>
@@ -134,12 +165,15 @@
134
165
  <script lang="ts" setup>
135
166
  import { callAdminForthApi } from '@/utils';
136
167
  import { Ref, ref, watch } from 'vue'
137
- import { Dialog, Button, Textarea } from '@/afcl';
168
+ import { Dialog, Button, Textarea, Toggle, Tooltip } from '@/afcl';
138
169
  import VisionTable from './VisionTable.vue'
139
170
  import adminforth from '@/adminforth';
140
171
  import { useI18n } from 'vue-i18n';
141
172
  import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
142
173
  import { useCoreStore } from '@/stores/core';
174
+ import { IconShieldSolid, IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
175
+ import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
176
+
143
177
 
144
178
  const coreStore = useCoreStore();
145
179
 
@@ -202,6 +236,7 @@ const generationPrompts = ref<any>({});
202
236
  const isDataSaved = ref(false);
203
237
 
204
238
  const regeneratingFieldsStatus = ref<Record<string, Record<string, boolean>>>({});
239
+ const overwriteExistingValues = ref<boolean>(false);
205
240
 
206
241
  const openDialog = async () => {
207
242
  window.addEventListener('beforeunload', beforeUnloadHandler);
@@ -633,6 +668,7 @@ async function runAiAction({
633
668
  actionType: actionType,
634
669
  recordId: checkbox,
635
670
  ...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
671
+ filterFilledFields: !overwriteExistingValues.value,
636
672
  },
637
673
  silentError: true,
638
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 Flow"
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: 'Edit prompts',
53
+ label: t('Cancel'),
47
54
  options: {
48
- class: 'w-fit ml-auto'
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) => { clickSettingsButton(); }
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="[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">
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"
@@ -112,7 +126,7 @@
112
126
  }}</p>
113
127
  <div class="grid grid-cols-2 gap-4">
114
128
  <div v-for="(prompt, promptKey) in promptsCategory" :key="promptKey">
115
- {{ formatLabel(promptKey) }} prompt:
129
+ {{ formatLabel(promptKey) }} {{ t('prompt') }}:
116
130
  <Textarea
117
131
  v-model="generationPrompts[key][promptKey]"
118
132
  class="w-full h-64 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
@@ -122,10 +136,27 @@
122
136
  </div>
123
137
  </div>
124
138
  </div>
125
- <div v-else class="flex flex-col gap-2">
126
- <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">
127
- Start generation
128
- </Button>
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('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 overwrite content for fields that already have data. When off - this helps to preserve existing information and avoid overwriting valuable content.') }}</p>
148
+ </template>
149
+ </Tooltip>
150
+ </div>
151
+ <Toggle
152
+ v-model="overwriteExistingValues"
153
+ />
154
+ </div>
155
+ <div :class="overwriteExistingValues === true ? '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>
129
160
  </div>
130
161
  </div>
131
162
  </Dialog>
@@ -134,12 +165,15 @@
134
165
  <script lang="ts" setup>
135
166
  import { callAdminForthApi } from '@/utils';
136
167
  import { Ref, ref, watch } from 'vue'
137
- import { Dialog, Button, Textarea } from '@/afcl';
168
+ import { Dialog, Button, Textarea, Toggle, Tooltip } from '@/afcl';
138
169
  import VisionTable from './VisionTable.vue'
139
170
  import adminforth from '@/adminforth';
140
171
  import { useI18n } from 'vue-i18n';
141
172
  import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
142
173
  import { useCoreStore } from '@/stores/core';
174
+ import { IconShieldSolid, IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
175
+ import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
176
+
143
177
 
144
178
  const coreStore = useCoreStore();
145
179
 
@@ -202,6 +236,7 @@ const generationPrompts = ref<any>({});
202
236
  const isDataSaved = ref(false);
203
237
 
204
238
  const regeneratingFieldsStatus = ref<Record<string, Record<string, boolean>>>({});
239
+ const overwriteExistingValues = ref<boolean>(false);
205
240
 
206
241
  const openDialog = async () => {
207
242
  window.addEventListener('beforeunload', beforeUnloadHandler);
@@ -633,6 +668,7 @@ async function runAiAction({
633
668
  actionType: actionType,
634
669
  recordId: checkbox,
635
670
  ...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
671
+ filterFilledFields: !overwriteExistingValues.value,
636
672
  },
637
673
  silentError: true,
638
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(jobId, recordId, adminUser, headers, customPrompt) {
97
- return __awaiter(this, void 0, void 0, function* () {
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 prompt = this.getPromptForImageAnalysis(compiledOutputFields);
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(jobId, recordId, adminUser, headers, customPrompt) {
175
- return __awaiter(this, void 0, void 0, function* () {
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 prompt = this.getPromptForPlainFields(compiledOutputFields);
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(jobId, recordId, adminUser, headers, customPrompt) {
222
- return __awaiter(this, void 0, void 0, function* () {
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) {
@@ -826,7 +851,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
826
851
  method: 'POST',
827
852
  path: `/plugin/${this.pluginInstanceId}/create-job`,
828
853
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
829
- const { actionType, recordId, customPrompt } = body;
854
+ const { actionType, recordId, customPrompt, filterFilledFields } = body;
830
855
  const jobId = randomUUID();
831
856
  jobs.set(jobId, { status: "in_progress" });
832
857
  if (!actionType) {
@@ -840,13 +865,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
840
865
  else {
841
866
  switch (actionType) {
842
867
  case 'generate_images':
843
- this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
868
+ this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
844
869
  break;
845
870
  case 'analyze_no_images':
846
- this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
871
+ this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
847
872
  break;
848
873
  case 'analyze':
849
- this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
874
+ this.analyze_image(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
850
875
  break;
851
876
  case 'regenerate_images':
852
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 prompt = this.getPromptForImageAnalysis(compiledOutputFields);
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
- const prompt = this.getPromptForPlainFields(compiledOutputFields);
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) {
@@ -842,7 +872,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
842
872
  method: 'POST',
843
873
  path: `/plugin/${this.pluginInstanceId}/create-job`,
844
874
  handler: async ({ body, adminUser, headers }) => {
845
- const { actionType, recordId, customPrompt } = body;
875
+ const { actionType, recordId, customPrompt, filterFilledFields } = body;
846
876
  const jobId = randomUUID();
847
877
  jobs.set(jobId, { status: "in_progress" });
848
878
  if (!actionType) {
@@ -855,13 +885,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
855
885
  } else {
856
886
  switch(actionType) {
857
887
  case 'generate_images':
858
- this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt);
888
+ this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
859
889
  break;
860
890
  case 'analyze_no_images':
861
- this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt);
891
+ this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
862
892
  break;
863
893
  case 'analyze':
864
- this.analyze_image(jobId, recordId, adminUser, headers, customPrompt);
894
+ this.analyze_image(jobId, recordId, adminUser, headers, customPrompt, filterFilledFields);
865
895
  break;
866
896
  case 'regenerate_images':
867
897
  if (!body.prompt || !body.fieldName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },