@adminforth/bulk-ai-flow 1.7.4 → 1.8.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/README.md CHANGED
@@ -1 +1,7 @@
1
- Readme
1
+ # AdminForth Bulk AI Flow Plugin
2
+
3
+ <img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /> <img src="https://woodpecker.devforth.io/api/badges/3848/status.svg" alt="Build Status" /> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow"> <img src="https://img.shields.io/npm/dt/@adminforth/bulk-ai-flow" alt="npm downloads" /></a> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow"><img src="https://img.shields.io/npm/v/@adminforth/bulk-ai-flow" alt="npm version" /></a> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow">
4
+
5
+ Allows you to log changes made to table records via adminforth.
6
+
7
+ ## For usage, see [AdminForth Bulk AI Flow Documentation](https://adminforth.dev/docs/tutorial/Plugins/bulk-ai-flow/)
package/build.log CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  sending incremental file list
6
6
  custom/
7
- custom/imageGenerationCarousel.vue
7
+ custom/ImageGenerationCarousel.vue
8
+ custom/VisionAction.vue
9
+ custom/VisionTable.vue
8
10
  custom/package-lock.json
9
11
  custom/package.json
10
12
  custom/tsconfig.json
11
- custom/visionAction.vue
12
- custom/visionTable.vue
13
13
 
14
- sent 180,683 bytes received 134 bytes 361,634.00 bytes/sec
15
- total size is 180,148 speedup is 1.00
14
+ sent 182,087 bytes received 134 bytes 364,442.00 bytes/sec
15
+ total size is 181,554 speedup is 1.00
@@ -175,10 +175,6 @@
175
175
  </div>
176
176
  </div>
177
177
  </div>
178
-
179
-
180
-
181
-
182
178
  </template>
183
179
 
184
180
  <script setup lang="ts">
@@ -193,8 +189,8 @@ import { ProgressBar } from '@/afcl';
193
189
  const { t: $t } = useI18n();
194
190
 
195
191
  const prompt = ref('');
196
- const emit = defineEmits(['close', 'selectImage', 'error']);
197
- const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage']);
192
+ const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
193
+ const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex']);
198
194
  const images = ref([]);
199
195
  const loading = ref(false);
200
196
  const attachmentFiles = ref<string[]>([])
@@ -208,12 +204,14 @@ function minifyField(field: string): string {
208
204
 
209
205
  const caurosel = ref(null);
210
206
  onMounted(async () => {
211
- images.value.push((props.images || []));
207
+ for (const img of props.images || []) {
208
+ images.value.push(img);
209
+ }
212
210
  const temp = await getGenerationPrompt() || '';
213
211
  prompt.value = temp[props.fieldName];
214
212
  await nextTick();
215
213
 
216
- const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
214
+ const currentIndex = props.carouselImageIndex || 0;
217
215
  caurosel.value = new Carousel(
218
216
  document.getElementById('gallery'),
219
217
  images.value.map((img, index) => {
@@ -298,6 +296,12 @@ async function confirmImage() {
298
296
  const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
299
297
  const img = images.value[currentIndex];
300
298
 
299
+ props.images.splice(0, props.images.length);
300
+ for (const i of images.value) {
301
+ props.images.push(i);
302
+ }
303
+
304
+ emit('updateCarouselIndex', currentIndex, props.recordId, props.fieldName);
301
305
  emit('selectImage', img, props.recordId, props.fieldName);
302
306
  emit('close');
303
307
 
@@ -25,7 +25,6 @@
25
25
  <VisionTable
26
26
  :checkbox="props.checkboxes"
27
27
  :records="records"
28
- :index="0"
29
28
  :meta="props.meta"
30
29
  :images="images"
31
30
  :tableHeaders="tableHeaders"
@@ -38,6 +37,8 @@
38
37
  :primaryKey="primaryKey"
39
38
  :openGenerationCarousel="openGenerationCarousel"
40
39
  @error="handleTableError"
40
+ :carouselSaveImages="carouselSaveImages"
41
+ :carouselImageIndex="carouselImageIndex"
41
42
  />
42
43
  </div>
43
44
  <div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
@@ -52,7 +53,7 @@
52
53
  <Button
53
54
  class="w-full md:w-64"
54
55
  @click="saveData"
55
- :disabled="isLoading || checkedCount < 1 || isCriticalError"
56
+ :disabled="isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords"
56
57
  :loader="isLoading"
57
58
  >
58
59
  {{ checkedCount > 1 ? 'Save fields' : 'Save field' }}
@@ -68,13 +69,11 @@
68
69
  import { callAdminForthApi } from '@/utils';
69
70
  import { Ref, ref, watch } from 'vue'
70
71
  import { Dialog, Button } from '@/afcl';
71
- import VisionTable from './visionTable.vue'
72
+ import VisionTable from './VisionTable.vue'
72
73
  import adminforth from '@/adminforth';
73
74
  import { useI18n } from 'vue-i18n';
74
- import { useRoute } from 'vue-router';
75
75
  import { AdminUser, type AdminForthResourceCommon } from '@/types';
76
76
 
77
- const route = useRoute();
78
77
  const { t } = useI18n();
79
78
 
80
79
  const props = defineProps<{
@@ -99,11 +98,14 @@ const tableColumns = ref([]);
99
98
  const tableColumnsIndexes = ref([]);
100
99
  const customFieldNames = ref([]);
101
100
  const selected = ref<any[]>([]);
101
+ const carouselSaveImages = ref<any[]>([]);
102
+ const carouselImageIndex = ref<any[]>([]);
102
103
  const isAiResponseReceivedAnalize = ref([]);
103
104
  const isAiResponseReceivedImage = ref([]);
104
105
  const primaryKey = props.meta.primaryKey;
105
106
  const openGenerationCarousel = ref([]);
106
107
  const isLoading = ref(false);
108
+ const isFetchingRecords = ref(false);
107
109
  const isError = ref(false);
108
110
  const isCriticalError = ref(false);
109
111
  const isImageGenerationError = ref(false);
@@ -128,7 +130,7 @@ const openDialog = async () => {
128
130
  return acc;
129
131
  },{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
130
132
  }
131
- isLoading.value = true;
133
+ isFetchingRecords.value = true;
132
134
  const tasks = [];
133
135
  if (props.meta.isFieldsForAnalizeFromImages) {
134
136
  tasks.push(runAiAction({
@@ -152,7 +154,12 @@ const openDialog = async () => {
152
154
  }));
153
155
  }
154
156
  await Promise.all(tasks);
155
- isLoading.value = false;
157
+
158
+ if (props.meta.isImageGeneration) {
159
+ fillCarouselSaveImages();
160
+ }
161
+
162
+ isFetchingRecords.value = false;
156
163
  }
157
164
 
158
165
  watch(selected, (val) => {
@@ -174,6 +181,23 @@ const closeDialog = () => {
174
181
  isCriticalError.value = false;
175
182
  isImageGenerationError.value = false;
176
183
  errorMessage.value = '';
184
+ carouselSaveImages.value = [];
185
+ carouselImageIndex.value = [];
186
+ }
187
+
188
+ function fillCarouselSaveImages() {
189
+ for (const item of selected.value) {
190
+ const tempItem: any = {};
191
+ const tempItemIndex: any = {};
192
+ for (const [key, value] of Object.entries(item)) {
193
+ if (props.meta.outputImageFields?.includes(key)) {
194
+ tempItem[key] = [value];
195
+ tempItemIndex[key] = 0;
196
+ }
197
+ }
198
+ carouselSaveImages.value.push(tempItem);
199
+ carouselImageIndex.value.push(tempItemIndex);
200
+ }
177
201
  }
178
202
 
179
203
  function formatLabel(str) {
@@ -270,7 +294,6 @@ async function getRecords() {
270
294
  console.error('Failed to get records:', error);
271
295
  isError.value = true;
272
296
  errorMessage.value = `Failed to fetch records. Please, try to re-run the action.`;
273
- // Handle error appropriately
274
297
  }
275
298
  }
276
299
 
@@ -288,7 +311,6 @@ async function getImages() {
288
311
  console.error('Failed to get images:', error);
289
312
  isError.value = true;
290
313
  errorMessage.value = `Failed to fetch images. Please, try to re-run the action.`;
291
- // Handle error appropriately
292
314
  }
293
315
  }
294
316
 
@@ -73,7 +73,7 @@
73
73
  <Input
74
74
  type="number"
75
75
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
76
- class="w-full "
76
+ class="w-full min-w-[80px]"
77
77
  :fullWidth="true"
78
78
  />
79
79
  </div>
@@ -91,13 +91,15 @@
91
91
  <div>
92
92
  <GenerationCarousel
93
93
  v-if="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
94
- :images="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
94
+ :images="carouselSaveImages[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
95
95
  :recordId="item[primaryKey]"
96
96
  :meta="props.meta"
97
97
  :fieldName="n"
98
+ :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
98
99
  @error="handleError"
99
100
  @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
100
101
  @selectImage="updateSelectedImage"
102
+ @updateCarouselIndex="updateActiveIndex"
101
103
  />
102
104
  </div>
103
105
  </div>
@@ -119,7 +121,7 @@
119
121
  import { ref, nextTick, watch } from 'vue'
120
122
  import mediumZoom from 'medium-zoom'
121
123
  import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle } from '@/afcl'
122
- import GenerationCarousel from './imageGenerationCarousel.vue'
124
+ import GenerationCarousel from './ImageGenerationCarousel.vue'
123
125
 
124
126
  const props = defineProps<{
125
127
  meta: any,
@@ -134,6 +136,8 @@ const props = defineProps<{
134
136
  openGenerationCarousel: any
135
137
  isError: boolean,
136
138
  errorMessage: string
139
+ carouselSaveImages: any[]
140
+ carouselImageIndex: any[]
137
141
  }>();
138
142
  const emit = defineEmits(['error']);
139
143
 
@@ -193,4 +197,8 @@ function handleError({ isError, errorMessage }) {
193
197
  });
194
198
  }
195
199
 
200
+ function updateActiveIndex(newIndex: number, id: any, fieldName: string) {
201
+ props.carouselImageIndex[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = newIndex;
202
+ }
203
+
196
204
  </script>
@@ -175,10 +175,6 @@
175
175
  </div>
176
176
  </div>
177
177
  </div>
178
-
179
-
180
-
181
-
182
178
  </template>
183
179
 
184
180
  <script setup lang="ts">
@@ -193,8 +189,8 @@ import { ProgressBar } from '@/afcl';
193
189
  const { t: $t } = useI18n();
194
190
 
195
191
  const prompt = ref('');
196
- const emit = defineEmits(['close', 'selectImage', 'error']);
197
- const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage']);
192
+ const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
193
+ const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex']);
198
194
  const images = ref([]);
199
195
  const loading = ref(false);
200
196
  const attachmentFiles = ref<string[]>([])
@@ -208,12 +204,14 @@ function minifyField(field: string): string {
208
204
 
209
205
  const caurosel = ref(null);
210
206
  onMounted(async () => {
211
- images.value.push((props.images || []));
207
+ for (const img of props.images || []) {
208
+ images.value.push(img);
209
+ }
212
210
  const temp = await getGenerationPrompt() || '';
213
211
  prompt.value = temp[props.fieldName];
214
212
  await nextTick();
215
213
 
216
- const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
214
+ const currentIndex = props.carouselImageIndex || 0;
217
215
  caurosel.value = new Carousel(
218
216
  document.getElementById('gallery'),
219
217
  images.value.map((img, index) => {
@@ -298,6 +296,12 @@ async function confirmImage() {
298
296
  const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
299
297
  const img = images.value[currentIndex];
300
298
 
299
+ props.images.splice(0, props.images.length);
300
+ for (const i of images.value) {
301
+ props.images.push(i);
302
+ }
303
+
304
+ emit('updateCarouselIndex', currentIndex, props.recordId, props.fieldName);
301
305
  emit('selectImage', img, props.recordId, props.fieldName);
302
306
  emit('close');
303
307
 
@@ -25,7 +25,6 @@
25
25
  <VisionTable
26
26
  :checkbox="props.checkboxes"
27
27
  :records="records"
28
- :index="0"
29
28
  :meta="props.meta"
30
29
  :images="images"
31
30
  :tableHeaders="tableHeaders"
@@ -38,6 +37,8 @@
38
37
  :primaryKey="primaryKey"
39
38
  :openGenerationCarousel="openGenerationCarousel"
40
39
  @error="handleTableError"
40
+ :carouselSaveImages="carouselSaveImages"
41
+ :carouselImageIndex="carouselImageIndex"
41
42
  />
42
43
  </div>
43
44
  <div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
@@ -52,7 +53,7 @@
52
53
  <Button
53
54
  class="w-full md:w-64"
54
55
  @click="saveData"
55
- :disabled="isLoading || checkedCount < 1 || isCriticalError"
56
+ :disabled="isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords"
56
57
  :loader="isLoading"
57
58
  >
58
59
  {{ checkedCount > 1 ? 'Save fields' : 'Save field' }}
@@ -68,13 +69,11 @@
68
69
  import { callAdminForthApi } from '@/utils';
69
70
  import { Ref, ref, watch } from 'vue'
70
71
  import { Dialog, Button } from '@/afcl';
71
- import VisionTable from './visionTable.vue'
72
+ import VisionTable from './VisionTable.vue'
72
73
  import adminforth from '@/adminforth';
73
74
  import { useI18n } from 'vue-i18n';
74
- import { useRoute } from 'vue-router';
75
75
  import { AdminUser, type AdminForthResourceCommon } from '@/types';
76
76
 
77
- const route = useRoute();
78
77
  const { t } = useI18n();
79
78
 
80
79
  const props = defineProps<{
@@ -99,11 +98,14 @@ const tableColumns = ref([]);
99
98
  const tableColumnsIndexes = ref([]);
100
99
  const customFieldNames = ref([]);
101
100
  const selected = ref<any[]>([]);
101
+ const carouselSaveImages = ref<any[]>([]);
102
+ const carouselImageIndex = ref<any[]>([]);
102
103
  const isAiResponseReceivedAnalize = ref([]);
103
104
  const isAiResponseReceivedImage = ref([]);
104
105
  const primaryKey = props.meta.primaryKey;
105
106
  const openGenerationCarousel = ref([]);
106
107
  const isLoading = ref(false);
108
+ const isFetchingRecords = ref(false);
107
109
  const isError = ref(false);
108
110
  const isCriticalError = ref(false);
109
111
  const isImageGenerationError = ref(false);
@@ -128,7 +130,7 @@ const openDialog = async () => {
128
130
  return acc;
129
131
  },{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
130
132
  }
131
- isLoading.value = true;
133
+ isFetchingRecords.value = true;
132
134
  const tasks = [];
133
135
  if (props.meta.isFieldsForAnalizeFromImages) {
134
136
  tasks.push(runAiAction({
@@ -152,7 +154,12 @@ const openDialog = async () => {
152
154
  }));
153
155
  }
154
156
  await Promise.all(tasks);
155
- isLoading.value = false;
157
+
158
+ if (props.meta.isImageGeneration) {
159
+ fillCarouselSaveImages();
160
+ }
161
+
162
+ isFetchingRecords.value = false;
156
163
  }
157
164
 
158
165
  watch(selected, (val) => {
@@ -174,6 +181,23 @@ const closeDialog = () => {
174
181
  isCriticalError.value = false;
175
182
  isImageGenerationError.value = false;
176
183
  errorMessage.value = '';
184
+ carouselSaveImages.value = [];
185
+ carouselImageIndex.value = [];
186
+ }
187
+
188
+ function fillCarouselSaveImages() {
189
+ for (const item of selected.value) {
190
+ const tempItem: any = {};
191
+ const tempItemIndex: any = {};
192
+ for (const [key, value] of Object.entries(item)) {
193
+ if (props.meta.outputImageFields?.includes(key)) {
194
+ tempItem[key] = [value];
195
+ tempItemIndex[key] = 0;
196
+ }
197
+ }
198
+ carouselSaveImages.value.push(tempItem);
199
+ carouselImageIndex.value.push(tempItemIndex);
200
+ }
177
201
  }
178
202
 
179
203
  function formatLabel(str) {
@@ -270,7 +294,6 @@ async function getRecords() {
270
294
  console.error('Failed to get records:', error);
271
295
  isError.value = true;
272
296
  errorMessage.value = `Failed to fetch records. Please, try to re-run the action.`;
273
- // Handle error appropriately
274
297
  }
275
298
  }
276
299
 
@@ -288,7 +311,6 @@ async function getImages() {
288
311
  console.error('Failed to get images:', error);
289
312
  isError.value = true;
290
313
  errorMessage.value = `Failed to fetch images. Please, try to re-run the action.`;
291
- // Handle error appropriately
292
314
  }
293
315
  }
294
316
 
@@ -73,7 +73,7 @@
73
73
  <Input
74
74
  type="number"
75
75
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
76
- class="w-full "
76
+ class="w-full min-w-[80px]"
77
77
  :fullWidth="true"
78
78
  />
79
79
  </div>
@@ -91,13 +91,15 @@
91
91
  <div>
92
92
  <GenerationCarousel
93
93
  v-if="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
94
- :images="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
94
+ :images="carouselSaveImages[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
95
95
  :recordId="item[primaryKey]"
96
96
  :meta="props.meta"
97
97
  :fieldName="n"
98
+ :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
98
99
  @error="handleError"
99
100
  @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
100
101
  @selectImage="updateSelectedImage"
102
+ @updateCarouselIndex="updateActiveIndex"
101
103
  />
102
104
  </div>
103
105
  </div>
@@ -119,7 +121,7 @@
119
121
  import { ref, nextTick, watch } from 'vue'
120
122
  import mediumZoom from 'medium-zoom'
121
123
  import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle } from '@/afcl'
122
- import GenerationCarousel from './imageGenerationCarousel.vue'
124
+ import GenerationCarousel from './ImageGenerationCarousel.vue'
123
125
 
124
126
  const props = defineProps<{
125
127
  meta: any,
@@ -134,6 +136,8 @@ const props = defineProps<{
134
136
  openGenerationCarousel: any
135
137
  isError: boolean,
136
138
  errorMessage: string
139
+ carouselSaveImages: any[]
140
+ carouselImageIndex: any[]
137
141
  }>();
138
142
  const emit = defineEmits(['error']);
139
143
 
@@ -193,4 +197,8 @@ function handleError({ isError, errorMessage }) {
193
197
  });
194
198
  }
195
199
 
200
+ function updateActiveIndex(newIndex: number, id: any, fieldName: string) {
201
+ props.carouselImageIndex[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = newIndex;
202
+ }
203
+
196
204
  </script>
package/dist/index.js CHANGED
@@ -61,52 +61,26 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
61
61
  const columns = this.resourceConfig.columns;
62
62
  let columnEnums = [];
63
63
  if (this.options.fillFieldsFromImages) {
64
- if (!this.options.attachFiles) {
65
- throw new Error('⚠️ attachFiles function must be provided in options when fillFieldsFromImages is used');
66
- }
67
- if (!this.options.visionAdapter) {
68
- throw new Error('⚠️ visionAdapter must be provided in options when fillFieldsFromImages is used');
69
- }
70
64
  for (const [key, value] of Object.entries((this.options.fillFieldsFromImages))) {
71
65
  const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
72
- if (column) {
73
- if (column.enum) {
74
- this.options.fillFieldsFromImages[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
75
- columnEnums.push({
76
- name: key,
77
- enum: column.enum,
78
- });
79
- }
80
- }
81
- else {
82
- throw new Error(`⚠️ No column found for key "${key}"`);
66
+ if (column && column.enum) {
67
+ this.options.fillFieldsFromImages[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
68
+ columnEnums.push({
69
+ name: key,
70
+ enum: column.enum,
71
+ });
83
72
  }
84
73
  }
85
74
  }
86
75
  if (this.options.fillPlainFields) {
87
- if (!this.options.textCompleteAdapter) {
88
- throw new Error('⚠️ textCompleteAdapter must be provided in options when fillPlainFields is used');
89
- }
90
76
  for (const [key, value] of Object.entries((this.options.fillPlainFields))) {
91
77
  const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
92
- if (column) {
93
- if (column.enum) {
94
- this.options.fillPlainFields[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
95
- columnEnums.push({
96
- name: key,
97
- enum: column.enum,
98
- });
99
- }
100
- }
101
- else {
102
- throw new Error(`⚠️ No column found for key "${key}"`);
103
- }
104
- }
105
- }
106
- if (this.options.generateImages && !this.options.imageGenerationAdapter) {
107
- for (const [key, value] of Object.entries(this.options.generateImages)) {
108
- if (!this.options.generateImages[key].adapter) {
109
- throw new Error(`⚠️ No image generation adapter found for key "${key}"`);
78
+ if (column && column.enum) {
79
+ this.options.fillPlainFields[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
80
+ columnEnums.push({
81
+ name: key,
82
+ enum: column.enum,
83
+ });
110
84
  }
111
85
  }
112
86
  }
@@ -120,28 +94,17 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
120
94
  //check if Upload plugin is installed on all attachment fields
121
95
  if (this.options.generateImages) {
122
96
  for (const [key, value] of Object.entries(this.options.generateImages)) {
123
- const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
124
- if (!column) {
125
- throw new Error(`⚠️ No column found for key "${key}"`);
126
- }
127
97
  const plugin = adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.resourceConfig.resourceId &&
128
98
  p.pluginOptions.pathColumnName === key);
129
- if (!plugin) {
130
- throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
99
+ if (plugin && plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
100
+ outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
131
101
  }
132
- if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
133
- throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
134
- uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.
135
- Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
136
- `);
137
- }
138
- outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
139
102
  }
140
103
  }
141
104
  const outputFields = Object.assign(Object.assign(Object.assign({}, this.options.fillFieldsFromImages), this.options.fillPlainFields), (this.options.generateImages || {}));
142
105
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
143
106
  const pageInjection = {
144
- file: this.componentPath('visionAction.vue'),
107
+ file: this.componentPath('VisionAction.vue'),
145
108
  meta: {
146
109
  pluginInstanceId: this.pluginInstanceId,
147
110
  outputFields: outputFields,
@@ -170,11 +133,57 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
170
133
  });
171
134
  }
172
135
  validateConfigAfterDiscover(adminforth, resourceConfig) {
173
- // optional method where you can safely check field types after database discovery was performed
136
+ const columns = this.resourceConfig.columns;
137
+ if (this.options.fillFieldsFromImages) {
138
+ if (!this.options.attachFiles) {
139
+ throw new Error('⚠️ attachFiles function must be provided when fillFieldsFromImages is used');
140
+ }
141
+ if (!this.options.visionAdapter) {
142
+ throw new Error('⚠️ visionAdapter must be provided when fillFieldsFromImages is used');
143
+ }
144
+ for (const key of Object.keys(this.options.fillFieldsFromImages)) {
145
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
146
+ if (!column) {
147
+ throw new Error(`⚠️ No column found for key "${key}"`);
148
+ }
149
+ }
150
+ }
151
+ if (this.options.fillPlainFields) {
152
+ if (!this.options.textCompleteAdapter) {
153
+ throw new Error('⚠️ textCompleteAdapter must be provided when fillPlainFields is used');
154
+ }
155
+ for (const key of Object.keys(this.options.fillPlainFields)) {
156
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
157
+ if (!column) {
158
+ throw new Error(`⚠️ No column found for key "${key}"`);
159
+ }
160
+ }
161
+ }
162
+ if (this.options.generateImages) {
163
+ for (const key of Object.keys(this.options.generateImages)) {
164
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
165
+ if (!column) {
166
+ throw new Error(`⚠️ No column found for key "${key}"`);
167
+ }
168
+ const perKeyAdapter = this.options.generateImages[key].adapter;
169
+ if (!perKeyAdapter && !this.options.imageGenerationAdapter) {
170
+ throw new Error(`⚠️ No image generation adapter provided for key "${key}"`);
171
+ }
172
+ const plugin = adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.resourceConfig.resourceId &&
173
+ p.pluginOptions.pathColumnName === key);
174
+ if (!plugin) {
175
+ throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
176
+ }
177
+ if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
178
+ throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
179
+ uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.
180
+ Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
181
+ `);
182
+ }
183
+ }
184
+ }
174
185
  }
175
186
  instanceUniqueRepresentation(pluginOptions) {
176
- // optional method to return unique string representation of plugin instance.
177
- // Needed if plugin can have multiple instances on one resource
178
187
  return `${this.pluginOptions.actionName}`;
179
188
  }
180
189
  setupEndpoints(server) {
@@ -236,23 +245,21 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
236
245
  }
237
246
  }
238
247
  const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
239
- // Fetch the record using the provided ID
240
248
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
241
249
  const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
242
- //create prompt for OpenAI
243
250
  const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
244
251
  const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
245
252
  Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
246
253
  Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
247
254
  If it's number field - return only number.`;
248
255
  //send prompt to OpenAI and get response
249
- const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], 500);
256
+ const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
257
+ const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
250
258
  const resp = chatResponse.response;
251
259
  const topLevelError = chatResponse.error;
252
260
  if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
253
261
  throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
254
262
  }
255
- //parse response and update record
256
263
  const resData = JSON.parse(chatResponse);
257
264
  return resData;
258
265
  }));
@@ -270,8 +277,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
270
277
  for (const [index, record] of records.entries()) {
271
278
  records[index]._label = this.resourceConfig.recordLabel(records[index]);
272
279
  }
280
+ const order = Object.fromEntries(body.body.record.map((id, i) => [id, i]));
281
+ const sortedRecords = records.sort((a, b) => order[a.id] - order[b.id]);
273
282
  return {
274
- records,
283
+ records: sortedRecords,
275
284
  };
276
285
  })
277
286
  });
@@ -336,6 +345,32 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
336
345
  }
337
346
  }
338
347
  }
348
+ try {
349
+ const AuditLogPlugin = this.adminforth.getPluginByClassName('AuditLogPlugin');
350
+ if (AuditLogPlugin) {
351
+ for (const [key, value] of Object.entries(oldRecord)) {
352
+ if (!(key in fieldsToUpdate[idx])) {
353
+ delete oldRecord[key];
354
+ }
355
+ }
356
+ const reorderedOldRecord = Object.keys(fieldsToUpdate[idx]).reduce((acc, key) => {
357
+ if (key in oldRecord) {
358
+ acc[key] = oldRecord[key];
359
+ }
360
+ return acc;
361
+ }, {});
362
+ AuditLogPlugin.logCustomAction({
363
+ resourceId: this.resourceConfig.resourceId,
364
+ recordId: ID,
365
+ actionId: 'Bulk-ai-flow',
366
+ oldData: reorderedOldRecord,
367
+ data: fieldsToUpdate[idx],
368
+ user: adminUser,
369
+ headers: headers
370
+ });
371
+ }
372
+ }
373
+ catch (error) { }
339
374
  return this.adminforth.resource(this.resourceConfig.resourceId).update(ID, fieldsToUpdate[idx]);
340
375
  }));
341
376
  yield Promise.all(updates);
package/index.ts CHANGED
@@ -73,58 +73,30 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
73
73
  const columns = this.resourceConfig.columns;
74
74
  let columnEnums = [];
75
75
  if (this.options.fillFieldsFromImages) {
76
- if (!this.options.attachFiles) {
77
- throw new Error('⚠️ attachFiles function must be provided in options when fillFieldsFromImages is used');
78
- }
79
- if (!this.options.visionAdapter) {
80
- throw new Error('⚠️ visionAdapter must be provided in options when fillFieldsFromImages is used');
81
- }
82
-
83
76
  for (const [key, value] of Object.entries((this.options.fillFieldsFromImages ))) {
84
77
  const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
85
- if (column) {
86
- if(column.enum){
87
- (this.options.fillFieldsFromImages as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
88
- columnEnums.push({
89
- name: key,
90
- enum: column.enum,
91
- });
92
- }
93
- } else {
94
- throw new Error(`⚠️ No column found for key "${key}"`);
78
+ if (column && column.enum) {
79
+ (this.options.fillFieldsFromImages as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
80
+ columnEnums.push({
81
+ name: key,
82
+ enum: column.enum,
83
+ });
95
84
  }
96
85
  }
97
86
  }
98
87
 
99
88
  if (this.options.fillPlainFields) {
100
- if (!this.options.textCompleteAdapter) {
101
- throw new Error('⚠️ textCompleteAdapter must be provided in options when fillPlainFields is used');
102
- }
103
-
104
89
  for (const [key, value] of Object.entries((this.options.fillPlainFields))) {
105
90
  const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
106
- if (column) {
107
- if(column.enum){
108
- (this.options.fillPlainFields as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
109
- columnEnums.push({
110
- name: key,
111
- enum: column.enum,
112
- });
113
- }
114
- } else {
115
- throw new Error(`⚠️ No column found for key "${key}"`);
116
- }
117
- }
118
- }
119
-
120
- if (this.options.generateImages && !this.options.imageGenerationAdapter) {
121
- for (const [key, value] of Object.entries(this.options.generateImages)) {
122
- if (!this.options.generateImages[key].adapter) {
123
- throw new Error(`⚠️ No image generation adapter found for key "${key}"`);
91
+ if (column && column.enum) {
92
+ (this.options.fillPlainFields as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
93
+ columnEnums.push({
94
+ name: key,
95
+ enum: column.enum,
96
+ });
124
97
  }
125
98
  }
126
- }
127
-
99
+ }
128
100
 
129
101
  const outputImageFields = [];
130
102
  if (this.options.generateImages) {
@@ -136,25 +108,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
136
108
  //check if Upload plugin is installed on all attachment fields
137
109
  if (this.options.generateImages) {
138
110
  for (const [key, value] of Object.entries(this.options.generateImages)) {
139
- const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
140
- if (!column) {
141
- throw new Error(`⚠️ No column found for key "${key}"`);
142
- }
143
111
  const plugin = adminforth.activatedPlugins.find(p =>
144
112
  p.resourceConfig!.resourceId === this.resourceConfig.resourceId &&
145
113
  p.pluginOptions.pathColumnName === key
146
114
  );
147
- if (!plugin) {
148
- throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
149
- }
150
- if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
151
- throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
152
- uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.
153
- Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
154
- `);
115
+ if (plugin && plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
116
+ outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
155
117
  }
156
-
157
- outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
158
118
  }
159
119
  }
160
120
 
@@ -168,7 +128,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
168
128
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
169
129
 
170
130
  const pageInjection = {
171
- file: this.componentPath('visionAction.vue'),
131
+ file: this.componentPath('VisionAction.vue'),
172
132
  meta: {
173
133
  pluginInstanceId: this.pluginInstanceId,
174
134
  outputFields: outputFields,
@@ -199,18 +159,65 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
199
159
  }
200
160
 
201
161
  validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
202
- // optional method where you can safely check field types after database discovery was performed
162
+ const columns = this.resourceConfig.columns;
163
+ if (this.options.fillFieldsFromImages) {
164
+ if (!this.options.attachFiles) {
165
+ throw new Error('⚠️ attachFiles function must be provided when fillFieldsFromImages is used');
166
+ }
167
+ if (!this.options.visionAdapter) {
168
+ throw new Error('⚠️ visionAdapter must be provided when fillFieldsFromImages is used');
169
+ }
170
+ for (const key of Object.keys(this.options.fillFieldsFromImages)) {
171
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
172
+ if (!column) {
173
+ throw new Error(`⚠️ No column found for key "${key}"`);
174
+ }
175
+ }
176
+ }
177
+ if (this.options.fillPlainFields) {
178
+ if (!this.options.textCompleteAdapter) {
179
+ throw new Error('⚠️ textCompleteAdapter must be provided when fillPlainFields is used');
180
+ }
181
+ for (const key of Object.keys(this.options.fillPlainFields)) {
182
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
183
+ if (!column) {
184
+ throw new Error(`⚠️ No column found for key "${key}"`);
185
+ }
186
+ }
187
+ }
188
+ if (this.options.generateImages) {
189
+ for (const key of Object.keys(this.options.generateImages)) {
190
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
191
+ if (!column) {
192
+ throw new Error(`⚠️ No column found for key "${key}"`);
193
+ }
194
+ const perKeyAdapter = this.options.generateImages[key].adapter;
195
+ if (!perKeyAdapter && !this.options.imageGenerationAdapter) {
196
+ throw new Error(`⚠️ No image generation adapter provided for key "${key}"`);
197
+ }
198
+
199
+ const plugin = adminforth.activatedPlugins.find(p =>
200
+ p.resourceConfig!.resourceId === this.resourceConfig.resourceId &&
201
+ p.pluginOptions.pathColumnName === key
202
+ );
203
+ if (!plugin) {
204
+ throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
205
+ }
206
+ if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
207
+ throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
208
+ uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.
209
+ Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
210
+ `);
211
+ }
212
+ }
213
+ }
203
214
  }
204
215
 
205
216
  instanceUniqueRepresentation(pluginOptions: any) : string {
206
- // optional method to return unique string representation of plugin instance.
207
- // Needed if plugin can have multiple instances on one resource
208
217
  return `${this.pluginOptions.actionName}`;
209
218
  }
210
219
 
211
220
  setupEndpoints(server: IHttpServer) {
212
-
213
-
214
221
  server.endpoint({
215
222
  method: 'POST',
216
223
  path: `/plugin/${this.pluginInstanceId}/analyze`,
@@ -224,7 +231,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
224
231
  const tasks = selectedIds.map(async (ID) => {
225
232
  // Fetch the record using the provided ID
226
233
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
227
- const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, ID)] );
234
+ const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)] );
228
235
 
229
236
  //recieve image URLs to analyze
230
237
  const attachmentFiles = await this.options.attachFiles({ record: record });
@@ -274,26 +281,23 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
274
281
  }
275
282
  }
276
283
  const tasks = selectedIds.map(async (ID) => {
277
- // Fetch the record using the provided ID
278
284
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
279
285
  const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, ID)] );
280
286
 
281
- //create prompt for OpenAI
282
287
  const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
283
288
  const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
284
289
  Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
285
290
  Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
286
291
  If it's number field - return only number.`;
287
292
  //send prompt to OpenAI and get response
288
- const { content: chatResponse } = await this.options.textCompleteAdapter.complete(prompt, [], 500);
293
+ const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
294
+ const { content: chatResponse } = await this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
289
295
 
290
296
  const resp: any = (chatResponse as any).response;
291
297
  const topLevelError = (chatResponse as any).error;
292
298
  if (topLevelError || resp?.error) {
293
299
  throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
294
300
  }
295
-
296
- //parse response and update record
297
301
  const resData = JSON.parse(chatResponse);
298
302
 
299
303
  return resData;
@@ -304,8 +308,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
304
308
  return { result };
305
309
  }
