@adminforth/bulk-ai-flow 1.5.4 → 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,531 bytes received 134 bytes 365,330.00 bytes/sec
15
- total size is 181,996 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
@@ -310,7 +310,7 @@ async function prepareDataForSave() {
310
310
 
311
311
  async function convertImages(fieldName, img) {
312
312
  let imgBlob;
313
- if (img.startsWith('data:')) {
313
+ if (typeof img === 'string' && img.startsWith('data:')) {
314
314
  const base64 = img.split(',')[1];
315
315
  const mimeType = img.split(';')[0].split(':')[1];
316
316
  const byteCharacters = atob(base64);
@@ -320,7 +320,7 @@ async function convertImages(fieldName, img) {
320
320
  }
321
321
  const byteArray = new Uint8Array(byteNumbers);
322
322
  imgBlob = new Blob([byteArray], { type: mimeType });
323
- } else {
323
+ } else if (typeof img === 'string') {
324
324
  imgBlob = await fetch(
325
325
  `/adminapi/v1/plugin/${props.meta.outputImagesPluginInstanceIds[fieldName]}/cors-proxy?url=${encodeURIComponent(img)}`
326
326
  ).then(res => { return res.blob() });
@@ -447,6 +447,9 @@ async function saveData() {
447
447
  for (const item of reqData) {
448
448
  for (const [key, value] of Object.entries(item)) {
449
449
  if(props.meta.outputImageFields?.includes(key)) {
450
+ if (!value) {
451
+ continue;
452
+ }
450
453
  const p = uploadImage(value, item[primaryKey], key).then(result => {
451
454
  item[key] = result;
452
455
  });
@@ -310,7 +310,7 @@ async function prepareDataForSave() {
310
310
 
311
311
  async function convertImages(fieldName, img) {
312
312
  let imgBlob;
313
- if (img.startsWith('data:')) {
313
+ if (typeof img === 'string' && img.startsWith('data:')) {
314
314
  const base64 = img.split(',')[1];
315
315
  const mimeType = img.split(';')[0].split(':')[1];
316
316
  const byteCharacters = atob(base64);
@@ -320,7 +320,7 @@ async function convertImages(fieldName, img) {
320
320
  }
321
321
  const byteArray = new Uint8Array(byteNumbers);
322
322
  imgBlob = new Blob([byteArray], { type: mimeType });
323
- } else {
323
+ } else if (typeof img === 'string') {
324
324
  imgBlob = await fetch(
325
325
  `/adminapi/v1/plugin/${props.meta.outputImagesPluginInstanceIds[fieldName]}/cors-proxy?url=${encodeURIComponent(img)}`
326
326
  ).then(res => { return res.blob() });
@@ -447,6 +447,9 @@ async function saveData() {
447
447
  for (const item of reqData) {
448
448
  for (const [key, value] of Object.entries(item)) {
449
449
  if(props.meta.outputImageFields?.includes(key)) {
450
+ if (!value) {
451
+ continue;
452
+ }
450
453
  const p = uploadImage(value, item[primaryKey], key).then(result => {
451
454
  item[key] = result;
452
455
  });
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 };
@@ -340,7 +343,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
340
343
  console.error(`Error setting tag to true for object ${oldRecord[value]}. File will not be auto-cleaned up`, e);
341
344
  }
342
345
  }
343
- if (fieldsToUpdate[idx][key] !== null) {
346
+ if (fieldsToUpdate[idx][key] && fieldsToUpdate[idx][key] !== null) {
344
347
  // remove tag from new file
345
348
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
346
349
  yield columnPlugin.pluginOptions.storageAdapter.markKeyForNotDeletation(fieldsToUpdate[idx][value]);
@@ -430,28 +433,33 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
430
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* () {
431
434
  const prompt = this.compileGenerationFieldTemplates(record)[key];
432
435
  let images;
433
- if (STUB_MODE) {
434
- yield new Promise((resolve) => setTimeout(resolve, 2000));
435
- images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
436
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
437
+ return { key, images: [] };
436
438
  }
437
439
  else {
438
- let generationAdapter;
439
- if (this.options.generateImages[key].adapter) {
440
- 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)}`;
441
443
  }
442
444
  else {
443
- generationAdapter = this.options.imageGenerationAdapter;
444
- ``;
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];
445
460
  }
446
- const resp = yield generationAdapter.generate({
447
- prompt,
448
- inputFiles: attachmentFiles,
449
- n: 1,
450
- size: this.options.generateImages[key].outputSize,
451
- });
452
- images = resp.imageURLs[0];
461
+ return { key, images };
453
462
  }
454
- return { key, images };
455
463
  }));
456
464
  const fieldResults = yield Promise.all(fieldTasks);
457
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);
@@ -386,7 +388,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
386
388
  console.error(`Error setting tag to true for object ${oldRecord[value]}. File will not be auto-cleaned up`, e);
387
389
  }
388
390
  }
389
- if (fieldsToUpdate[idx][key] !== null) {
391
+ if (fieldsToUpdate[idx][key] && fieldsToUpdate[idx][key] !== null) {
390
392
  // remove tag from new file
391
393
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
392
394
  await columnPlugin.pluginOptions.storageAdapter.markKeyForNotDeletation(fieldsToUpdate[idx][value]);
@@ -479,27 +481,31 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
479
481
  const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
480
482
  const prompt = this.compileGenerationFieldTemplates(record)[key];
481
483
  let images;
482
- if (STUB_MODE) {
483
- await new Promise((resolve) => setTimeout(resolve, 2000));
484
- images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
484
+ if (this.options.attachFiles && attachmentFiles.length === 0) {
485
+ return { key, images: [] };
485
486
  } else {
486
- let generationAdapter;
487
- if (this.options.generateImages[key].adapter) {
488
- 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)}`;
489
490
  } else {
490
- generationAdapter = this.options.imageGenerationAdapter;``
491
- }
492
- const resp = await generationAdapter.generate(
493
- {
494
- prompt,
495
- inputFiles: attachmentFiles,
496
- n: 1,
497
- 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;``
498
496
  }
499
- )
500
- images = resp.imageURLs[0];
501
- }
502
- 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
+ }
503
509
  });
504
510
 
505
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.4",
3
+ "version": "1.5.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },