@adminforth/bulk-ai-flow 1.7.4 → 1.8.1

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.
@@ -5,60 +5,38 @@
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
- <Dialog ref="confirmDialog">
9
- <div
10
- class="fixed inset-0 z-20 flex items-center justify-center bg-black/40"
11
- >
12
- <div
13
- class="bulk-vision-dialog flex items-center justify-center relative w-[100vw] h-[100vh] max-h-[100vh] md:w-auto md:max-w-[95vw] md:min-w-[640px] md:h-auto md:max-h-[90vh] bg-white dark:bg-gray-900 rounded-none md:rounded-md shadow-2xl overflow-hidden"
14
- >
15
- <div class="bulk-vision-table flex flex-col items-center justify-evenly md:max-h-[90vh] gap-3 md:gap-4 w-full h-full p-4 md:p-6 overflow-y-auto overflow-x-auto">
16
- <button type="button"
17
- @click="closeDialog"
18
- class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
19
- <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
20
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
21
- </svg>
22
- </button>
23
-
24
- <div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
25
- <VisionTable
26
- :checkbox="props.checkboxes"
27
- :records="records"
28
- :index="0"
29
- :meta="props.meta"
30
- :images="images"
31
- :tableHeaders="tableHeaders"
32
- :tableColumns="tableColumns"
33
- :customFieldNames="customFieldNames"
34
- :tableColumnsIndexes="tableColumnsIndexes"
35
- :selected="selected"
36
- :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
37
- :isAiResponseReceivedImage="isAiResponseReceivedImage"
38
- :primaryKey="primaryKey"
39
- :openGenerationCarousel="openGenerationCarousel"
40
- @error="handleTableError"
41
- />
42
- </div>
43
- <div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
44
- <div class="h-full text-red-600 font-semibold flex items-center justify-center md:mb-2">
45
- <p v-if="isError === true">{{ errorMessage }}</p>
46
- </div>
47
- <button type="button" class="w-full md:w-auto py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
48
- @click="closeDialog"
49
- >
50
- {{'Cancel'}}
51
- </button>
52
- <Button
53
- class="w-full md:w-64"
54
- @click="saveData"
55
- :disabled="isLoading || checkedCount < 1 || isCriticalError"
56
- :loader="isLoading"
57
- >
58
- {{ checkedCount > 1 ? 'Save fields' : 'Save field' }}
59
- </Button>
60
- </div>
61
- </div>
8
+ <Dialog
9
+ ref="confirmDialog"
10
+ header="Bulk AI Flow"
11
+ class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
12
+ :buttons="[
13
+ { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } },
14
+ { label: 'Cancel', onclick: (dialog) => dialog.hide() },
15
+ ]"
16
+ >
17
+ <div class="bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[90vh] gap-3 md:gap-4 w-full h-full overflow-y-auto">
18
+ <div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
19
+ <VisionTable
20
+ :checkbox="props.checkboxes"
21
+ :records="records"
22
+ :meta="props.meta"
23
+ :images="images"
24
+ :tableHeaders="tableHeaders"
25
+ :tableColumns="tableColumns"
26
+ :customFieldNames="customFieldNames"
27
+ :tableColumnsIndexes="tableColumnsIndexes"
28
+ :selected="selected"
29
+ :isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
30
+ :isAiResponseReceivedImage="isAiResponseReceivedImage"
31
+ :primaryKey="primaryKey"
32
+ :openGenerationCarousel="openGenerationCarousel"
33
+ @error="handleTableError"
34
+ :carouselSaveImages="carouselSaveImages"
35
+ :carouselImageIndex="carouselImageIndex"
36
+ />
37
+ </div>
38
+ <div class="text-red-600 flex items-center w-full">
39
+ <p v-if="isError === true">{{ errorMessage }}</p>
62
40
  </div>
63
41
  </div>
64
42
  </Dialog>
@@ -68,13 +46,11 @@
68
46
  import { callAdminForthApi } from '@/utils';
69
47
  import { Ref, ref, watch } from 'vue'
70
48
  import { Dialog, Button } from '@/afcl';
71
- import VisionTable from './visionTable.vue'
49
+ import VisionTable from './VisionTable.vue'
72
50
  import adminforth from '@/adminforth';
73
51
  import { useI18n } from 'vue-i18n';
74
- import { useRoute } from 'vue-router';
75
52
  import { AdminUser, type AdminForthResourceCommon } from '@/types';
76
53
 
77
- const route = useRoute();
78
54
  const { t } = useI18n();
79
55
 
