@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 CHANGED
@@ -13,5 +13,5 @@ custom/package-lock.json
13
13
  custom/package.json
14
14
  custom/tsconfig.json
15
15
 
16
- sent 91,084 bytes received 172 bytes 182,512.00 bytes/sec
17
- total size is 90,439 speedup is 0.99
16
+ sent 92,827 bytes received 172 bytes 185,998.00 bytes/sec
17
+ total size is 92,182 speedup is 0.99
@@ -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}/regenerate-cell`,
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
- actionType: actionType,
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
- const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordInfo.recordId));
1063
-
1064
- const pk = selected.value[index]?.[primaryKey];
1065
- if (pk) {
1066
- selected.value[index] = {
1067
- ...selected.value[index],
1068
- ...res.result,
1069
- isChecked: true,
1070
- [primaryKey]: pk,
1071
- };
1072
- }
1073
- if (actionType === 'analyze') {
1074
- imageToTextErrorMessages.value[index][recordInfo.fieldName] = '';
1075
- } else if (actionType === 'analyze_no_images') {
1076
- textToTextErrorMessages.value[index][recordInfo.fieldName] = '';
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}/regenerate-cell`,
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
- actionType: actionType,
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
- const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordInfo.recordId));
1063
-
1064
- const pk = selected.value[index]?.[primaryKey];
1065
- if (pk) {
1066
- selected.value[index] = {
1067
- ...selected.value[index],
1068
- ...res.result,
1069
- isChecked: true,
1070
- [primaryKey]: pk,
1071
- };
1072
- }
1073
- if (actionType === 'analyze') {
1074
- imageToTextErrorMessages.value[index][recordInfo.fieldName] = '';
1075
- } else if (actionType === 'analyze_no_images') {
1076
- textToTextErrorMessages.value[index][recordInfo.fieldName] = '';
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.17.9",
3
+ "version": "1.18.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },