@adminforth/bulk-ai-flow 1.26.4 → 1.26.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 CHANGED
@@ -14,5 +14,5 @@ custom/package.json
14
14
  custom/pnpm-lock.yaml
15
15
  custom/tsconfig.json
16
16
 
17
- sent 129,913 bytes received 191 bytes 260,208.00 bytes/sec
18
- total size is 129,205 speedup is 0.99
17
+ sent 130,637 bytes received 191 bytes 261,656.00 bytes/sec
18
+ total size is 129,924 speedup is 0.99
@@ -482,7 +482,7 @@ const generationModeButtons = computed(() => {
482
482
  return arrayToReturn;
483
483
  });
484
484
 
485
- async function regenerateRecord({ recordId }) {
485
+ async function regenerateRecord({ recordId }: { recordId: string }) {
486
486
 
487
487
  const promises = [];
488
488
 
@@ -610,7 +610,8 @@ async function getListOfIds() {
610
610
 
611
611
 
612
612
  async function runAiActions() {
613
- if (!await checkRateLimits()) {
613
+ const { ok, sessionIds } = await checkRateLimits();
614
+ if (!ok || !sessionIds) {
614
615
  confirmDialog.value.close();
615
616
  return;
616
617
  }
@@ -625,7 +626,7 @@ async function runAiActions() {
625
626
  tableRef.value?.refresh();
626
627
  const limit = pLimit(props.meta.concurrencyLimit || 10);
627
628
  const tasks = recordIds.value
628
- .map(id => limit(() => processOneRecord(String(id))));
629
+ .map(id => limit(() => processOneRecord(String(id), sessionIds)));
629
630
  await Promise.all(tasks);
630
631
  showGenerationFailureSummary();
631
632
  isActiveGeneration.value = false;
@@ -825,7 +826,7 @@ function createEmptyRecord(recordId: string | number): RecordState {
825
826
  };
826
827
  }
827
828
 
828
- async function processOneRecord(recordId: string) {
829
+ async function processOneRecord(recordId: string, sessionIds: string[]) {
829
830
  if (!checkIfDialogOpen()) {
830
831
  return;
831
832
  }
@@ -898,7 +899,7 @@ async function processOneRecord(recordId: string) {
898
899
  actions.push('analyze_no_images');
899
900
  }
900
901
 
901
- const results = await Promise.allSettled(actions.map(actionType => runActionForRecord(record, actionType)));
902
+ const results = await Promise.allSettled(actions.map(actionType => runActionForRecord(record, actionType, sessionIds)));
902
903
  if (!checkIfDialogOpen()) {
903
904
  return;
904
905
  }
@@ -908,17 +909,22 @@ async function processOneRecord(recordId: string) {
908
909
  touchRecords();
909
910
  }
910
911
 
911
- async function checkRateLimits() {
912
+ async function checkRateLimits(actions?: GenerationAction[]): Promise<{ ok: boolean; sessionIds?: string[] }> {
913
+ let sessionIds: string[] = [];
912
914
  isCheckingRateLimits.value = true;
913
915
  const actionsToCheck: GenerationAction[] = [];
914
- if (props.meta.isImageGeneration) {
915
- actionsToCheck.push('generate_images');
916
- }
917
- if (props.meta.isFieldsForAnalizeFromImages) {
918
- actionsToCheck.push('analyze');
919
- }
920
- if (props.meta.isFieldsForAnalizePlain) {
921
- actionsToCheck.push('analyze_no_images');
916
+ if (actions) {
917
+ actionsToCheck.push(...actions);
918
+ } else {
919
+ if (props.meta.isImageGeneration) {
920
+ actionsToCheck.push('generate_images');
921
+ }
922
+ if (props.meta.isFieldsForAnalizeFromImages) {
923
+ actionsToCheck.push('analyze');
924
+ }
925
+ if (props.meta.isFieldsForAnalizePlain) {
926
+ actionsToCheck.push('analyze_no_images');
927
+ }
922
928
  }
923
929
  for (const actionType of actionsToCheck) {
924
930
  try {
@@ -933,8 +939,9 @@ async function checkRateLimits() {
933
939
  variant: 'danger',
934
940
  timeout: 'unlimited',
935
941
  });
936
- return false;
942
+ return { ok: false };
937
943
  }
944
+ sessionIds.push(rateLimitRes.sessionId);
938
945
  } catch (e) {
939
946
  adminforth.alert({
940
947
  message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
@@ -942,14 +949,14 @@ async function checkRateLimits() {
942
949
  timeout: 'unlimited',
943
950
  });
944
951
  isCheckingRateLimits.value = false;
945
- return false;
952
+ return { ok: false };
946
953
  }
947
954
  }
948
955
  isCheckingRateLimits.value = false;
949
- return true;
956
+ return { ok: true, sessionIds };
950
957
  }
951
958
 
952
- async function runActionForRecord(record: RecordState, actionType: GenerationAction, forceFilterFilledFields?: boolean) {
959
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction, sessionIds: string[], forceFilterFilledFields?: boolean) {
953
960
  if (!checkIfDialogOpen()) {
954
961
  return;
955
962
  }
@@ -982,6 +989,7 @@ async function runActionForRecord(record: RecordState, actionType: GenerationAct
982
989
  recordId: record.id,
983
990
  ...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
984
991
  filterFilledFields: forceFilterFilledFields !== undefined ? forceFilterFilledFields: !overwriteExistingValues.value,
992
+ sessionIds
985
993
  },
986
994
  silentError: true,
987
995
  });
@@ -1438,6 +1446,10 @@ async function uploadImage(imgBlob, id, fieldName) {
1438
1446
  }
1439
1447
 
1440
1448
  async function regenerateImages({ recordId }: { recordId: any }) {
1449
+ const {ok, sessionIds} = await checkRateLimits(['generate_images']);
1450
+ if (!ok || !sessionIds) {
1451
+ return;
1452
+ }
1441
1453
  if (coreStore.isInternetError) {
1442
1454
  adminforth.alert({
1443
1455
  message: t('Cannot regenerate images while internet connection is lost. Please check your connection and try again.'),
@@ -1458,6 +1470,7 @@ async function regenerateImages({ recordId }: { recordId: any }) {
1458
1470
  await runActionForRecord(
1459
1471
  record,
1460
1472
  'generate_images',
1473
+ sessionIds,
1461
1474
  false
1462
1475
  );
1463
1476
 
@@ -1616,6 +1629,12 @@ async function regenerateCell(recordInfo: any) {
1616
1629
  });
1617
1630
  return;
1618
1631
  }
1632
+
1633
+ const {ok, sessionIds} = await checkRateLimits(['analyze_no_images', 'analyze']);
1634
+ if (!ok || !sessionIds) {
1635
+ return;
1636
+ }
1637
+
1619
1638
  const record = recordsById.get(String(recordInfo.recordId));
1620
1639
  if (!record) {
1621
1640
  return;
@@ -1658,6 +1677,7 @@ async function regenerateCell(recordInfo: any) {
1658
1677
  action: actionType,
1659
1678
  actionType: "regenerate_cell",
1660
1679
  prompt: generationPromptsForField[recordInfo.fieldName] || null,
1680
+ sessionIds
1661
1681
  },
1662
1682
  silentError: true,
1663
1683
  });
@@ -482,7 +482,7 @@ const generationModeButtons = computed(() => {
482
482
  return arrayToReturn;
483
483
  });
484
484
 
485
- async function regenerateRecord({ recordId }) {
485
+ async function regenerateRecord({ recordId }: { recordId: string }) {
486
486
 
487
487
  const promises = [];
488
488
 
@@ -610,7 +610,8 @@ async function getListOfIds() {
610
610
 
611
611
 
612
612
  async function runAiActions() {
613
- if (!await checkRateLimits()) {
613
+ const { ok, sessionIds } = await checkRateLimits();
614
+ if (!ok || !sessionIds) {
614
615
  confirmDialog.value.close();
615
616
  return;
616
617
  }
@@ -625,7 +626,7 @@ async function runAiActions() {
625
626
  tableRef.value?.refresh();
626
627
  const limit = pLimit(props.meta.concurrencyLimit || 10);
627
628
  const tasks = recordIds.value
628
- .map(id => limit(() => processOneRecord(String(id))));
629
+ .map(id => limit(() => processOneRecord(String(id), sessionIds)));
629
630
  await Promise.all(tasks);
630
631
  showGenerationFailureSummary();
631
632
  isActiveGeneration.value = false;
@@ -825,7 +826,7 @@ function createEmptyRecord(recordId: string | number): RecordState {
825
826
  };
826
827
  }
827
828
 
828
- async function processOneRecord(recordId: string) {
829
+ async function processOneRecord(recordId: string, sessionIds: string[]) {
829
830
  if (!checkIfDialogOpen()) {
830
831
  return;
831
832
  }
@@ -898,7 +899,7 @@ async function processOneRecord(recordId: string) {
898
899
  actions.push('analyze_no_images');
899
900
  }
900
901
 
901
- const results = await Promise.allSettled(actions.map(actionType => runActionForRecord(record, actionType)));
902
+ const results = await Promise.allSettled(actions.map(actionType => runActionForRecord(record, actionType, sessionIds)));
902
903
  if (!checkIfDialogOpen()) {
903
904
  return;
904
905
  }
@@ -908,17 +909,22 @@ async function processOneRecord(recordId: string) {
908
909
  touchRecords();
909
910
  }
910
911
 
911
- async function checkRateLimits() {
912
+ async function checkRateLimits(actions?: GenerationAction[]): Promise<{ ok: boolean; sessionIds?: string[] }> {
913
+ let sessionIds: string[] = [];
912
914
  isCheckingRateLimits.value = true;
913
915
  const actionsToCheck: GenerationAction[] = [];
914
- if (props.meta.isImageGeneration) {
915
- actionsToCheck.push('generate_images');
916
- }
917
- if (props.meta.isFieldsForAnalizeFromImages) {
918
- actionsToCheck.push('analyze');
919
- }
920
- if (props.meta.isFieldsForAnalizePlain) {
921
- actionsToCheck.push('analyze_no_images');
916
+ if (actions) {
917
+ actionsToCheck.push(...actions);
918
+ } else {
919
+ if (props.meta.isImageGeneration) {
920
+ actionsToCheck.push('generate_images');
921
+ }
922
+ if (props.meta.isFieldsForAnalizeFromImages) {
923
+ actionsToCheck.push('analyze');
924
+ }
925
+ if (props.meta.isFieldsForAnalizePlain) {
926
+ actionsToCheck.push('analyze_no_images');
927
+ }
922
928
  }
923
929
  for (const actionType of actionsToCheck) {
924
930
  try {
@@ -933,8 +939,9 @@ async function checkRateLimits() {
933
939
  variant: 'danger',
934
940
  timeout: 'unlimited',
935
941
  });
936
- return false;
942
+ return { ok: false };
937
943
  }
944
+ sessionIds.push(rateLimitRes.sessionId);
938
945
  } catch (e) {
939
946
  adminforth.alert({
940
947
  message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
@@ -942,14 +949,14 @@ async function checkRateLimits() {
942
949
  timeout: 'unlimited',
943
950
  });
944
951
  isCheckingRateLimits.value = false;
945
- return false;
952
+ return { ok: false };
946
953
  }
947
954
  }
948
955
  isCheckingRateLimits.value = false;
949
- return true;
956
+ return { ok: true, sessionIds };
950
957
  }
951
958
 
952
- async function runActionForRecord(record: RecordState, actionType: GenerationAction, forceFilterFilledFields?: boolean) {
959
+ async function runActionForRecord(record: RecordState, actionType: GenerationAction, sessionIds: string[], forceFilterFilledFields?: boolean) {
953
960
  if (!checkIfDialogOpen()) {
954
961
  return;
955
962
  }
@@ -982,6 +989,7 @@ async function runActionForRecord(record: RecordState, actionType: GenerationAct
982
989
  recordId: record.id,
983
990
  ...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
984
991
  filterFilledFields: forceFilterFilledFields !== undefined ? forceFilterFilledFields: !overwriteExistingValues.value,
992
+ sessionIds
985
993
  },
986
994
  silentError: true,
987
995
  });
@@ -1438,6 +1446,10 @@ async function uploadImage(imgBlob, id, fieldName) {
1438
1446
  }
1439
1447
 
1440
1448
  async function regenerateImages({ recordId }: { recordId: any }) {
1449
+ const {ok, sessionIds} = await checkRateLimits(['generate_images']);
1450
+ if (!ok || !sessionIds) {
1451
+ return;
1452
+ }
1441
1453
  if (coreStore.isInternetError) {
1442
1454
  adminforth.alert({
1443
1455
  message: t('Cannot regenerate images while internet connection is lost. Please check your connection and try again.'),
@@ -1458,6 +1470,7 @@ async function regenerateImages({ recordId }: { recordId: any }) {
1458
1470
  await runActionForRecord(
1459
1471
  record,
1460
1472
  'generate_images',
1473
+ sessionIds,
1461
1474
  false
1462
1475
  );
1463
1476
 
@@ -1616,6 +1629,12 @@ async function regenerateCell(recordInfo: any) {
1616
1629
  });
1617
1630
  return;
1618
1631
  }
1632
+
1633
+ const {ok, sessionIds} = await checkRateLimits(['analyze_no_images', 'analyze']);
1634
+ if (!ok || !sessionIds) {
1635
+ return;
1636
+ }
1637
+
1619
1638
  const record = recordsById.get(String(recordInfo.recordId));
1620
1639
  if (!record) {
1621
1640
  return;
@@ -1658,6 +1677,7 @@ async function regenerateCell(recordInfo: any) {
1658
1677
  action: actionType,
1659
1678
  actionType: "regenerate_cell",
1660
1679
  prompt: generationPromptsForField[recordInfo.fieldName] || null,
1680
+ sessionIds
1661
1681
  },
1662
1682
  silentError: true,
1663
1683
  });
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
8
8
  totalCalls: number;
9
9
  totalDuration: number;
10
10
  rateLimiters: Record<string, RateLimiter>;
11
+ sessionIds: Set<string>;
11
12
  constructor(options: PluginOptions);
12
13
  private compileTemplates;
13
14
  private compileOutputFieldsTemplates;
package/dist/index.js CHANGED
@@ -18,10 +18,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
18
18
  constructor(options) {
19
19
  super(options, import.meta.url);
20
20
  this.rateLimiters = {};
21
+ this.sessionIds = new Set();
21
22
  this.options = options;
22
23
  // for calculating average time
23
24
  this.totalCalls = 0;
24
25
  this.totalDuration = 0;
26
+ // for rate limiting
27
+ this.sessionIds = new Set();
25
28
  }
26
29
  // Compile Handlebars templates in outputFields using record fields as context
27
30
  compileTemplates(source, record, valueSelector) {
@@ -945,7 +948,20 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
945
948
  method: 'POST',
946
949
  path: `/plugin/${this.pluginInstanceId}/create-job`,
947
950
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
948
- const { actionType, recordId, customPrompt, filterFilledFields } = body;
951
+ const { actionType, recordId, customPrompt, filterFilledFields, sessionIds } = body;
952
+ if (this.options.rateLimits) {
953
+ if (sessionIds && sessionIds.length > 0) {
954
+ for (const sessionId of sessionIds) {
955
+ if (!this.sessionIds.has(sessionId)) {
956
+ return { ok: false, error: "Invalid session id" };
957
+ }
958
+ this.sessionIds.delete(sessionId);
959
+ }
960
+ }
961
+ else {
962
+ return { ok: false, error: "Missing session ids" };
963
+ }
964
+ }
949
965
  const jobId = randomUUID();
950
966
  jobs.set(jobId, { status: "in_progress" });
951
967
  if (!actionType) {
@@ -1026,6 +1042,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1026
1042
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
1027
1043
  var _b, _c, _d;
1028
1044
  const actionType = body.actionType;
1045
+ const sessionId = randomUUID();
1046
+ this.sessionIds.add(sessionId);
1029
1047
  if (actionType === 'analyze' && ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillFieldsFromImages)) {
1030
1048
  if (yield this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
1031
1049
  return { ok: false, error: "Rate limit exceeded for image analyze" };
@@ -1041,7 +1059,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1041
1059
  return { ok: false, error: "Rate limit exceeded for image generation" };
1042
1060
  }
1043
1061
  }
1044
- return { ok: true };
1062
+ setTimeout(() => this.sessionIds.delete(sessionId), 300000);
1063
+ return { ok: true, sessionId };
1045
1064
  })
1046
1065
  });
1047
1066
  server.endpoint({
package/index.ts CHANGED
@@ -13,6 +13,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
13
13
  totalCalls: number;
14
14
  totalDuration: number;
15
15
  rateLimiters: Record<string, RateLimiter> = {};
16
+ sessionIds: Set<string> = new Set();
16
17
 
17
18
  constructor(options: PluginOptions) {
18
19
  super(options, import.meta.url);
@@ -21,6 +22,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
21
22
  // for calculating average time
22
23
  this.totalCalls = 0;
23
24
  this.totalDuration = 0;
25
+
26
+ // for rate limiting
27
+ this.sessionIds = new Set();
24
28
  }
25
29
 
26
30
  // Compile Handlebars templates in outputFields using record fields as context
@@ -970,7 +974,19 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
970
974
  method: 'POST',
971
975
  path: `/plugin/${this.pluginInstanceId}/create-job`,
972
976
  handler: async ({ body, adminUser, headers }) => {
973
- const { actionType, recordId, customPrompt, filterFilledFields } = body;
977
+ const { actionType, recordId, customPrompt, filterFilledFields, sessionIds } = body;
978
+ if (this.options.rateLimits) {
979
+ if (sessionIds && sessionIds.length > 0) {
980
+ for (const sessionId of sessionIds) {
981
+ if (!this.sessionIds.has(sessionId)) {
982
+ return { ok: false, error: "Invalid session id" };
983
+ }
984
+ this.sessionIds.delete(sessionId);
985
+ }
986
+ } else {
987
+ return { ok: false, error: "Missing session ids" };
988
+ }
989
+ }
974
990
  const jobId = randomUUID();
975
991
  jobs.set(jobId, { status: "in_progress" });
976
992
  if (!actionType) {
@@ -1056,6 +1072,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1056
1072
  path: `/plugin/${this.pluginInstanceId}/update-rate-limits`,
1057
1073
  handler: async ({ body, adminUser, headers }) => {
1058
1074
  const actionType = body.actionType;
1075
+ const sessionId = randomUUID();
1076
+ this.sessionIds.add(sessionId);
1059
1077
  if (actionType === 'analyze' && this.options.rateLimits?.fillFieldsFromImages) {
1060
1078
  if (await this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
1061
1079
  return {ok: false, error: "Rate limit exceeded for image analyze" };
@@ -1071,8 +1089,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1071
1089
  return {ok: false, error: "Rate limit exceeded for image generation" };
1072
1090
  }
1073
1091
  }
1074
-
1075
- return { ok: true };
1092
+ setTimeout(() => this.sessionIds.delete(sessionId), 300_000);
1093
+ return { ok: true, sessionId };
1076
1094
  }
1077
1095
  });
1078
1096
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.26.4",
3
+ "version": "1.26.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -25,7 +25,7 @@
25
25
  "description": "Bulk AI workflow plugin for AdminForth for batch field generation and image processing",
26
26
  "devDependencies": {
27
27
  "@types/node": "latest",
28
- "adminforth": "^3.1.1",
28
+ "adminforth": "^3.1.0",
29
29
  "semantic-release": "^24.2.1",
30
30
  "semantic-release-slack-bot": "^4.0.2",
31
31
  "typescript": "^5.7.3"
@@ -35,7 +35,7 @@
35
35
  "handlebars": "^4.7.8"
36
36
  },
37
37
  "peerDependencies": {
38
- "adminforth": "^3.1.1"
38
+ "adminforth": "^3.1.0"
39
39
  },
40
40
  "release": {
41
41
  "plugins": [