306
310
  });
307
-
308
-
309
311
  server.endpoint({
310
312
  method: 'POST',
311
313
  path: `/plugin/${this.pluginInstanceId}/get_records`,
@@ -316,13 +318,16 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
316
318
  for( const [index, record] of records.entries() ) {
317
319
  records[index]._label = this.resourceConfig.recordLabel(records[index]);
318
320
  }
321
+ const order = Object.fromEntries(body.body.record.map((id, i) => [id, i]));
322
+
323
+ const sortedRecords = records.sort(
324
+ (a, b) => order[a.id] - order[b.id]
325
+ );
319
326
  return {
320
- records,
327
+ records: sortedRecords,
321
328
  };
322
329
  }
323
330
  });
324
-
325
-
326
331
  server.endpoint({
327
332
  method: 'POST',
328
333
  path: `/plugin/${this.pluginInstanceId}/get_images`,
@@ -340,8 +345,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
340
345
  };
341
346
  }
342
347
  });
343
-
344
-
345
348
  server.endpoint({
346
349
  method: 'POST',
347
350
  path: `/plugin/${this.pluginInstanceId}/update_fields`,
@@ -387,6 +390,35 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
387
390
  }
388
391
  }
389
392
  }
393
+ try {
394
+ const AuditLogPlugin:any = this.adminforth.getPluginByClassName('AuditLogPlugin');
395
+ if (AuditLogPlugin) {
396
+
397
+ for (const [key, value] of Object.entries(oldRecord)) {
398
+ if (!(key in fieldsToUpdate[idx])) {
399
+ delete oldRecord[key];
400
+ }
401
+ }
402
+
403
+ const reorderedOldRecord = Object.keys(fieldsToUpdate[idx]).reduce((acc, key) => {
404
+ if (key in oldRecord) {
405
+ acc[key] = oldRecord[key];
406
+ }
407
+ return acc;
408
+ }, {} as Record<string, unknown>);
409
+
410
+ AuditLogPlugin.logCustomAction({
411
+ resourceId: this.resourceConfig.resourceId,
412
+ recordId: ID,
413
+ actionId: 'Bulk-ai-flow',
414
+ oldData: reorderedOldRecord,
415
+ data: fieldsToUpdate[idx],
416
+ user: adminUser,
417
+ headers: headers
418
+ });
419
+ }
420
+ } catch (error) { }
421
+
390
422
  return this.adminforth.resource(this.resourceConfig.resourceId).update(ID, fieldsToUpdate[idx])
391
423
  });
392
424
  await Promise.all(updates);
@@ -396,8 +428,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
396
428
  }
397
429
  }
398
430
  });
399
-
400
-
401
431
  server.endpoint({
402
432
  method: 'POST',
403
433
  path: `/plugin/${this.pluginInstanceId}/regenerate_images`,
@@ -449,8 +479,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
449
479
  return { images };
450
480
  }
451
481
  });
452
-
453
-
454
482
  server.endpoint({
455
483
  method: 'POST',
456
484
  path: `/plugin/${this.pluginInstanceId}/initial_image_generate`,
@@ -518,8 +546,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
518
546
  return { result };
519
547
  }
520
548
  });
521
-
522
-
523
549
  server.endpoint({
524
550
  method: 'POST',
525
551
  path: `/plugin/${this.pluginInstanceId}/get_generation_prompts`,
@@ -530,8 +556,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
530
556
  return { generationOptions: compiledGenerationOptions };
531
557
  }
532
558
  });
533
-
534
-
535
559
  server.endpoint({
536
560
  method: 'GET',
537
561
  path: `/plugin/${this.pluginInstanceId}/averageDuration`,
@@ -543,8 +567,5 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
543
567
  };
544
568
  }
545
569
  });
546
-
547
-
548
-
549
570
  }