80
56
  const props = defineProps<{
@@ -99,11 +75,14 @@ const tableColumns = ref([]);
99
75
  const tableColumnsIndexes = ref([]);
100
76
  const customFieldNames = ref([]);
101
77
  const selected = ref<any[]>([]);
78
+ const carouselSaveImages = ref<any[]>([]);
79
+ const carouselImageIndex = ref<any[]>([]);
102
80
  const isAiResponseReceivedAnalize = ref([]);
103
81
  const isAiResponseReceivedImage = ref([]);
104
82
  const primaryKey = props.meta.primaryKey;
105
83
  const openGenerationCarousel = ref([]);
106
84
  const isLoading = ref(false);
85
+ const isFetchingRecords = ref(false);
107
86
  const isError = ref(false);
108
87
  const isCriticalError = ref(false);
109
88
  const isImageGenerationError = ref(false);
@@ -128,7 +107,7 @@ const openDialog = async () => {
128
107
  return acc;
129
108
  },{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
130
109
  }
131
- isLoading.value = true;
110
+ isFetchingRecords.value = true;
132
111
  const tasks = [];
133
112
  if (props.meta.isFieldsForAnalizeFromImages) {
134
113
  tasks.push(runAiAction({
@@ -152,7 +131,12 @@ const openDialog = async () => {
152
131
  }));
153
132
  }
154
133
  await Promise.all(tasks);
155
- isLoading.value = false;
134
+
135
+ if (props.meta.isImageGeneration) {
136
+ fillCarouselSaveImages();
137
+ }
138
+
139
+ isFetchingRecords.value = false;
156
140
  }
157
141
 
158
142
  watch(selected, (val) => {
@@ -160,22 +144,22 @@ watch(selected, (val) => {
160
144
  checkedCount.value = val.filter(item => item.isChecked === true).length;
161
145
  }, { deep: true });
162
146
 
163
- const closeDialog = () => {
164
- confirmDialog.value.close();
165
- isAiResponseReceivedAnalize.value = [];
166
- isAiResponseReceivedImage.value = [];
167
-
168
- records.value = [];
169
- images.value = [];
170
- selected.value = [];
171
- tableColumns.value = [];
172
- tableColumnsIndexes.value = [];
173
- isError.value = false;
174
- isCriticalError.value = false;
175
- isImageGenerationError.value = false;
176
- errorMessage.value = '';
147
+ function fillCarouselSaveImages() {
148
+ for (const item of selected.value) {
149
+ const tempItem: any = {};
150
+ const tempItemIndex: any = {};
151
+ for (const [key, value] of Object.entries(item)) {
152
+ if (props.meta.outputImageFields?.includes(key)) {
153
+ tempItem[key] = [value];
154
+ tempItemIndex[key] = 0;
155
+ }
156
+ }
157
+ carouselSaveImages.value.push(tempItem);
158
+ carouselImageIndex.value.push(tempItemIndex);
159
+ }
177
160
  }
178
161
 
162
+
179
163
  function formatLabel(str) {
180
164
  return str
181
165
  .split('_')
@@ -270,7 +254,6 @@ async function getRecords() {
270
254
  console.error('Failed to get records:', error);
271
255
  isError.value = true;
272
256
  errorMessage.value = `Failed to fetch records. Please, try to re-run the action.`;
273
- // Handle error appropriately
274
257
  }
275
258
  }
276
259
 
@@ -288,7 +271,6 @@ async function getImages() {
288
271
  console.error('Failed to get images:', error);
289
272
  isError.value = true;
290
273
  errorMessage.value = `Failed to fetch images. Please, try to re-run the action.`;
291
- // Handle error appropriately
292
274
  }
293
275
  }
294
276
 
@@ -1,9 +1,8 @@
1
1
  <template>
2
- <div>
3
2
  <Table
4
3
  :columns="tableHeaders"
5
4
  :data="tableColumns"
6
- :pageSize="8"
5
+ :pageSize="6"
7
6
  >
8
7
  <!-- HEADER TEMPLATE -->
9
8
  <template #header:checkboxes="{ item }">
@@ -28,18 +27,22 @@
28
27
  @click="zoomImage(image)"
29
28
  />
30
29
  </div>
30
+ </div>
31
+ <transition name="fade">
31
32
  <div
32
33
  v-if="zoomedImage"
33
34
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
34
35
  @click.self="closeZoom"
35
36
  >
36
- <img
37
- :src="zoomedImage"
38
- ref="zoomedImg"
39
- class="max-w-full max-h-full rounded-lg object-contain cursor-grab z-75"
40
- />
37
+ <transition name="zoom">
38
+ <img
39
+ v-if="zoomedImage"
40
+ :src="zoomedImage"
41
+ class="max-w-full max-h-full rounded-lg object-contain cursor-grab z-75"
42
+ />
43
+ </transition>
41
44
  </div>
42
- </div>
45
+ </transition>
43
46
  </div>
44
47
  </template>
45
48
  <!-- CUSTOM FIELD TEMPLATES -->
@@ -73,7 +76,7 @@
73
76
  <Input
74
77
  type="number"
75
78
  v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
76
- class="w-full "
79
+ class="w-full min-w-[80px]"
77
80
  :fullWidth="true"
78
81
  />
79
82
  </div>
@@ -91,13 +94,15 @@
91
94
  <div>
92
95
  <GenerationCarousel
93
96
  v-if="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
94
- :images="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
97
+ :images="carouselSaveImages[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
95
98
  :recordId="item[primaryKey]"
96
99
  :meta="props.meta"
97
100
  :fieldName="n"
101
+ :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
98
102
  @error="handleError"
99
103
  @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
100
104
  @selectImage="updateSelectedImage"
105
+ @updateCarouselIndex="updateActiveIndex"
101
106
  />
102
107
  </div>
103
108
  </div>
@@ -112,14 +117,12 @@
112
117
  </div>
113
118
  </template>
114
119
  </Table>
115
- </div>
116
120
  </template>
117
121
 
118
122
  <script lang="ts" setup>
119
- import { ref, nextTick, watch } from 'vue'
120
- import mediumZoom from 'medium-zoom'
123
+ import { ref } from 'vue'
121
124
  import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle } from '@/afcl'
122
- import GenerationCarousel from './imageGenerationCarousel.vue'
125
+ import GenerationCarousel from './ImageGenerationCarousel.vue'
123
126
 
124
127
  const props = defineProps<{
125
128
  meta: any,
@@ -134,12 +137,13 @@ const props = defineProps<{
134
137
  openGenerationCarousel: any
135
138
  isError: boolean,
136
139
  errorMessage: string
140
+ carouselSaveImages: any[]
141
+ carouselImageIndex: any[]
137
142
  }>();
138
143
  const emit = defineEmits(['error']);
139
144
 
140
145
 
141
146
  const zoomedImage = ref(null)
142
- const zoomedImg = ref(null)
143
147
 
144
148
 
145
149
  function zoomImage(img) {
@@ -150,17 +154,6 @@ function closeZoom() {
150
154
  zoomedImage.value = null
151
155
  }
152
156
 
153
- watch(zoomedImage, async (val) => {
154
- await nextTick()
155
- if (val && zoomedImg.value) {
156
- mediumZoom(zoomedImg.value, {
157
- margin: 24,
158
- background: 'rgba(0, 0, 0, 0.9)',
159
- scrollOffset: 150
160
- }).show()
161
- }
162
- })
163
-
164
157
  function isInColumnEnum(key: string): boolean {
165
158
  const colEnum = props.meta.columnEnums?.find(c => c.name === key);
166
159
  if (!colEnum) {
@@ -193,4 +186,25 @@ function handleError({ isError, errorMessage }) {
193
186
  });
194
187
  }
195
188
 
196
- </script>
189
+ function updateActiveIndex(newIndex: number, id: any, fieldName: string) {
190
+ props.carouselImageIndex[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = newIndex;
191
+ }
192
+
193
+ </script>
194
+
195
+ <style scoped>
196
+ .fade-enter-active, .fade-leave-active {
197
+ transition: opacity 0.2s ease;
198
+ }
199
+ .fade-enter-from, .fade-leave-to {
200
+ opacity: 0;
201
+ }
202
+
203
+ .zoom-enter-active, .zoom-leave-active {
204
+ transition: transform 0.2s ease, opacity 0.2s ease;
205
+ }
206
+ .zoom-enter-from, .zoom-leave-to {
207
+ transform: scale(0.95);
208
+ opacity: 0;
209
+ }
210
+ </style>
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,15 @@ 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}`);
131
- }
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
99
  outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
139
100
  }
140
101
  }
141
102
  const outputFields = Object.assign(Object.assign(Object.assign({}, this.options.fillFieldsFromImages), this.options.fillPlainFields), (this.options.generateImages || {}));
142
103
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
143
104
  const pageInjection = {
144
- file: this.componentPath('visionAction.vue'),
105
+ file: this.componentPath('VisionAction.vue'),
145
106
  meta: {
146
107
  pluginInstanceId: this.pluginInstanceId,
147
108
  outputFields: outputFields,
@@ -170,11 +131,63 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
170
131
  });
171
132
  }
172
133
  validateConfigAfterDiscover(adminforth, resourceConfig) {
173
- // optional method where you can safely check field types after database discovery was performed
134
+ const columns = this.resourceConfig.columns;
135
+ if (this.options.fillFieldsFromImages) {
136
+ if (!this.options.attachFiles) {
137
+ throw new Error('⚠️ attachFiles function must be provided when fillFieldsFromImages is used');
138
+ }
139
+ if (!this.options.visionAdapter) {
140
+ throw new Error('⚠️ visionAdapter must be provided when fillFieldsFromImages is used');
141
+ }
142
+ for (const key of Object.keys(this.options.fillFieldsFromImages)) {
143
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
144
+ if (!column) {
145
+ throw new Error(`⚠️ No column found for key "${key}"`);
146
+ }
147
+ }
148
+ }
149
+ if (this.options.fillPlainFields) {
150
+ if (!this.options.textCompleteAdapter) {
151
+ throw new Error('⚠️ textCompleteAdapter must be provided when fillPlainFields is used');
152
+ }
153
+ for (const key of Object.keys(this.options.fillPlainFields)) {
154
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
155
+ if (!column) {
156
+ throw new Error(`⚠️ No column found for key "${key}"`);
157
+ }
158
+ }
159
+ }
160
+ if (this.options.generateImages) {
161
+ for (const key of Object.keys(this.options.generateImages)) {
162
+ const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
163
+ if (!column) {
164
+ throw new Error(`⚠️ No column found for key "${key}"`);
165
+ }
166
+ const perKeyAdapter = this.options.generateImages[key].adapter;
167
+ if (!perKeyAdapter && !this.options.imageGenerationAdapter) {
168
+ throw new Error(`⚠️ No image generation adapter provided for key "${key}"`);
169
+ }
170
+ const plugin = adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.resourceConfig.resourceId &&
171
+ p.pluginOptions.pathColumnName === key);
172
+ if (!plugin) {
173
+ 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}`);
174
+ }
175
+ if (!plugin.pluginOptions || !plugin.pluginOptions.storageAdapter) {
176
+ throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' is missing a storageAdapter configuration.`);
177
+ }
178
+ if (typeof plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly !== 'function') {
179
+ throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' uses a storage adapter without 'objectCanBeAccesedPublicly' method.`);
180
+ }
181
+ if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
182
+ throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
183
+ 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.
184
+ Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
185
+ `);
186
+ }
187
+ }
188
+ }
174
189
  }
175
190
  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
191
  return `${this.pluginOptions.actionName}`;
179
192
  }
180
193
  setupEndpoints(server) {
@@ -236,23 +249,21 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
236
249
  }
237
250
  }
238
251
  const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
239
- // Fetch the record using the provided ID
240
252
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
241
253
  const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
242
- //create prompt for OpenAI
243
254
  const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
244
255
  const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
245
256
  Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
246
257
  Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
247
258
  If it's number field - return only number.`;
248
259
  //send prompt to OpenAI and get response
249
- const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], 500);
260
+ const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
261
+ const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
250
262
  const resp = chatResponse.response;
