@adminforth/bulk-ai-flow 1.5.3 → 1.5.5

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
@@ -11,5 +11,5 @@ custom/tsconfig.json
11
11
  custom/visionAction.vue
12
12
  custom/visionTable.vue
13
13
 
14
- sent 182,050 bytes received 134 bytes 364,368.00 bytes/sec
15
- total size is 181,515 speedup is 1.00
14
+ sent 182,651 bytes received 134 bytes 365,570.00 bytes/sec
15
+ total size is 182,116 speedup is 1.00
@@ -106,6 +106,7 @@ const openGenerationCarousel = ref([]);
106
106
  const isLoading = ref(false);
107
107
  const isError = ref(false);
108
108
  const isCriticalError = ref(false);
109
+ const isImageGenerationError = ref(false);
109
110
  const errorMessage = ref('');
110
111
  const checkedCount = ref(0);
111
112
 
@@ -289,26 +290,27 @@ async function prepareDataForSave() {
289
290
  .filter(item => item.isChecked === true)
290
291
  .map(item => item[primaryKey]);
291
292
 
292
- const promises = [];
293
- for (const item of checkedItems) {
294
- for (const [key, value] of Object.entries(item)) {
295
- if(props.meta.outputImageFields?.includes(key)) {
296
- const p = convertImages(key, value).then(result => {
297
- item[key] = result;
298
- });
299
-
300
- promises.push(p);
293
+ if (isImageGenerationError.value !== true) {
294
+ const promises = [];
295
+ for (const item of checkedItems) {
296
+ for (const [key, value] of Object.entries(item)) {
297
+ if(props.meta.outputImageFields?.includes(key)) {
298
+ const p = convertImages(key, value).then(result => {
299
+ item[key] = result;
300
+ });
301
+
302
+ promises.push(p);
303
+ }
301
304
  }
302
305
  }
306
+ await Promise.all(promises);
303
307
  }
304
- await Promise.all(promises);
305
-
306
308
  return [checkedItemsIDs, checkedItems];
307
309
  }
308
310
 
309
311
  async function convertImages(fieldName, img) {
310
312
  let imgBlob;
311
- if (img.startsWith('data:')) {
313
+ if (typeof img === 'string' && img.startsWith('data:')) {
312
314
  const base64 = img.split(',')[1];
313
315
  const mimeType = img.split(';')[0].split(':')[1];
314
316
  const byteCharacters = atob(base64);
@@ -318,7 +320,7 @@ async function convertImages(fieldName, img) {
318
320
  }
319
321
  const byteArray = new Uint8Array(byteNumbers);
320
322
  imgBlob = new Blob([byteArray], { type: mimeType });
321
- } else {
323
+ } else if (typeof img === 'string') {
322
324
  imgBlob = await fetch(
323
325
  `/adminapi/v1/plugin/${props.meta.outputImagesPluginInstanceIds[fieldName]}/cors-proxy?url=${encodeURIComponent(img)}`
324
326
  ).then(res => { return res.blob() });
@@ -328,9 +330,8 @@ async function convertImages(fieldName, img) {
328
330
 
329
331
 
330
332
  async function analyzeFields() {
333
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
331
334
  try {
332
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
333
-
334
335
  const res = await callAdminForthApi({
335
336
  path: `/plugin/${props.meta.pluginInstanceId}/analyze`,
336
337
  method: 'POST',
@@ -339,8 +340,6 @@ async function analyzeFields() {
339
340
  },
340
341
  });
341
342
 
342
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
343
-
344
343
  if (res?.error) {
345
344
  adminforth.alert({
346
345
  message: res.error,
@@ -350,7 +349,7 @@ async function analyzeFields() {
350
349
 
351
350
  console.error('Failed to analyze image(s):', res.error);
352
351
  isError.value = true;
353
- isCriticalError.value = true;
352
+ //isCriticalError.value = true;
354
353
  errorMessage.value = `Failed to fetch analyze image(s). Please, try to re-run the action.`;
355
354
  } else {
356
355
  res.result.forEach((item, idx) => {
@@ -375,16 +374,16 @@ async function analyzeFields() {
375
374
 
376
375
  console.error('Failed to analyze image(s):', error);
377
376
  isError.value = true;
378
- isCriticalError.value = true;
377
+ //isCriticalError.value = true;
379
378
  errorMessage.value = res.error;
380
379
  }
380
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
381
381
  }
382
382
 
383
383
 
384
384
  async function analyzeFieldsNoImages() {
385
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
385
386
  try {
386
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
387
-
388
387
  const res = await callAdminForthApi({
389
388
  path: `/plugin/${props.meta.pluginInstanceId}/analyze_no_images`,
390
389
  method: 'POST',
@@ -392,9 +391,6 @@ async function analyzeFieldsNoImages() {
392
391
  selectedIds: props.checkboxes,
393
392
  },
394
393
  });
395
- if(!props.meta.isFieldsForAnalizeFromImages) {
396
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
397
- }
398
394
  if(res?.error) {
399
395
  adminforth.alert({
400
396
  message: res.error,
@@ -403,7 +399,7 @@ async function analyzeFieldsNoImages() {
403
399
  });
404
400
  console.error('Failed to analyze fields:', res.error);
405
401
  isError.value = true;
406
- isCriticalError.value = true;
402
+ //isCriticalError.value = true;
407
403
  errorMessage.value = res.error;
408
404
  } else {
409
405
  res.result.forEach((item, idx) => {
@@ -427,9 +423,12 @@ async function analyzeFieldsNoImages() {
427
423
  });
428
424
  console.error('Failed to analyze fields:', error);
429
425
  isError.value = true;
430
- isCriticalError.value = true;
426
+ //isCriticalError.value = true;
431
427
  errorMessage.value = `Failed to analyze fields. Please, try to re-run the action.`;
432
428
  }
429
+ if(!props.meta.isFieldsForAnalizeFromImages) {
430
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
431
+ }
433
432
  }
434
433
 
435
434
 
@@ -443,19 +442,23 @@ async function saveData() {
443
442
  try {
444
443
  isLoading.value = true;
445
444
  const [checkedItemsIDs, reqData] = await prepareDataForSave();
446
-
447
- const imagesToUpload = [];
448
- for (const item of reqData) {
449
- for (const [key, value] of Object.entries(item)) {
450
- if(props.meta.outputImageFields?.includes(key)) {
451
- const p = uploadImage(value, item[primaryKey], key).then(result => {
452
- item[key] = result;
453
- });
454
- imagesToUpload.push(p);
445
+ if (isImageGenerationError.value === false) {
446
+ const imagesToUpload = [];
447
+ for (const item of reqData) {
448
+ for (const [key, value] of Object.entries(item)) {
449
+ if(props.meta.outputImageFields?.includes(key)) {
450
+ if (!value) {
451
+ continue;
452
+ }
453
+ const p = uploadImage(value, item[primaryKey], key).then(result => {
454
+ item[key] = result;
455
+ });
456
+ imagesToUpload.push(p);
457
+ }
455
458
  }
456
459
  }
460
+ await Promise.all(imagesToUpload);
457
461
  }
458
- await Promise.all(imagesToUpload);
459
462
 
460
463
  const res = await callAdminForthApi({
461
464
  path: `/plugin/${props.meta.pluginInstanceId}/update_fields`,
@@ -463,6 +466,7 @@ async function saveData() {
463
466
  body: {
464
467
  selectedIds: checkedItemsIDs,
465
468
  fields: reqData,
469
+ saveImages: !isImageGenerationError.value
466
470
  },
467
471
  });
468
472
 
@@ -508,7 +512,7 @@ async function generateImages() {
508
512
  } catch (e) {
509
513
  console.error('Error generating images:', e);
510
514
  isError.value = true;
511
- isCriticalError.value = true;
515
+ isImageGenerationError.value = true;
512
516
  errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
513
517
  }
514
518
  isAiResponseReceivedImage.value = props.checkboxes.map(() => true);
@@ -519,7 +523,7 @@ async function generateImages() {
519
523
  if (!res) {
520
524
  error = 'Error generating images, something went wrong';
521
525
  isError.value = true;
522
- isCriticalError.value = true;
526
+ isImageGenerationError.value = true;
523
527
  errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
524
528
  }
525
529
 
@@ -528,9 +532,9 @@ async function generateImages() {
528
532
  message: error,
529
533
  variant: 'danger',
530
534
  timeout: 'unlimited',
531
- });;
535
+ });
532
536
  isError.value = true;
533
- isCriticalError.value = true;
537
+ isImageGenerationError.value = true;
534
538
  errorMessage.value = error;
535
539
  } else {
536
540
  res.result.forEach((item, idx) => {
@@ -78,7 +78,7 @@
78
78
  </div>
79
79
  </div>
80
80
 
81
- <div v-else-if="isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])]">
81
+ <div v-if="isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])]">
82
82
  <div v-if="isInColumnImage(n)">
83
83
  <div class="mt-2 flex items-center justify-center gap-2">
84
84
  <img
@@ -102,11 +102,11 @@
102
102
  </div>
103
103
  </div>
104
104
 
105
- <div v-else-if="isInColumnImage(n)">
105
+ <div v-if="!isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && isInColumnImage(n)">
106
106
  <Skeleton type="image" class="w-20 h-20" />
107
107
  </div>
108
108
 
109
- <div v-else>
109
+ <div v-if="!isAiResponseReceivedAnalize[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && !isInColumnImage(n)">
110
110
  <Skeleton class="w-full h-6" />
111
111
  </div>
112
112
  </template>
@@ -106,6 +106,7 @@ const openGenerationCarousel = ref([]);
106
106
  const isLoading = ref(false);
107
107
  const isError = ref(false);
108
108
  const isCriticalError = ref(false);
109
+ const isImageGenerationError = ref(false);
109
110
  const errorMessage = ref('');
110
111
  const checkedCount = ref(0);
111
112
 
@@ -289,26 +290,27 @@ async function prepareDataForSave() {
289
290
  .filter(item => item.isChecked === true)
290
291
  .map(item => item[primaryKey]);
291
292
 
292
- const promises = [];
293
- for (const item of checkedItems) {
294
- for (const [key, value] of Object.entries(item)) {
295
- if(props.meta.outputImageFields?.includes(key)) {
296
- const p = convertImages(key, value).then(result => {
297
- item[key] = result;
298
- });
299
-
300
- promises.push(p);
293
+ if (isImageGenerationError.value !== true) {
294
+ const promises = [];
295
+ for (const item of checkedItems) {
296
+ for (const [key, value] of Object.entries(item)) {
297
+ if(props.meta.outputImageFields?.includes(key)) {
298
+ const p = convertImages(key, value).then(result => {
299
+ item[key] = result;
300
+ });
301
+
302
+ promises.push(p);
303
+ }
301
304
  }
302
305
  }
306
+ await Promise.all(promises);
303
307
  }
304
- await Promise.all(promises);
305
-
306
308
  return [checkedItemsIDs, checkedItems];
307
309
  }
308
310
 
309
311
  async function convertImages(fieldName, img) {
310
312
  let imgBlob;
311
- if (img.startsWith('data:')) {
313
+ if (typeof img === 'string' && img.startsWith('data:')) {
312
314
  const base64 = img.split(',')[1];
313
315
  const mimeType = img.split(';')[0].split(':')[1];
314
316
  const byteCharacters = atob(base64);
@@ -318,7 +320,7 @@ async function convertImages(fieldName, img) {
318
320
  }
319
321
  const byteArray = new Uint8Array(byteNumbers);
320
322
  imgBlob = new Blob([byteArray], { type: mimeType });
321
- } else {
323
+ } else if (typeof img === 'string') {
322
324
  imgBlob = await fetch(
323
325
  `/adminapi/v1/plugin/${props.meta.outputImagesPluginInstanceIds[fieldName]}/cors-proxy?url=${encodeURIComponent(img)}`
324
326
  ).then(res => { return res.blob() });
@@ -328,9 +330,8 @@ async function convertImages(fieldName, img) {
328
330
 
329
331
 
330
332
  async function analyzeFields() {
333
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
331
334
  try {
332
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
333
-
334
335
  const res = await callAdminForthApi({
335
336
  path: `/plugin/${props.meta.pluginInstanceId}/analyze`,
336
337
  method: 'POST',
@@ -339,8 +340,6 @@ async function analyzeFields() {
339
340
  },
340
341
  });
341
342
 
342
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
343
-
344
343
  if (res?.error) {
345
344
  adminforth.alert({
346
345
  message: res.error,
@@ -350,7 +349,7 @@ async function analyzeFields() {
350
349
 
351
350
  console.error('Failed to analyze image(s):', res.error);
352
351
  isError.value = true;
353
- isCriticalError.value = true;
352
+ //isCriticalError.value = true;
354
353
  errorMessage.value = `Failed to fetch analyze image(s). Please, try to re-run the action.`;
355
354
  } else {
356
355
  res.result.forEach((item, idx) => {
@@ -375,16 +374,16 @@ async function analyzeFields() {
375
374
 
376
375
  console.error('Failed to analyze image(s):', error);
377
376
  isError.value = true;
378
- isCriticalError.value = true;
377
+ //isCriticalError.value = true;
379
378
  errorMessage.value = res.error;
380
379
  }
380
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
381
381
  }
382
382
 
383
383
 
384
384
  async function analyzeFieldsNoImages() {
385
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
385
386
  try {
386
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => false);
387
-
388
387
  const res = await callAdminForthApi({
389
388
  path: `/plugin/${props.meta.pluginInstanceId}/analyze_no_images`,
390
389
  method: 'POST',
@@ -392,9 +391,6 @@ async function analyzeFieldsNoImages() {
392
391
  selectedIds: props.checkboxes,
393
392
  },
394
393
  });
395
- if(!props.meta.isFieldsForAnalizeFromImages) {
396
- isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
397
- }
398
394
  if(res?.error) {
399
395
  adminforth.alert({
400
396
  message: res.error,
@@ -403,7 +399,7 @@ async function analyzeFieldsNoImages() {
403
399
  });
404
400
  console.error('Failed to analyze fields:', res.error);
405
401
  isError.value = true;
406
- isCriticalError.value = true;
402
+ //isCriticalError.value = true;
407
403
  errorMessage.value = res.error;
408
404
  } else {
409
405
  res.result.forEach((item, idx) => {
@@ -427,9 +423,12 @@ async function analyzeFieldsNoImages() {
427
423
  });
428
424
  console.error('Failed to analyze fields:', error);
429
425
  isError.value = true;
430
- isCriticalError.value = true;
426
+ //isCriticalError.value = true;
431
427
  errorMessage.value = `Failed to analyze fields. Please, try to re-run the action.`;
432
428
  }
429
+ if(!props.meta.isFieldsForAnalizeFromImages) {
430
+ isAiResponseReceivedAnalize.value = props.checkboxes.map(() => true);
431
+ }
433
432
  }
434
433
 
435
434
 
@@ -443,19 +442,23 @@ async function saveData() {
443
442
  try {
444
443
  isLoading.value = true;
445
444
  const [checkedItemsIDs, reqData] = await prepareDataForSave();
446
-
447
- const imagesToUpload = [];
448
- for (const item of reqData) {
449
- for (const [key, value] of Object.entries(item)) {
450
- if(props.meta.outputImageFields?.includes(key)) {
451
- const p = uploadImage(value, item[primaryKey], key).then(result => {
452
- item[key] = result;
453
- });
454
- imagesToUpload.push(p);
445
+ if (isImageGenerationError.value === false) {
446
+ const imagesToUpload = [];
447
+ for (const item of reqData) {
448
+ for (const [key, value] of Object.entries(item)) {
449
+ if(props.meta.outputImageFields?.includes(key)) {
450
+ if (!value) {
451
+ continue;
452
+ }
453
+ const p = uploadImage(value, item[primaryKey], key).then(result => {
454
+ item[key] = result;
455
+ });
456
+ imagesToUpload.push(p);
457
+ }
455
458
  }
456
459
  }
460
+ await Promise.all(imagesToUpload);
457
461
  }
458
- await Promise.all(imagesToUpload);
459
462
 
460
463
  const res = await callAdminForthApi({
461
464
  path: `/plugin/${props.meta.pluginInstanceId}/update_fields`,
@@ -463,6 +466,7 @@ async function saveData() {
463
466
  body: {
464
467
  selectedIds: checkedItemsIDs,
465
468
  fields: reqData,
469
+ saveImages: !isImageGenerationError.value
466
470
  },
467
471
  });
468
472
 
@@ -508,7 +512,7 @@ async function generateImages() {
508
512
  } catch (e) {
509
513
  console.error('Error generating images:', e);
510
514
  isError.value = true;
511
- isCriticalError.value = true;
515
+ isImageGenerationError.value = true;
512
516
  errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
513
517
  }
514
518
  isAiResponseReceivedImage.value = props.checkboxes.map(() => true);
@@ -519,7 +523,7 @@ async function generateImages() {
519
523
  if (!res) {
520
524
  error = 'Error generating images, something went wrong';
521
525
  isError.value = true;
522
- isCriticalError.value = true;
526
+ isImageGenerationError.value = true;
523
527
  errorMessage.value = `Failed to generate images. Please, try to re-run the action.`;
524
528
  }
525
529
 
@@ -528,9 +532,9 @@ async function generateImages() {
528
532
  message: error,
529
533
  variant: 'danger',
530
534
  timeout: 'unlimited',
531
- });;
535
+ });
532
536
  isError.value = true;
533
- isCriticalError.value = true;
537
+ isImageGenerationError.value = true;
534
538
  errorMessage.value = error;
535
539
  } else {
536
540
  res.result.forEach((item, idx) => {
@@ -78,7 +78,7 @@
78
78
  </div>
79
79
  </div>
80
80
 
81
- <div v-else-if="isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])]">
81
+ <div v-if="isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])]">
82
82
  <div v-if="isInColumnImage(n)">
83
83
  <div class="mt-2 flex items-center justify-center gap-2">
84
84
  <img
@@ -102,11 +102,11 @@
102
102
  </div>
103
103
  </div>
104
104
 
105
- <div v-else-if="isInColumnImage(n)">
105
+ <div v-if="!isAiResponseReceivedImage[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && isInColumnImage(n)">
106
106
  <Skeleton type="image" class="w-20 h-20" />
107
107
  </div>
108
108
 
109
- <div v-else>
109
+ <div v-if="!isAiResponseReceivedAnalize[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])] && !isInColumnImage(n)">
110
110
  <Skeleton class="w-full h-6" />
111
111
  </div>
112
112
  </template>
package/dist/index.js CHANGED
@@ -211,26 +211,29 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
211
211
  const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
212
212
  //recieve image URLs to analyze
213
213
  const attachmentFiles = yield this.options.attachFiles({ record: record });
214
- //create prompt for OpenAI
215
- const compiledOutputFields = this.compileOutputFieldsTemplates(record);
216
- const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
217
- Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
218
- Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
219
- Image URLs:`;
220
- //send prompt to OpenAI and get response
221
- const chatResponse = yield this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
222
- const resp = chatResponse.response;
223
- const topLevelError = chatResponse.error;
224
- if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
225
- throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
226
- }
227
- const textOutput = (_f = (_e = (_d = (_c = (_b = (_a = resp === null || resp === void 0 ? void 0 : resp.output) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : resp === null || resp === void 0 ? void 0 : resp.output_text) !== null && _f !== void 0 ? _f : (_j = (_h = (_g = resp === null || resp === void 0 ? void 0 : resp.choices) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.message) === null || _j === void 0 ? void 0 : _j.content;
228
- if (!textOutput || typeof textOutput !== 'string') {
229
- throw new Error('Unexpected AI response format');
214
+ if (attachmentFiles.length !== 0) {
215
+ //create prompt for OpenAI
216
+ const compiledOutputFields = this.compileOutputFieldsTemplates(record);
217
+ const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
218
+ Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
219
+ Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
220
+ Image URLs:`;
221
+ //send prompt to OpenAI and get response
222
+ const chatResponse = yield this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
223
+ const resp = chatResponse.response;
224
+ const topLevelError = chatResponse.error;
225
+ if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
226
+ throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
227
+ }
228
+ const textOutput = (_f = (_e = (_d = (_c = (_b = (_a = resp === null || resp === void 0 ? void 0 : resp.output) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : resp === null || resp === void 0 ? void 0 : resp.output_text) !== null && _f !== void 0 ? _f : (_j = (_h = (_g = resp === null || resp === void 0 ? void 0 : resp.choices) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.message) === null || _j === void 0 ? void 0 : _j.content;
229
+ if (!textOutput || typeof textOutput !== 'string') {
230
+ throw new Error('Unexpected AI response format');
231
+ }
232
+ //parse response and update record
233
+ const resData = JSON.parse(textOutput);
234
+ return resData;
230
235
  }
231
- //parse response and update record
232
- const resData = JSON.parse(textOutput);
233
- return resData;
236
+ ;
234
237
  }));
235
238
  const result = yield Promise.all(tasks);
236
239
  return { result };
@@ -315,6 +318,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
315
318
  if (isAllowedToSave.ok !== false) {
316
319
  const selectedIds = body.selectedIds || [];
317
320
  const fieldsToUpdate = body.fields || {};
321
+ const saveImages = body.saveImages;
318
322
  const outputImageFields = [];
319
323
  if (this.options.generateImages) {
320
324
  for (const [key, value] of Object.entries(this.options.generateImages)) {
@@ -327,7 +331,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
327
331
  for (const [key, value] of Object.entries(outputImageFields)) {
328
332
  const columnPlugin = this.adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.resourceConfig.resourceId &&
329
333
  p.pluginOptions.pathColumnName === value);
330
- if (columnPlugin) {
334
+ if (columnPlugin && saveImages) {
331
335
  if (columnPlugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
332
336
  if (oldRecord[value]) {
333
337
  // put tag to delete old file
@@ -339,7 +343,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
339
343
  console.error(`Error setting tag to true for object ${oldRecord[value]}. File will not be auto-cleaned up`, e);
340
344
  }
341
345
  }
342
- if (fieldsToUpdate[idx][key] !== null) {
346
+ if (fieldsToUpdate[idx][key] && fieldsToUpdate[idx][key] !== null) {
343
347
  // remove tag from new file
344
348
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
345
349
  yield columnPlugin.pluginOptions.storageAdapter.markKeyForNotDeletation(fieldsToUpdate[idx][value]);
@@ -429,28 +433,33 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
429
433
  const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
430
434
  const prompt = this.compileGenerationFieldTemplates(record)[key];
431
435
  let images;
432
- if (STUB_MODE) {
433
- yield new Promise((resolve) => setTimeout(resolve, 2000));
434
- images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
436
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
437
+ return { key, images: [] };
435
438
  }
436
439
  else {
437
- let generationAdapter;
438
- if (this.options.generateImages[key].adapter) {
439
- generationAdapter = this.options.generateImages[key].adapter;
440
+ if (STUB_MODE) {
441
+ yield new Promise((resolve) => setTimeout(resolve, 2000));
442
+ images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
440
443
  }
441
444
  else {
442
- generationAdapter = this.options.imageGenerationAdapter;
443
- ``;
445
+ let generationAdapter;
446
+ if (this.options.generateImages[key].adapter) {
447
+ generationAdapter = this.options.generateImages[key].adapter;
448
+ }
449
+ else {
450
+ generationAdapter = this.options.imageGenerationAdapter;
451
+ ``;
452
+ }
453
+ const resp = yield generationAdapter.generate({
454
+ prompt,
455
+ inputFiles: attachmentFiles,
456
+ n: 1,
457
+ size: this.options.generateImages[key].outputSize,
458
+ });
459
+ images = resp.imageURLs[0];
444
460
  }
445
- const resp = yield generationAdapter.generate({
446
- prompt,
447
- inputFiles: attachmentFiles,
448
- n: 1,
449
- size: this.options.generateImages[key].outputSize,
450
- });
451
- images = resp.imageURLs[0];
461
+ return { key, images };
452
462
  }
453
- return { key, images };
454
463
  }));
455
464
  const fieldResults = yield Promise.all(fieldTasks);
456
465
  const recordResult = {};
package/index.ts CHANGED
@@ -237,31 +237,33 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
237
237
 
238
238
  //recieve image URLs to analyze
239
239
  const attachmentFiles = await this.options.attachFiles({ record: record });
240
- //create prompt for OpenAI
241
- const compiledOutputFields = this.compileOutputFieldsTemplates(record);
242
- const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
243
- Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
244
- Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
245
- Image URLs:`;
246
-
247
- //send prompt to OpenAI and get response
248
- const chatResponse = await this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
249
-
250
- const resp: any = (chatResponse as any).response;
251
- const topLevelError = (chatResponse as any).error;
252
- if (topLevelError || resp?.error) {
253
- throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
254
- }
240
+ if (attachmentFiles.length !== 0) {
241
+ //create prompt for OpenAI
242
+ const compiledOutputFields = this.compileOutputFieldsTemplates(record);
243
+ const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
244
+ Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
245
+ Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
246
+ Image URLs:`;
247
+
248
+ //send prompt to OpenAI and get response
249
+ const chatResponse = await this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
250
+
251
+ const resp: any = (chatResponse as any).response;
252
+ const topLevelError = (chatResponse as any).error;
253
+ if (topLevelError || resp?.error) {
254
+ throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
255
+ }
255
256
 
256
- const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
257
- if (!textOutput || typeof textOutput !== 'string') {
258
- throw new Error('Unexpected AI response format');
259
- }
257
+ const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
258
+ if (!textOutput || typeof textOutput !== 'string') {
259
+ throw new Error('Unexpected AI response format');
260
+ }
260
261
 
261
- //parse response and update record
262
- const resData = JSON.parse(textOutput);
262
+ //parse response and update record
263
+ const resData = JSON.parse(textOutput);
263
264
 
264
- return resData;
265
+ return resData;
266
+ };
265
267
  });
266
268
 
267
269
  const result = await Promise.all(tasks);
@@ -360,6 +362,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
360
362
  if (isAllowedToSave.ok !== false) {
361
363
  const selectedIds = body.selectedIds || [];
362
364
  const fieldsToUpdate = body.fields || {};
365
+ const saveImages = body.saveImages;
363
366
  const outputImageFields = [];
364
367
  if (this.options.generateImages) {
365
368
  for (const [key, value] of Object.entries(this.options.generateImages)) {
@@ -374,7 +377,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
374
377
  p.resourceConfig!.resourceId === this.resourceConfig.resourceId &&
375
378
  p.pluginOptions.pathColumnName === value
376
379
  );
377
- if (columnPlugin) {
380
+ if (columnPlugin && saveImages) {
378
381
  if(columnPlugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
379
382
  if (oldRecord[value]) {
380
383
  // put tag to delete old file
@@ -385,7 +388,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
385
388
  console.error(`Error setting tag to true for object ${oldRecord[value]}. File will not be auto-cleaned up`, e);
386
389
  }
387
390
  }
388
- if (fieldsToUpdate[idx][key] !== null) {
391
+ if (fieldsToUpdate[idx][key] && fieldsToUpdate[idx][key] !== null) {
389
392
  // remove tag from new file
390
393
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
391
394
  await columnPlugin.pluginOptions.storageAdapter.markKeyForNotDeletation(fieldsToUpdate[idx][value]);
@@ -478,27 +481,31 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
478
481
  const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
479
482
  const prompt = this.compileGenerationFieldTemplates(record)[key];
480
483
  let images;
481
- if (STUB_MODE) {
482
- await new Promise((resolve) => setTimeout(resolve, 2000));
483
- images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
484
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
485
+ return { key, images: [] };
484
486
  } else {
485
- let generationAdapter;
486
- if (this.options.generateImages[key].adapter) {
487
- generationAdapter = this.options.generateImages[key].adapter;
487
+ if (STUB_MODE) {
488
+ await new Promise((resolve) => setTimeout(resolve, 2000));
489
+ images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
488
490
  } else {
489
- generationAdapter = this.options.imageGenerationAdapter;``
490
- }
491
- const resp = await generationAdapter.generate(
492
- {
493
- prompt,
494
- inputFiles: attachmentFiles,
495
- n: 1,
496
- size: this.options.generateImages[key].outputSize,
491
+ let generationAdapter;
492
+ if (this.options.generateImages[key].adapter) {
493
+ generationAdapter = this.options.generateImages[key].adapter;
494
+ } else {
495
+ generationAdapter = this.options.imageGenerationAdapter;``
497
496
  }
498
- )
499
- images = resp.imageURLs[0];
500
- }
501
- return { key, images };
497
+ const resp = await generationAdapter.generate(
498
+ {
499
+ prompt,
500
+ inputFiles: attachmentFiles,
501
+ n: 1,
502
+ size: this.options.generateImages[key].outputSize,
503
+ }
504
+ )
505
+ images = resp.imageURLs[0];
506
+ }
507
+ return { key, images };
508
+ }
502
509
  });
503
510
 
504
511
  const fieldResults = await Promise.all(fieldTasks);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },