@adminforth/bulk-ai-flow 1.21.8 → 1.22.0

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/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AdminForthPlugin, Filters } from "adminforth";
1
+ import { AdminForthFilterOperators, AdminForthPlugin, Filters } from "adminforth";
2
2
  import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResource } from "adminforth";
3
3
  import { suggestIfTypo } from "adminforth";
4
4
  import type { PluginOptions } from './types.js';
@@ -192,10 +192,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
192
192
  const selectedId = recordId;
193
193
  let isError = false;
194
194
  if (STUB_MODE) {
195
- await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
196
- jobs.set(jobId, { status: 'completed', result: {} });
197
- jobs.set(jobId, { status: 'failed', error: `ERROR: test error` });
198
- return { ok: false, error: 'test error' };
195
+ const fakeError = Math.random() < 0.005; // 0.05% chance of error
196
+ // await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
197
+ if (fakeError) {
198
+ jobs.set(jobId, { status: 'failed', error: `ERROR: test error` });
199
+ return { ok: false, error: 'test error' };
200
+ } else {
201
+ jobs.set(jobId, { status: 'completed', result: {description: 'test description', price: 99999999, engine_power: 999} });
202
+ return { ok: true };
203
+ }
199
204
  } else {
200
205
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
201
206
  const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] );
@@ -601,7 +606,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
601
606
  isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
602
607
  isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false,
603
608
  isAttachFiles: this.options.attachFiles ? true : false,
604
- disabledWhenNoCheckboxes: true,
609
+ disabledWhenNoCheckboxes: this.options.recordSelector === 'filtered' ? false : true,
605
610
  refreshRates: {
606
611
  fillFieldsFromImages: this.options.refreshRates?.fillFieldsFromImages || 2_000,
607
612
  fillPlainFields: this.options.refreshRates?.fillPlainFields || 1_000,
@@ -609,6 +614,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
609
614
  regenerateImages: this.options.refreshRates?.regenerateImages || 5_000,
610
615
  },
611
616
  askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false,
617
+ concurrencyLimit: this.options.concurrencyLimit || 10,
618
+ recordSelector: this.options.recordSelector || 'checkbox',
619
+ askConfirmation: this.options.askConfirmation || [],
612
620
  generationPrompts: {
613
621
  plainFieldsPrompts: this.options.fillPlainFields || {},
614
622
  imageFieldsPrompts: this.options.fillFieldsFromImages || {},
@@ -765,6 +773,26 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
765
773
  });
766
774
 
767
775
 
776
+ server.endpoint({
777
+ method: 'POST',
778
+ path: `/plugin/${this.pluginInstanceId}/get_old_data`,
779
+ handler: async ({ body }) => {
780
+ const recordId = body.recordId;
781
+ if (recordId === undefined || recordId === null) {
782
+ return { ok: false, error: "Missing recordId" };
783
+ }
784
+ const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
785
+ const record = await this.adminforth.resource(this.resourceConfig.resourceId)
786
+ .get([Filters.EQ(primaryKeyColumn.name, recordId)]);
787
+ if (!record) {
788
+ return { ok: false, error: "Record not found" };
789
+ }
790
+ record._label = this.resourceConfig.recordLabel(record);
791
+ return { ok: true, record };
792
+ }
793
+ });
794
+
795
+
768
796
  server.endpoint({
769
797
  method: 'POST',
770
798
  path: `/plugin/${this.pluginInstanceId}/get_images`,
@@ -803,7 +831,11 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
803
831
  }
804
832
  }
805
833
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
834
+
806
835
  const decimalFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'decimal').map(c => c.name);
836
+ const integerFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'integer').map(c => c.name);
837
+ const floatFieldsArray = this.resourceConfig.columns.filter(c => c.type === 'float').map(c => c.name);
838
+
807
839
  const updates = selectedIds.map(async (ID, idx) => {
808
840
  const oldRecord = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, ID)] );
809
841
  for (const [key, value] of Object.entries(outputImageFields)) {
@@ -847,6 +879,21 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
847
879
  }
848
880
  }
849
881
  }
882
+ if (integerFieldsArray.length > 0) {
883
+ for (const fieldName of integerFieldsArray) {
884
+ if (fieldsToUpdate[idx].hasOwnProperty(fieldName)) {
885
+ fieldsToUpdate[idx][fieldName] = parseInt(fieldsToUpdate[idx][fieldName], 10);
886
+ }
887
+ }
888
+ }
889
+ if (floatFieldsArray.length > 0) {
890
+ for (const fieldName of floatFieldsArray) {
891
+ if (fieldsToUpdate[idx].hasOwnProperty(fieldName)) {
892
+ fieldsToUpdate[idx][fieldName] = parseFloat(fieldsToUpdate[idx][fieldName]);
893
+ }
894
+ }
895
+ }
896
+
850
897
  const newRecord = {
851
898
  ...oldRecord,
852
899
  ...fieldsToUpdate[idx]
@@ -855,11 +902,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
855
902
  resource: this.resourceConfig,
856
903
  recordId: ID,
857
904
  oldRecord: oldRecord,
858
- record: newRecord,
905
+ updates: newRecord,
859
906
  adminUser: adminUser,
860
907
  })
861
908
  });
862
- await Promise.all(updates);
909
+ try {
910
+ await Promise.all(updates);
911
+ } catch (error) {
912
+ return { ok: false, error: `Error updating records, because of unprocesseble data for record ID ${selectedIds}` };
913
+ }
863
914
  return { ok: true };
864
915
  } else {
865
916
  return { ok: false, error: isAllowedToSave.error };
@@ -905,7 +956,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
905
956
  jobs.set(jobId, { status: "failed", error: "Missing action type" });
906
957
  //return { error: "Missing action type" };
907
958
  }
908
- else if (!recordId) {
959
+ else if (!recordId && typeof recordId !== 'number') {
909
960
  jobs.set(jobId, { status: "failed", error: "Missing record id" });
910
961
  //return { error: "Missing record id" };
911
962
  } else {
@@ -1013,5 +1064,44 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1013
1064
  }
1014
1065
  }
1015
1066
  });
1067
+
1068
+ server.endpoint({
1069
+ method: 'POST',
1070
+ path: `/plugin/${this.pluginInstanceId}/get_filtered_ids`,
1071
+ handler: async ({ body, adminUser, headers }) => {
1072
+ const filters = body.filters;
1073
+
1074
+ const normalizedFilters = { operator: AdminForthFilterOperators.AND, subFilters: [] };
1075
+ if (filters) {
1076
+ if (typeof filters !== 'object') {
1077
+ throw new Error(`Filter should be an array or an object`);
1078
+ }
1079
+ if (Array.isArray(filters)) {
1080
+ // if filters are an array, they will be connected with "AND" operator by default
1081
+ normalizedFilters.subFilters = filters;
1082
+ } else if (filters.field) {
1083
+ // assume filter is a SingleFilter
1084
+ normalizedFilters.subFilters = [filters];
1085
+ } else if (filters.subFilters) {
1086
+ // assume filter is a AndOr filter
1087
+ normalizedFilters.operator = filters.operator;
1088
+ normalizedFilters.subFilters = filters.subFilters;
1089
+ } else {
1090
+ // wrong filter
1091
+ throw new Error(`Wrong filter object value: ${JSON.stringify(filters)}`);
1092
+ }
1093
+ }
1094
+
1095
+ const records = await this.adminforth.resource(this.resourceConfig.resourceId).list(normalizedFilters);
1096
+ if (!records) {
1097
+ return { ok: true, recordIds: [] };
1098
+ }
1099
+ const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
1100
+
1101
+ const recordIds = records.map(record => record[primaryKeyColumn.name]);
1102
+
1103
+ return { ok: true, recordIds }
1104
+ }
1105
+ });
1016
1106
  }
1017
1107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.21.8",
3
+ "version": "1.22.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/types.ts CHANGED
@@ -119,4 +119,29 @@ export interface PluginOptions {
119
119
  }) => Record<string, any> | Promise<Record<string, any>>;
120
120
 
121
121
  askConfirmationBeforeGenerating?: boolean;
122
+
123
+ /**
124
+ * Maximum number of records processed concurrently on the frontend.
125
+ */
126
+ concurrencyLimit?: number;
127
+
128
+ /**
129
+ * Defines the way how records are selected for the action.
130
+ *
131
+ * 'checkbox' means that user will select records manually by checkboxes,
132
+ *
133
+ * 'filtered' means that action will be applied to all records matching current
134
+ * filters without showing any checkboxes (use with caution).
135
+ *
136
+ * Default is 'checkbox'.
137
+ */
138
+ recordSelector?: 'checkbox' | 'filtered';
139
+
140
+ /**
141
+ * additional confirmation: generation of very many records is risky in terms of budget. On 1m budget it might be thousands USD,
142
+ * this settings allows to suspend rgeneration and allow user to review everything what was already generated so far
143
+ * and then suggest Resume / Stop Generation buttons. You can set it in mode where it is shown only afterFirst N records (e.g. at start) or every N records
144
+ */
145
+ askConfirmation?: ({ afterRecords: number } | { everyRecords: number })[]
146
+
122
147
  }