@adminforth/bulk-ai-flow 1.5.4 → 1.5.6
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 +2 -2
- package/custom/visionAction.vue +5 -2
- package/dist/custom/visionAction.vue +5 -2
- package/dist/index.js +47 -36
- package/index.ts +50 -42
- package/package.json +1 -1
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,
|
|
15
|
-
total size is
|
|
14
|
+
sent 182,651 bytes received 134 bytes 365,570.00 bytes/sec
|
|
15
|
+
total size is 182,116 speedup is 1.00
|
package/custom/visionAction.vue
CHANGED
|
@@ -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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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]);
|
|
@@ -380,6 +383,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
380
383
|
attachmentFiles = yield this.options.attachFiles({ record });
|
|
381
384
|
}
|
|
382
385
|
const images = yield Promise.all((new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
|
|
386
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
383
389
|
if (STUB_MODE) {
|
|
384
390
|
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
385
391
|
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
@@ -430,28 +436,33 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
430
436
|
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
437
|
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
432
438
|
let images;
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
439
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
440
|
+
return { key, images: [] };
|
|
436
441
|
}
|
|
437
442
|
else {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
443
|
+
if (STUB_MODE) {
|
|
444
|
+
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
445
|
+
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
441
446
|
}
|
|
442
447
|
else {
|
|
443
|
-
generationAdapter
|
|
444
|
-
|
|
448
|
+
let generationAdapter;
|
|
449
|
+
if (this.options.generateImages[key].adapter) {
|
|
450
|
+
generationAdapter = this.options.generateImages[key].adapter;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
generationAdapter = this.options.imageGenerationAdapter;
|
|
454
|
+
``;
|
|
455
|
+
}
|
|
456
|
+
const resp = yield generationAdapter.generate({
|
|
457
|
+
prompt,
|
|
458
|
+
inputFiles: attachmentFiles,
|
|
459
|
+
n: 1,
|
|
460
|
+
size: this.options.generateImages[key].outputSize,
|
|
461
|
+
});
|
|
462
|
+
images = resp.imageURLs[0];
|
|
445
463
|
}
|
|
446
|
-
|
|
447
|
-
prompt,
|
|
448
|
-
inputFiles: attachmentFiles,
|
|
449
|
-
n: 1,
|
|
450
|
-
size: this.options.generateImages[key].outputSize,
|
|
451
|
-
});
|
|
452
|
-
images = resp.imageURLs[0];
|
|
464
|
+
return { key, images };
|
|
453
465
|
}
|
|
454
|
-
return { key, images };
|
|
455
466
|
}));
|
|
456
467
|
const fieldResults = yield Promise.all(fieldTasks);
|
|
457
468
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
262
|
-
|
|
262
|
+
//parse response and update record
|
|
263
|
+
const resData = JSON.parse(textOutput);
|
|
263
264
|
|
|
264
|
-
|
|
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]);
|
|
@@ -426,7 +428,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
426
428
|
}
|
|
427
429
|
const images = await Promise.all(
|
|
428
430
|
(new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(async () => {
|
|
429
|
-
|
|
431
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
430
434
|
if (STUB_MODE) {
|
|
431
435
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
432
436
|
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
@@ -479,27 +483,31 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
479
483
|
const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
|
|
480
484
|
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
481
485
|
let images;
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
486
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
487
|
+
return { key, images: [] };
|
|
485
488
|
} else {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
if (STUB_MODE) {
|
|
490
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
491
|
+
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
489
492
|
} else {
|
|
490
|
-
generationAdapter
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
{
|
|
494
|
-
|
|
495
|
-
inputFiles: attachmentFiles,
|
|
496
|
-
n: 1,
|
|
497
|
-
size: this.options.generateImages[key].outputSize,
|
|
493
|
+
let generationAdapter;
|
|
494
|
+
if (this.options.generateImages[key].adapter) {
|
|
495
|
+
generationAdapter = this.options.generateImages[key].adapter;
|
|
496
|
+
} else {
|
|
497
|
+
generationAdapter = this.options.imageGenerationAdapter;``
|
|
498
498
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
499
|
+
const resp = await generationAdapter.generate(
|
|
500
|
+
{
|
|
501
|
+
prompt,
|
|
502
|
+
inputFiles: attachmentFiles,
|
|
503
|
+
n: 1,
|
|
504
|
+
size: this.options.generateImages[key].outputSize,
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
images = resp.imageURLs[0];
|
|
508
|
+
}
|
|
509
|
+
return { key, images };
|
|
510
|
+
}
|
|
503
511
|
});
|
|
504
512
|
|
|
505
513
|
const fieldResults = await Promise.all(fieldTasks);
|