@adminforth/bulk-ai-flow 1.17.9 → 1.18.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.
- package/build.log +2 -2
- package/custom/VisionAction.vue +60 -18
- package/dist/custom/VisionAction.vue +60 -18
- package/dist/index.js +97 -87
- package/index.ts +92 -86
- package/package.json +1 -1
package/build.log
CHANGED
|
@@ -13,5 +13,5 @@ custom/package-lock.json
|
|
|
13
13
|
custom/package.json
|
|
14
14
|
custom/tsconfig.json
|
|
15
15
|
|
|
16
|
-
sent
|
|
17
|
-
total size is
|
|
16
|
+
sent 92,827 bytes received 172 bytes 185,998.00 bytes/sec
|
|
17
|
+
total size is 92,182 speedup is 0.99
|
package/custom/VisionAction.vue
CHANGED
|
@@ -1028,19 +1028,23 @@ async function regenerateCell(recordInfo: any) {
|
|
|
1028
1028
|
let generationPromptsForField = {};
|
|
1029
1029
|
if (actionType === 'analyze') {
|
|
1030
1030
|
generationPromptsForField = generationPrompts.value.imageFieldsPrompts || {};
|
|
1031
|
+
isAnalizingFields.value = true;
|
|
1031
1032
|
} else if (actionType === 'analyze_no_images') {
|
|
1032
1033
|
generationPromptsForField = generationPrompts.value.plainFieldsPrompts || {};
|
|
1034
|
+
isAnalizingImages.value = true;
|
|
1033
1035
|
}
|
|
1034
1036
|
|
|
1037
|
+
let jobId;
|
|
1035
1038
|
let res;
|
|
1036
1039
|
try {
|
|
1037
1040
|
res = await callAdminForthApi({
|
|
1038
|
-
path: `/plugin/${props.meta.pluginInstanceId}/
|
|
1041
|
+
path: `/plugin/${props.meta.pluginInstanceId}/create-job`,
|
|
1039
1042
|
method: 'POST',
|
|
1040
1043
|
body: {
|
|
1041
1044
|
fieldToRegenerate: recordInfo.fieldName,
|
|
1042
1045
|
recordId: recordInfo.recordId,
|
|
1043
|
-
|
|
1046
|
+
action: actionType,
|
|
1047
|
+
actionType: "regenerate_cell",
|
|
1044
1048
|
prompt: generationPromptsForField[recordInfo.fieldName] || null,
|
|
1045
1049
|
},
|
|
1046
1050
|
silentError: true,
|
|
@@ -1059,23 +1063,61 @@ async function regenerateCell(recordInfo: any) {
|
|
|
1059
1063
|
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1060
1064
|
return;
|
|
1061
1065
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1066
|
+
jobId = res.jobId;
|
|
1067
|
+
res = {}
|
|
1068
|
+
do {
|
|
1069
|
+
res = await callAdminForthApi({
|
|
1070
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get-job-status`,
|
|
1071
|
+
method: 'POST',
|
|
1072
|
+
body: { jobId },
|
|
1073
|
+
silentError: true,
|
|
1074
|
+
});
|
|
1075
|
+
if (actionType === 'analyze') {
|
|
1076
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillFieldsFromImages));
|
|
1077
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1078
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillPlainFields));
|
|
1079
|
+
}
|
|
1080
|
+
} while (res.job?.status === 'in_progress');
|
|
1081
|
+
if (res.job?.status === 'failed' || !res.ok || !res) {
|
|
1082
|
+
adminforth.alert({
|
|
1083
|
+
message: t(`Regeneration action failed for record: ${recordInfo.recordId}. Error: ${res.job?.error || 'Unknown error'}`),
|
|
1084
|
+
variant: 'danger',
|
|
1085
|
+
timeout: 'unlimited',
|
|
1086
|
+
});
|
|
1087
|
+
if (actionType === 'analyze') {
|
|
1088
|
+
imageToTextErrorMessages.value[recordInfo.recordId][recordInfo.fieldName] = res.job?.error || 'Unknown error';
|
|
1089
|
+
isAnalizingFields.value = false;
|
|
1090
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1091
|
+
textToTextErrorMessages.value[recordInfo.recordId][recordInfo.fieldName] = res.job?.error || 'Unknown error';
|
|
1092
|
+
isAnalizingImages.value = false;
|
|
1093
|
+
}
|
|
1094
|
+
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1095
|
+
return;
|
|
1096
|
+
} else if (res.job?.status === 'completed') {
|
|
1097
|
+
const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordInfo.recordId));
|
|
1098
|
+
|
|
1099
|
+
const pk = selected.value[index]?.[primaryKey];
|
|
1100
|
+
if (pk) {
|
|
1101
|
+
selected.value[index] = {
|
|
1102
|
+
...selected.value[index],
|
|
1103
|
+
...res.job.result,
|
|
1104
|
+
isChecked: true,
|
|
1105
|
+
[primaryKey]: pk,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
if (actionType === 'analyze') {
|
|
1109
|
+
if (imageToTextErrorMessages.value[index]) {
|
|
1110
|
+
imageToTextErrorMessages.value[index][recordInfo.fieldName] = '';
|
|
1111
|
+
}
|
|
1112
|
+
isAnalizingFields.value = false;
|
|
1113
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1114
|
+
if (textToTextErrorMessages.value[index]) {
|
|
1115
|
+
textToTextErrorMessages.value[index][recordInfo.fieldName] = '';
|
|
1116
|
+
}
|
|
1117
|
+
isAnalizingImages.value = false;
|
|
1118
|
+
}
|
|
1119
|
+
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1077
1120
|
}
|
|
1078
|
-
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1079
1121
|
}
|
|
1080
1122
|
|
|
1081
1123
|
const beforeUnloadHandler = (event) => {
|
|
@@ -1028,19 +1028,23 @@ async function regenerateCell(recordInfo: any) {
|
|
|
1028
1028
|
let generationPromptsForField = {};
|
|
1029
1029
|
if (actionType === 'analyze') {
|
|
1030
1030
|
generationPromptsForField = generationPrompts.value.imageFieldsPrompts || {};
|
|
1031
|
+
isAnalizingFields.value = true;
|
|
1031
1032
|
} else if (actionType === 'analyze_no_images') {
|
|
1032
1033
|
generationPromptsForField = generationPrompts.value.plainFieldsPrompts || {};
|
|
1034
|
+
isAnalizingImages.value = true;
|
|
1033
1035
|
}
|
|
1034
1036
|
|
|
1037
|
+
let jobId;
|
|
1035
1038
|
let res;
|
|
1036
1039
|
try {
|
|
1037
1040
|
res = await callAdminForthApi({
|
|
1038
|
-
path: `/plugin/${props.meta.pluginInstanceId}/
|
|
1041
|
+
path: `/plugin/${props.meta.pluginInstanceId}/create-job`,
|
|
1039
1042
|
method: 'POST',
|
|
1040
1043
|
body: {
|
|
1041
1044
|
fieldToRegenerate: recordInfo.fieldName,
|
|
1042
1045
|
recordId: recordInfo.recordId,
|
|
1043
|
-
|
|
1046
|
+
action: actionType,
|
|
1047
|
+
actionType: "regenerate_cell",
|
|
1044
1048
|
prompt: generationPromptsForField[recordInfo.fieldName] || null,
|
|
1045
1049
|
},
|
|
1046
1050
|
silentError: true,
|
|
@@ -1059,23 +1063,61 @@ async function regenerateCell(recordInfo: any) {
|
|
|
1059
1063
|
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1060
1064
|
return;
|
|
1061
1065
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1066
|
+
jobId = res.jobId;
|
|
1067
|
+
res = {}
|
|
1068
|
+
do {
|
|
1069
|
+
res = await callAdminForthApi({
|
|
1070
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get-job-status`,
|
|
1071
|
+
method: 'POST',
|
|
1072
|
+
body: { jobId },
|
|
1073
|
+
silentError: true,
|
|
1074
|
+
});
|
|
1075
|
+
if (actionType === 'analyze') {
|
|
1076
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillFieldsFromImages));
|
|
1077
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1078
|
+
await new Promise(resolve => setTimeout(resolve, props.meta.refreshRates?.fillPlainFields));
|
|
1079
|
+
}
|
|
1080
|
+
} while (res.job?.status === 'in_progress');
|
|
1081
|
+
if (res.job?.status === 'failed' || !res.ok || !res) {
|
|
1082
|
+
adminforth.alert({
|
|
1083
|
+
message: t(`Regeneration action failed for record: ${recordInfo.recordId}. Error: ${res.job?.error || 'Unknown error'}`),
|
|
1084
|
+
variant: 'danger',
|
|
1085
|
+
timeout: 'unlimited',
|
|
1086
|
+
});
|
|
1087
|
+
if (actionType === 'analyze') {
|
|
1088
|
+
imageToTextErrorMessages.value[recordInfo.recordId][recordInfo.fieldName] = res.job?.error || 'Unknown error';
|
|
1089
|
+
isAnalizingFields.value = false;
|
|
1090
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1091
|
+
textToTextErrorMessages.value[recordInfo.recordId][recordInfo.fieldName] = res.job?.error || 'Unknown error';
|
|
1092
|
+
isAnalizingImages.value = false;
|
|
1093
|
+
}
|
|
1094
|
+
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1095
|
+
return;
|
|
1096
|
+
} else if (res.job?.status === 'completed') {
|
|
1097
|
+
const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordInfo.recordId));
|
|
1098
|
+
|
|
1099
|
+
const pk = selected.value[index]?.[primaryKey];
|
|
1100
|
+
if (pk) {
|
|
1101
|
+
selected.value[index] = {
|
|
1102
|
+
...selected.value[index],
|
|
1103
|
+
...res.job.result,
|
|
1104
|
+
isChecked: true,
|
|
1105
|
+
[primaryKey]: pk,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
if (actionType === 'analyze') {
|
|
1109
|
+
if (imageToTextErrorMessages.value[index]) {
|
|
1110
|
+
imageToTextErrorMessages.value[index][recordInfo.fieldName] = '';
|
|
1111
|
+
}
|
|
1112
|
+
isAnalizingFields.value = false;
|
|
1113
|
+
} else if (actionType === 'analyze_no_images') {
|
|
1114
|
+
if (textToTextErrorMessages.value[index]) {
|
|
1115
|
+
textToTextErrorMessages.value[index][recordInfo.fieldName] = '';
|
|
1116
|
+
}
|
|
1117
|
+
isAnalizingImages.value = false;
|
|
1118
|
+
}
|
|
1119
|
+
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1077
1120
|
}
|
|
1078
|
-
regeneratingFieldsStatus.value[recordInfo.recordId][recordInfo.fieldName] = false;
|
|
1079
1121
|
}
|
|
1080
1122
|
|
|
1081
1123
|
const beforeUnloadHandler = (event) => {
|
package/dist/index.js
CHANGED
|
@@ -376,6 +376,99 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
376
376
|
}
|
|
377
377
|
});
|
|
378
378
|
}
|
|
379
|
+
regenerateCell(jobId, fieldToRegenerate, recordId, actionType, prompt) {
|
|
380
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
381
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
382
|
+
if (!fieldToRegenerate || !recordId || !actionType) {
|
|
383
|
+
jobs.set(jobId, { status: 'failed', error: 'Missing parameters' });
|
|
384
|
+
//return { ok: false, error: "Missing parameters" };
|
|
385
|
+
}
|
|
386
|
+
if (!prompt) {
|
|
387
|
+
if (actionType === 'analyze') {
|
|
388
|
+
prompt = this.options.fillFieldsFromImages ? this.options.fillFieldsFromImages[fieldToRegenerate] : null;
|
|
389
|
+
}
|
|
390
|
+
else if (actionType === 'analyze_no_images') {
|
|
391
|
+
prompt = this.options.fillPlainFields ? this.options.fillPlainFields[fieldToRegenerate] : null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
395
|
+
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, recordId)]);
|
|
396
|
+
let promptToPass = JSON.stringify({ [fieldToRegenerate]: prompt });
|
|
397
|
+
if (STUB_MODE) {
|
|
398
|
+
yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
399
|
+
// return { ok: true, result: {[fieldToRegenerate]: "stub value"} };
|
|
400
|
+
jobs.set(jobId, { status: 'completed', result: { [fieldToRegenerate]: "stub value" } });
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
if (actionType === 'analyze') {
|
|
404
|
+
const compiledPropmt = yield this.compileOutputFieldsTemplates(record, promptToPass);
|
|
405
|
+
const finalPrompt = this.getPromptForImageAnalysis(compiledPropmt);
|
|
406
|
+
const attachmentFiles = yield this.options.attachFiles({ record: record });
|
|
407
|
+
if (attachmentFiles.length === 0) {
|
|
408
|
+
// return { ok: false, error: "No source images found" };
|
|
409
|
+
jobs.set(jobId, { status: 'failed', error: "No source images found" });
|
|
410
|
+
}
|
|
411
|
+
let visionAdapterResponse;
|
|
412
|
+
try {
|
|
413
|
+
visionAdapterResponse = yield this.options.visionAdapter.generate({ prompt: finalPrompt, inputFileUrls: attachmentFiles });
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
// return { ok: false, error: 'AI provider refused to analyze images' };
|
|
417
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analyze images' });
|
|
418
|
+
}
|
|
419
|
+
const resp = visionAdapterResponse.response;
|
|
420
|
+
const topLevelError = visionAdapterResponse.error;
|
|
421
|
+
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
422
|
+
// return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError.message || resp?.error.message)}` };
|
|
423
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError.message || (resp === null || resp === void 0 ? void 0 : resp.error.message))}` });
|
|
424
|
+
}
|
|
425
|
+
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;
|
|
426
|
+
if (!textOutput || typeof textOutput !== 'string') {
|
|
427
|
+
// return { ok: false, error: 'AI response is not valid text' };
|
|
428
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid text' });
|
|
429
|
+
}
|
|
430
|
+
let resData;
|
|
431
|
+
try {
|
|
432
|
+
resData = JSON.parse(textOutput);
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
// return { ok: false, error: 'AI response is not valid JSON. Probably attached invalid image URL' };
|
|
436
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid JSON. Probably attached invalid image URL' });
|
|
437
|
+
}
|
|
438
|
+
// return { ok: true, result: resData };
|
|
439
|
+
jobs.set(jobId, { status: 'completed', result: resData });
|
|
440
|
+
}
|
|
441
|
+
else if (actionType === 'analyze_no_images') {
|
|
442
|
+
const compiledPropmt = yield this.compileOutputFieldsTemplatesNoImage(record, promptToPass);
|
|
443
|
+
const finalPrompt = this.getPromptForPlainFields(compiledPropmt);
|
|
444
|
+
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
445
|
+
let resp;
|
|
446
|
+
try {
|
|
447
|
+
const { content: chatResponse, error: topLevelError } = yield this.options.textCompleteAdapter.complete(finalPrompt, [], numberOfTokens);
|
|
448
|
+
if (topLevelError) {
|
|
449
|
+
// return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError)}` };
|
|
450
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError)}` });
|
|
451
|
+
}
|
|
452
|
+
resp = chatResponse;
|
|
453
|
+
}
|
|
454
|
+
catch (e) {
|
|
455
|
+
// return { ok: false, error: 'AI provider refused to analyze plain fields' };
|
|
456
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analyze plain fields' });
|
|
457
|
+
}
|
|
458
|
+
let resData;
|
|
459
|
+
try {
|
|
460
|
+
resData = JSON.parse(resp);
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
// return { ok: false, error: 'AI response is not valid JSON' };
|
|
464
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid JSON' });
|
|
465
|
+
}
|
|
466
|
+
// return { ok: true, result: resData };
|
|
467
|
+
jobs.set(jobId, { status: 'completed', result: resData });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
379
472
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
380
473
|
const _super = Object.create(null, {
|
|
381
474
|
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
@@ -759,6 +852,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
759
852
|
}
|
|
760
853
|
this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers);
|
|
761
854
|
break;
|
|
855
|
+
case 'regenerate_cell':
|
|
856
|
+
const fieldToRegenerate = body.fieldToRegenerate;
|
|
857
|
+
this.regenerateCell(jobId, fieldToRegenerate, recordId, body.action, body.prompt);
|
|
858
|
+
break;
|
|
762
859
|
default:
|
|
763
860
|
jobs.set(jobId, { status: "failed", error: "Unknown action type" });
|
|
764
861
|
}
|
|
@@ -836,92 +933,5 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
836
933
|
}
|
|
837
934
|
})
|
|
838
935
|
});
|
|
839
|
-
server.endpoint({
|
|
840
|
-
method: 'POST',
|
|
841
|
-
path: `/plugin/${this.pluginInstanceId}/regenerate-cell`,
|
|
842
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
843
|
-
var _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
844
|
-
const recordId = body.recordId;
|
|
845
|
-
const fieldToRegenerate = body.fieldToRegenerate;
|
|
846
|
-
let prompt = body.prompt;
|
|
847
|
-
const actionType = body.actionType;
|
|
848
|
-
if (!fieldToRegenerate || !recordId || !actionType) {
|
|
849
|
-
return { ok: false, error: "Missing parameters" };
|
|
850
|
-
}
|
|
851
|
-
if (!prompt) {
|
|
852
|
-
if (actionType === 'analyze') {
|
|
853
|
-
prompt = this.options.fillFieldsFromImages ? this.options.fillFieldsFromImages[fieldToRegenerate] : null;
|
|
854
|
-
}
|
|
855
|
-
else if (actionType === 'analyze_no_images') {
|
|
856
|
-
prompt = this.options.fillPlainFields ? this.options.fillPlainFields[fieldToRegenerate] : null;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
860
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, recordId)]);
|
|
861
|
-
let promptToPass = JSON.stringify({ [fieldToRegenerate]: prompt });
|
|
862
|
-
if (STUB_MODE) {
|
|
863
|
-
yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
864
|
-
return { ok: true, result: { [fieldToRegenerate]: "stub value" } };
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
if (actionType === 'analyze') {
|
|
868
|
-
const compiledPropmt = yield this.compileOutputFieldsTemplates(record, promptToPass);
|
|
869
|
-
const finalPrompt = this.getPromptForImageAnalysis(compiledPropmt);
|
|
870
|
-
const attachmentFiles = yield this.options.attachFiles({ record: record });
|
|
871
|
-
if (attachmentFiles.length === 0) {
|
|
872
|
-
return { ok: false, error: "No source images found" };
|
|
873
|
-
}
|
|
874
|
-
let visionAdapterResponse;
|
|
875
|
-
try {
|
|
876
|
-
visionAdapterResponse = yield this.options.visionAdapter.generate({ prompt: finalPrompt, inputFileUrls: attachmentFiles });
|
|
877
|
-
}
|
|
878
|
-
catch (e) {
|
|
879
|
-
return { ok: false, error: 'AI provider refused to analyze images' };
|
|
880
|
-
}
|
|
881
|
-
const resp = visionAdapterResponse.response;
|
|
882
|
-
const topLevelError = visionAdapterResponse.error;
|
|
883
|
-
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
884
|
-
return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError.message || (resp === null || resp === void 0 ? void 0 : resp.error.message))}` };
|
|
885
|
-
}
|
|
886
|
-
const textOutput = (_g = (_f = (_e = (_d = (_c = (_b = resp === null || resp === void 0 ? void 0 : resp.output) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.text) !== null && _f !== void 0 ? _f : resp === null || resp === void 0 ? void 0 : resp.output_text) !== null && _g !== void 0 ? _g : (_k = (_j = (_h = resp === null || resp === void 0 ? void 0 : resp.choices) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.message) === null || _k === void 0 ? void 0 : _k.content;
|
|
887
|
-
if (!textOutput || typeof textOutput !== 'string') {
|
|
888
|
-
return { ok: false, error: 'AI response is not valid text' };
|
|
889
|
-
}
|
|
890
|
-
let resData;
|
|
891
|
-
try {
|
|
892
|
-
resData = JSON.parse(textOutput);
|
|
893
|
-
}
|
|
894
|
-
catch (e) {
|
|
895
|
-
return { ok: false, error: 'AI response is not valid JSON. Probably attached invalid image URL' };
|
|
896
|
-
}
|
|
897
|
-
return { ok: true, result: resData };
|
|
898
|
-
}
|
|
899
|
-
else if (actionType === 'analyze_no_images') {
|
|
900
|
-
const compiledPropmt = yield this.compileOutputFieldsTemplatesNoImage(record, promptToPass);
|
|
901
|
-
const finalPrompt = this.getPromptForPlainFields(compiledPropmt);
|
|
902
|
-
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
903
|
-
let resp;
|
|
904
|
-
try {
|
|
905
|
-
const { content: chatResponse, error: topLevelError } = yield this.options.textCompleteAdapter.complete(finalPrompt, [], numberOfTokens);
|
|
906
|
-
if (topLevelError) {
|
|
907
|
-
return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError)}` };
|
|
908
|
-
}
|
|
909
|
-
resp = chatResponse;
|
|
910
|
-
}
|
|
911
|
-
catch (e) {
|
|
912
|
-
return { ok: false, error: 'AI provider refused to analyze plain fields' };
|
|
913
|
-
}
|
|
914
|
-
let resData;
|
|
915
|
-
try {
|
|
916
|
-
resData = JSON.parse(resp);
|
|
917
|
-
}
|
|
918
|
-
catch (e) {
|
|
919
|
-
return { ok: false, error: 'AI response is not valid JSON' };
|
|
920
|
-
}
|
|
921
|
-
return { ok: true, result: resData };
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
})
|
|
925
|
-
});
|
|
926
936
|
}
|
|
927
937
|
}
|
package/index.ts
CHANGED
|
@@ -366,6 +366,94 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
private async regenerateCell(jobId, fieldToRegenerate, recordId, actionType, prompt) {
|
|
370
|
+
if (!fieldToRegenerate || !recordId || !actionType ) {
|
|
371
|
+
jobs.set(jobId, { status: 'failed', error: 'Missing parameters' });
|
|
372
|
+
//return { ok: false, error: "Missing parameters" };
|
|
373
|
+
}
|
|
374
|
+
if ( !prompt ) {
|
|
375
|
+
if (actionType === 'analyze') {
|
|
376
|
+
prompt = this.options.fillFieldsFromImages ? (this.options.fillFieldsFromImages as any)[fieldToRegenerate] : null;
|
|
377
|
+
} else if (actionType === 'analyze_no_images') {
|
|
378
|
+
prompt = this.options.fillPlainFields ? (this.options.fillPlainFields as any)[fieldToRegenerate] : null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
382
|
+
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, recordId)] );
|
|
383
|
+
|
|
384
|
+
let promptToPass = JSON.stringify({[fieldToRegenerate]: prompt});
|
|
385
|
+
if (STUB_MODE) {
|
|
386
|
+
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
387
|
+
// return { ok: true, result: {[fieldToRegenerate]: "stub value"} };
|
|
388
|
+
jobs.set(jobId, { status: 'completed', result: {[fieldToRegenerate]: "stub value"} });
|
|
389
|
+
} else {
|
|
390
|
+
if ( actionType === 'analyze') {
|
|
391
|
+
const compiledPropmt = await this.compileOutputFieldsTemplates(record, promptToPass);
|
|
392
|
+
const finalPrompt = this.getPromptForImageAnalysis(compiledPropmt);
|
|
393
|
+
const attachmentFiles = await this.options.attachFiles({ record: record });
|
|
394
|
+
if (attachmentFiles.length === 0) {
|
|
395
|
+
// return { ok: false, error: "No source images found" };
|
|
396
|
+
jobs.set(jobId, { status: 'failed', error: "No source images found" });
|
|
397
|
+
}
|
|
398
|
+
let visionAdapterResponse;
|
|
399
|
+
try {
|
|
400
|
+
visionAdapterResponse = await this.options.visionAdapter.generate({ prompt: finalPrompt, inputFileUrls: attachmentFiles });
|
|
401
|
+
} catch (e) {
|
|
402
|
+
// return { ok: false, error: 'AI provider refused to analyze images' };
|
|
403
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analyze images' });
|
|
404
|
+
}
|
|
405
|
+
const resp: any = (visionAdapterResponse as any).response;
|
|
406
|
+
const topLevelError = (visionAdapterResponse as any).error;
|
|
407
|
+
if (topLevelError || resp?.error) {
|
|
408
|
+
// return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError.message || resp?.error.message)}` };
|
|
409
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError.message || resp?.error.message)}` });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
|
|
413
|
+
if (!textOutput || typeof textOutput !== 'string') {
|
|
414
|
+
// return { ok: false, error: 'AI response is not valid text' };
|
|
415
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid text' });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let resData;
|
|
419
|
+
try {
|
|
420
|
+
resData = JSON.parse(textOutput);
|
|
421
|
+
} catch (e) {
|
|
422
|
+
// return { ok: false, error: 'AI response is not valid JSON. Probably attached invalid image URL' };
|
|
423
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid JSON. Probably attached invalid image URL' });
|
|
424
|
+
}
|
|
425
|
+
// return { ok: true, result: resData };
|
|
426
|
+
jobs.set(jobId, { status: 'completed', result: resData });
|
|
427
|
+
|
|
428
|
+
} else if ( actionType === 'analyze_no_images') {
|
|
429
|
+
const compiledPropmt = await this.compileOutputFieldsTemplatesNoImage(record, promptToPass);
|
|
430
|
+
const finalPrompt = this.getPromptForPlainFields(compiledPropmt);
|
|
431
|
+
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
432
|
+
let resp;
|
|
433
|
+
try {
|
|
434
|
+
const { content: chatResponse, error: topLevelError } = await this.options.textCompleteAdapter.complete(finalPrompt, [], numberOfTokens);
|
|
435
|
+
if (topLevelError) {
|
|
436
|
+
// return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError)}` };
|
|
437
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError)}` });
|
|
438
|
+
}
|
|
439
|
+
resp = chatResponse;
|
|
440
|
+
} catch (e) {
|
|
441
|
+
// return { ok: false, error: 'AI provider refused to analyze plain fields' };
|
|
442
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analyze plain fields' });
|
|
443
|
+
}
|
|
444
|
+
let resData;
|
|
445
|
+
try {
|
|
446
|
+
resData = JSON.parse(resp);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
// return { ok: false, error: 'AI response is not valid JSON' };
|
|
449
|
+
jobs.set(jobId, { status: 'failed', error: 'AI response is not valid JSON' });
|
|
450
|
+
}
|
|
451
|
+
// return { ok: true, result: resData };
|
|
452
|
+
jobs.set(jobId, { status: 'completed', result: resData });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
369
457
|
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
370
458
|
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
371
459
|
|
|
@@ -777,6 +865,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
777
865
|
}
|
|
778
866
|
this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers);
|
|
779
867
|
break;
|
|
868
|
+
case 'regenerate_cell':
|
|
869
|
+
const fieldToRegenerate = body.fieldToRegenerate;
|
|
870
|
+
this.regenerateCell(jobId, fieldToRegenerate, recordId, body.action, body.prompt);
|
|
871
|
+
break;
|
|
780
872
|
default:
|
|
781
873
|
jobs.set(jobId, { status: "failed", error: "Unknown action type" });
|
|
782
874
|
}
|
|
@@ -860,91 +952,5 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
860
952
|
}
|
|
861
953
|
}
|
|
862
954
|
});
|
|
863
|
-
|
|
864
|
-
server.endpoint({
|
|
865
|
-
method: 'POST',
|
|
866
|
-
path: `/plugin/${this.pluginInstanceId}/regenerate-cell`,
|
|
867
|
-
handler: async ({ body, adminUser, headers }) => {
|
|
868
|
-
const recordId = body.recordId;
|
|
869
|
-
const fieldToRegenerate = body.fieldToRegenerate;
|
|
870
|
-
let prompt = body.prompt;
|
|
871
|
-
const actionType = body.actionType;
|
|
872
|
-
if (!fieldToRegenerate || !recordId || !actionType ) {
|
|
873
|
-
return { ok: false, error: "Missing parameters" };
|
|
874
|
-
}
|
|
875
|
-
if ( !prompt ) {
|
|
876
|
-
if (actionType === 'analyze') {
|
|
877
|
-
prompt = this.options.fillFieldsFromImages ? (this.options.fillFieldsFromImages as any)[fieldToRegenerate] : null;
|
|
878
|
-
} else if (actionType === 'analyze_no_images') {
|
|
879
|
-
prompt = this.options.fillPlainFields ? (this.options.fillPlainFields as any)[fieldToRegenerate] : null;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
883
|
-
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, recordId)] );
|
|
884
|
-
|
|
885
|
-
let promptToPass = JSON.stringify({[fieldToRegenerate]: prompt});
|
|
886
|
-
if (STUB_MODE) {
|
|
887
|
-
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
888
|
-
return { ok: true, result: {[fieldToRegenerate]: "stub value"} };
|
|
889
|
-
} else {
|
|
890
|
-
if ( actionType === 'analyze') {
|
|
891
|
-
const compiledPropmt = await this.compileOutputFieldsTemplates(record, promptToPass);
|
|
892
|
-
const finalPrompt = this.getPromptForImageAnalysis(compiledPropmt);
|
|
893
|
-
const attachmentFiles = await this.options.attachFiles({ record: record });
|
|
894
|
-
if (attachmentFiles.length === 0) {
|
|
895
|
-
return { ok: false, error: "No source images found" };
|
|
896
|
-
}
|
|
897
|
-
let visionAdapterResponse;
|
|
898
|
-
try {
|
|
899
|
-
visionAdapterResponse = await this.options.visionAdapter.generate({ prompt: finalPrompt, inputFileUrls: attachmentFiles });
|
|
900
|
-
} catch (e) {
|
|
901
|
-
return { ok: false, error: 'AI provider refused to analyze images' };
|
|
902
|
-
}
|
|
903
|
-
const resp: any = (visionAdapterResponse as any).response;
|
|
904
|
-
const topLevelError = (visionAdapterResponse as any).error;
|
|
905
|
-
if (topLevelError || resp?.error) {
|
|
906
|
-
return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError.message || resp?.error.message)}` };
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
|
|
910
|
-
if (!textOutput || typeof textOutput !== 'string') {
|
|
911
|
-
return { ok: false, error: 'AI response is not valid text' };
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
let resData;
|
|
915
|
-
try {
|
|
916
|
-
resData = JSON.parse(textOutput);
|
|
917
|
-
} catch (e) {
|
|
918
|
-
return { ok: false, error: 'AI response is not valid JSON. Probably attached invalid image URL' };
|
|
919
|
-
}
|
|
920
|
-
return { ok: true, result: resData };
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
} else if ( actionType === 'analyze_no_images') {
|
|
925
|
-
const compiledPropmt = await this.compileOutputFieldsTemplatesNoImage(record, promptToPass);
|
|
926
|
-
const finalPrompt = this.getPromptForPlainFields(compiledPropmt);
|
|
927
|
-
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
928
|
-
let resp;
|
|
929
|
-
try {
|
|
930
|
-
const { content: chatResponse, error: topLevelError } = await this.options.textCompleteAdapter.complete(finalPrompt, [], numberOfTokens);
|
|
931
|
-
if (topLevelError) {
|
|
932
|
-
return { ok: false, error: `ERROR: ${JSON.stringify(topLevelError)}` };
|
|
933
|
-
}
|
|
934
|
-
resp = chatResponse;
|
|
935
|
-
} catch (e) {
|
|
936
|
-
return { ok: false, error: 'AI provider refused to analyze plain fields' };
|
|
937
|
-
}
|
|
938
|
-
let resData;
|
|
939
|
-
try {
|
|
940
|
-
resData = JSON.parse(resp);
|
|
941
|
-
} catch (e) {
|
|
942
|
-
return { ok: false, error: 'AI response is not valid JSON' };
|
|
943
|
-
}
|
|
944
|
-
return { ok: true, result: resData };
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
955
|
}
|
|
950
956
|
}
|