251
263
  const topLevelError = chatResponse.error;
252
264
  if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
253
265
  throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
254
266
  }
255
- //parse response and update record
256
267
  const resData = JSON.parse(chatResponse);
257
268
  return resData;
258
269
  }));
@@ -270,8 +281,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
270
281
  for (const [index, record] of records.entries()) {
271
282
  records[index]._label = this.resourceConfig.recordLabel(records[index]);
272
283
  }
284
+ const order = Object.fromEntries(body.body.record.map((id, i) => [id, i]));
285
+ const sortedRecords = records.sort((a, b) => order[a.id] - order[b.id]);
273
286
  return {
274
- records,
287
+ records: sortedRecords,
275
288
  };
276
289
  })
277
290
  });
@@ -336,6 +349,32 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
336
349
  }
337
350
  }
338
351
  }
352
+ try {
353
+ const AuditLogPlugin = this.adminforth.getPluginByClassName('AuditLogPlugin');
354
+ if (AuditLogPlugin) {
355
+ for (const [key, value] of Object.entries(oldRecord)) {
356
+ if (!(key in fieldsToUpdate[idx])) {
357
+ delete oldRecord[key];
358
+ }
359
+ }
360
+ const reorderedOldRecord = Object.keys(fieldsToUpdate[idx]).reduce((acc, key) => {
361
+ if (key in oldRecord) {
362
+ acc[key] = oldRecord[key];
363
+ }
364
+ return acc;
365
+ }, {});
366
+ AuditLogPlugin.logCustomAction({
367
+ resourceId: this.resourceConfig.resourceId,
368
+ recordId: ID,
369
+ actionId: 'Bulk-ai-flow',
370
+ oldData: reorderedOldRecord,
371
+ data: fieldsToUpdate[idx],
372
+ user: adminUser,
373
+ headers: headers
374
+ });
375
+ }
376
+ }
377
+ catch (error) { }
339
378
  return this.adminforth.resource(this.resourceConfig.resourceId).update(ID, fieldsToUpdate[idx]);
340
379
  }));
341
380
  yield Promise.all(updates);