550
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/types.ts CHANGED
@@ -1,20 +1,52 @@
1
- import { ImageVisionAdapter, AdminUser, IAdminForth, StorageAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth";
1
+ import { ImageVisionAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth";
2
2
 
3
3
 
4
4
  export interface PluginOptions {
5
+ /**
6
+ * Name of the action in three dots menu.
7
+ */
5
8
  actionName: string,
9
+
10
+ /**
11
+ * The adapter to use for scaning images and filling fields basing on the image content.
12
+ */
6
13
  visionAdapter?: ImageVisionAdapter,
14
+
15
+ /**
16
+ * The adapter to use for text->text generation.
17
+ */
7
18
  textCompleteAdapter?: CompletionAdapter,
19
+
8
20
  /**
9
21
  * The adapter to use for image generation.
10
22
  */
11
23
  imageGenerationAdapter?: ImageGenerationAdapter,
24
+
25
+ /**
26
+ * List of fields that should be filled based on the image content analysis.
27
+ */
12
28
  fillFieldsFromImages?: Record<string, string>, // can analyze what is on image and fill fields, typical tasks "find dominant color", "describe what is on image", "clasify to one enum item, e.g. what is on image dog/cat/plant"
29
+
30
+ /**
31
+ * List of fields that should be filled based on the text.
32
+ */
13
33
  fillPlainFields?: Record<string, string>,
34
+
35
+ /**
36
+ * Number of tokens to generate (Only for text completion adapter). Default is 1000. 1 token ~= ¾ words
37
+ */
38
+ fillPlainFieldsMaxTokens?: number,
39
+
40
+ /**
41
+ * If you want to generate fields or images based on the image content attached images
42
+ */
14
43
  attachFiles?: ({ record }: {
15
44
  record: any,
16
45
  }) => string[] | Promise<string[]>,
17
46
 
47
+ /**
48
+ * List of image fields, that should be filled.
49
+ */
18
50
  generateImages?: Record<
19
51
  string, {
20
52
  // can generate from images or just from another fields, e.g. "remove text from images", "improve image quality", "turn image into ghibli style"
@@ -43,6 +75,10 @@ export interface PluginOptions {
43
75
  */
44
76
  countToGenerate: number,
45
77
  }>,
78
+
79
+ /**
80
+ * Rate limits for each action.
81
+ */
46
82
  rateLimits?: {
47
83
  fillFieldsFromImages?: string, // e.g. 5/1d - 5 requests per day
48
84
  fillPlainFields?: string,