@adminforth/bulk-ai-flow 1.9.1 → 1.10.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/build.log CHANGED
@@ -11,5 +11,5 @@ custom/package-lock.json
11
11
  custom/package.json
12
12
  custom/tsconfig.json
13
13
 
14
- sent 184,983 bytes received 134 bytes 370,234.00 bytes/sec
15
- total size is 184,450 speedup is 1.00
14
+ sent 186,334 bytes received 134 bytes 372,936.00 bytes/sec
15
+ total size is 185,801 speedup is 1.00
@@ -9,6 +9,7 @@
9
9
  ref="confirmDialog"
10
10
  header="Bulk AI Flow"
11
11
  class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
12
+ :beforeCloseFunction="closeDialog"
12
13
  :buttons="[
13
14
  { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } },
14
15
  { label: 'Cancel', onclick: (dialog) => dialog.hide() },
@@ -91,9 +92,10 @@ const checkedCount = ref(0);
91
92
  const isGeneratingImages = ref(false);
92
93
  const isAnalizingFields = ref(false);
93
94
  const isAnalizingImages = ref(false);
94
-
95
+ const isDialogOpen = ref(false);
95
96
 
96
97
  const openDialog = async () => {
98
+ isDialogOpen.value = true;
97
99
  confirmDialog.value.open();
98
100
  isFetchingRecords.value = true;
99
101
  await getRecords();
@@ -143,6 +145,23 @@ const openDialog = async () => {
143
145
  }
144
146
  }
145
147
 
148
+ const closeDialog = () => {
149
+ confirmDialog.value.close();
150
+ isAiResponseReceivedAnalize.value = [];
151
+ isAiResponseReceivedImage.value = [];
152
+
153
+ records.value = [];
154
+ images.value = [];
155
+ selected.value = [];
156
+ tableColumns.value = [];
157
+ tableColumnsIndexes.value = [];
158
+ isError.value = false;
159
+ isCriticalError.value = false;
160
+ isImageGenerationError.value = false;
161
+ errorMessage.value = '';
162
+ isDialogOpen.value = false;
163
+ }
164
+
146
165
  watch(selected, (val) => {
147
166
  //console.log('Selected changed:', val);
148
167
  checkedCount.value = val.filter(item => item.isChecked === true).length;
@@ -406,7 +425,35 @@ async function runAiAction({
406
425
  let errorMessage = '';
407
426
  const jobsIds: { jobId: any; recordId: any; }[] = [];
408
427
  responseFlag.value = props.checkboxes.map(() => false);
409
-
428
+ let isRateLimitExceeded = false;
429
+ try {
430
+ const rateLimitRes = await callAdminForthApi({
431
+ path: `/plugin/${props.meta.pluginInstanceId}/update-rate-limits`,
432
+ method: 'POST',
433
+ body: {
434
+ actionType: actionType,
435
+ },
436
+ });
437
+ if (rateLimitRes?.error) {
438
+ isRateLimitExceeded = true;
439
+ adminforth.alert({
440
+ message: `Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`,
441
+ variant: 'danger',
442
+ timeout: 'unlimited',
443
+ });
444
+ return;
445
+ }
446
+ } catch (e) {
447
+ adminforth.alert({
448
+ message: `Error checking rate limit for "${actionType.replace('_', ' ')}" action.`,
449
+ variant: 'danger',
450
+ timeout: 'unlimited',
451
+ });
452
+ isRateLimitExceeded = true;
453
+ }
454
+ if (isRateLimitExceeded) {
455
+ return;
456
+ };
410
457
  //creating jobs
411
458
  const tasks = props.checkboxes.map(async (checkbox, i) => {
412
459
  try {
@@ -440,7 +487,7 @@ async function runAiAction({
440
487
  //polling jobs
441
488
  let isInProgress = true;
442
489
  //if no jobs were created, skip polling
443
- while (isInProgress) {
490
+ while (isInProgress && isDialogOpen.value) {
444
491
  //check if at least one job is still in progress
445
492
  let isAtLeastOneInProgress = false;
446
493
  //checking status of each job
@@ -9,6 +9,7 @@
9
9
  ref="confirmDialog"
10
10
  header="Bulk AI Flow"
11
11
  class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
12
+ :beforeCloseFunction="closeDialog"
12
13
  :buttons="[
13
14
  { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } },
14
15
  { label: 'Cancel', onclick: (dialog) => dialog.hide() },
@@ -91,9 +92,10 @@ const checkedCount = ref(0);
91
92
  const isGeneratingImages = ref(false);
92
93
  const isAnalizingFields = ref(false);
93
94
  const isAnalizingImages = ref(false);
94
-
95
+ const isDialogOpen = ref(false);
95
96
 
96
97
  const openDialog = async () => {
98
+ isDialogOpen.value = true;
97
99
  confirmDialog.value.open();
98
100
  isFetchingRecords.value = true;
99
101
  await getRecords();
@@ -143,6 +145,23 @@ const openDialog = async () => {
143
145
  }
144
146
  }
145
147
 
148
+ const closeDialog = () => {
149
+ confirmDialog.value.close();
150
+ isAiResponseReceivedAnalize.value = [];
151
+ isAiResponseReceivedImage.value = [];
152
+
153
+ records.value = [];
154
+ images.value = [];
155
+ selected.value = [];
156
+ tableColumns.value = [];
157
+ tableColumnsIndexes.value = [];
158
+ isError.value = false;
159
+ isCriticalError.value = false;
160
+ isImageGenerationError.value = false;
161
+ errorMessage.value = '';
162
+ isDialogOpen.value = false;
163
+ }
164
+
146
165
  watch(selected, (val) => {
147
166
  //console.log('Selected changed:', val);
148
167
  checkedCount.value = val.filter(item => item.isChecked === true).length;
@@ -406,7 +425,35 @@ async function runAiAction({
406
425
  let errorMessage = '';
407
426
  const jobsIds: { jobId: any; recordId: any; }[] = [];
408
427
  responseFlag.value = props.checkboxes.map(() => false);
409
-
428
+ let isRateLimitExceeded = false;
429
+ try {
430
+ const rateLimitRes = await callAdminForthApi({
431
+ path: `/plugin/${props.meta.pluginInstanceId}/update-rate-limits`,
432
+ method: 'POST',
433
+ body: {
434
+ actionType: actionType,
435
+ },
436
+ });
437
+ if (rateLimitRes?.error) {
438
+ isRateLimitExceeded = true;
439
+ adminforth.alert({
440
+ message: `Rate limit exceeded for "${actionType.replace('_', ' ')}" action. Please try again later.`,
441
+ variant: 'danger',
442
+ timeout: 'unlimited',
443
+ });
444
+ return;
445
+ }
446
+ } catch (e) {
447
+ adminforth.alert({
448
+ message: `Error checking rate limit for "${actionType.replace('_', ' ')}" action.`,
449
+ variant: 'danger',
450
+ timeout: 'unlimited',
451
+ });
452
+ isRateLimitExceeded = true;
453
+ }
454
+ if (isRateLimitExceeded) {
455
+ return;
456
+ };
410
457
  //creating jobs
411
458
  const tasks = props.checkboxes.map(async (checkbox, i) => {
412
459
  try {
@@ -440,7 +487,7 @@ async function runAiAction({
440
487
  //polling jobs
441
488
  let isInProgress = true;
442
489
  //if no jobs were created, skip polling
443
- while (isInProgress) {
490
+ while (isInProgress && isDialogOpen.value) {
444
491
  //check if at least one job is still in progress
445
492
  let isAtLeastOneInProgress = false;
446
493
  //checking status of each job
package/dist/index.js CHANGED
@@ -56,15 +56,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
56
56
  }
57
57
  analyze_image(jobId, recordId, adminUser, headers) {
58
58
  return __awaiter(this, void 0, void 0, function* () {
59
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
59
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
60
60
  const selectedId = recordId;
61
61
  let isError = false;
62
- if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.fillFieldsFromImages) === 'string') {
63
- if (this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
64
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
65
- return { error: "Rate limit exceeded" };
66
- }
67
- }
62
+ // if (typeof(this.options.rateLimits?.fillFieldsFromImages) === 'string'){
63
+ // if (this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
64
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
65
+ // return { error: "Rate limit exceeded" };
66
+ // }
67
+ // }
68
68
  // Fetch the record using the provided ID
69
69
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
70
70
  const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)]);
@@ -98,7 +98,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
98
98
  if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
99
99
  jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}` });
100
100
  }
101
- 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;
101
+ 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;
102
102
  if (!textOutput || typeof textOutput !== 'string') {
103
103
  jobs.set(jobId, { status: 'failed', error: 'Unexpected AI response format' });
104
104
  }
@@ -109,20 +109,22 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
109
109
  return { ok: true };
110
110
  }
111
111
  }
112
- ;
112
+ else {
113
+ jobs.set(jobId, { status: 'failed', error: "No attachment files found" });
114
+ return { ok: false, error: "No attachment files found" };
115
+ }
113
116
  });
114
117
  }
115
118
  analyzeNoImages(jobId, recordId, adminUser, headers) {
116
119
  return __awaiter(this, void 0, void 0, function* () {
117
- var _a;
118
120
  const selectedId = recordId;
119
121
  let isError = false;
120
- if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.fillPlainFields) === 'string') {
121
- if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
122
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
123
- return { error: "Rate limit exceeded" };
124
- }
125
- }
122
+ // if (typeof(this.options.rateLimits?.fillPlainFields) === 'string'){
123
+ // if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
124
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
125
+ // return { error: "Rate limit exceeded" };
126
+ // }
127
+ // }
126
128
  if (STUB_MODE) {
127
129
  yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
128
130
  jobs.set(jobId, { status: 'completed', result: {} });
@@ -163,17 +165,17 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
163
165
  }
164
166
  initialImageGenerate(jobId, recordId, adminUser, headers) {
165
167
  return __awaiter(this, void 0, void 0, function* () {
166
- var _a, _b, _c;
168
+ var _a, _b;
167
169
  const selectedId = recordId;
168
170
  let isError = false;
169
- if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.generateImages) === 'string') {
170
- if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
171
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
172
- return { error: "Rate limit exceeded" };
173
- }
174
- }
171
+ // if (typeof(this.options.rateLimits?.generateImages) === 'string'){
172
+ // if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
173
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
174
+ // return { error: "Rate limit exceeded" };
175
+ // }
176
+ // }
175
177
  const start = +new Date();
176
- const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_b = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _b === void 0 ? void 0 : _b.name, selectedId)]);
178
+ const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_a = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name, selectedId)]);
177
179
  let attachmentFiles;
178
180
  if (!this.options.attachFiles) {
179
181
  attachmentFiles = [];
@@ -181,7 +183,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
181
183
  else {
182
184
  attachmentFiles = yield this.options.attachFiles({ record });
183
185
  }
184
- const fieldTasks = Object.keys(((_c = this.options) === null || _c === void 0 ? void 0 : _c.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
186
+ const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
185
187
  const prompt = this.compileGenerationFieldTemplates(record)[key];
186
188
  let images;
187
189
  if (this.options.attachFiles && attachmentFiles.length === 0) {
@@ -600,25 +602,25 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
600
602
  }
601
603
  switch (actionType) {
602
604
  case 'generate_images':
603
- setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.initialImageGenerate(jobId, recordId, adminUser, headers); }), 100);
605
+ this.initialImageGenerate(jobId, recordId, adminUser, headers);
604
606
  break;
605
607
  case 'analyze_no_images':
606
- setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.analyzeNoImages(jobId, recordId, adminUser, headers); }), 100);
608
+ this.analyzeNoImages(jobId, recordId, adminUser, headers);
607
609
  break;
608
610
  case 'analyze':
609
- setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.analyze_image(jobId, recordId, adminUser, headers); }), 100);
611
+ this.analyze_image(jobId, recordId, adminUser, headers);
610
612
  break;
611
613
  case 'regenerate_images':
612
614
  if (!body.prompt || !body.fieldName) {
613
615
  jobs.set(jobId, { status: "failed", error: "Missing prompt or field name" });
614
616
  break;
615
617
  }
616
- setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers); }), 100);
618
+ this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers);
617
619
  break;
618
620
  default:
619
621
  jobs.set(jobId, { status: "failed", error: "Unknown action type" });
620
622
  }
621
- setTimeout(() => jobs.delete(jobId), 300000);
623
+ setTimeout(() => jobs.delete(jobId), 1800000);
622
624
  return { ok: true, jobId };
623
625
  })
624
626
  });
@@ -637,5 +639,29 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
637
639
  return { ok: true, job };
638
640
  })
639
641
  });
642
+ server.endpoint({
643
+ method: 'POST',
644
+ path: `/plugin/${this.pluginInstanceId}/update-rate-limits`,
645
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
646
+ var _b, _c, _d;
647
+ const actionType = body.actionType;
648
+ if (actionType === 'analyze' && ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillFieldsFromImages)) {
649
+ if (this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
650
+ return { ok: false, error: "Rate limit exceeded for image analyze" };
651
+ }
652
+ }
653
+ if (actionType === 'analyze_no_images' && ((_c = this.options.rateLimits) === null || _c === void 0 ? void 0 : _c.fillPlainFields)) {
654
+ if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
655
+ return { ok: false, error: "Rate limit exceeded for plain field analyze" };
656
+ }
657
+ }
658
+ if (actionType === 'generate_images' && ((_d = this.options.rateLimits) === null || _d === void 0 ? void 0 : _d.generateImages)) {
659
+ if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
660
+ return { ok: false, error: "Rate limit exceeded for image generation" };
661
+ }
662
+ }
663
+ return { ok: true };
664
+ })
665
+ });
640
666
  }
641
667
  }
package/index.ts CHANGED
@@ -70,12 +70,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
70
70
  private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
71
71
  const selectedId = recordId;
72
72
  let isError = false;
73
- if (typeof(this.options.rateLimits?.fillFieldsFromImages) === 'string'){
74
- if (this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
75
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
76
- return { error: "Rate limit exceeded" };
77
- }
78
- }
73
+ // if (typeof(this.options.rateLimits?.fillFieldsFromImages) === 'string'){
74
+ // if (this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
75
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
76
+ // return { error: "Rate limit exceeded" };
77
+ // }
78
+ // }
79
79
  // Fetch the record using the provided ID
80
80
  const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
81
81
  const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)] );
@@ -121,18 +121,22 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
121
121
  jobs.set(jobId, { status: 'completed', result });
122
122
  return { ok: true };
123
123
  }
124
- };
124
+ } else {
125
+ jobs.set(jobId, { status: 'failed', error: "No attachment files found" });
126
+ return { ok: false, error: "No attachment files found" };
127
+ }
128
+
125
129
  }
126
130
 
127
131
  private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
128
132
  const selectedId = recordId;
129
133
  let isError = false;
130
- if (typeof(this.options.rateLimits?.fillPlainFields) === 'string'){
131
- if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
132
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
133
- return { error: "Rate limit exceeded" };
134
- }
135
- }
134
+ // if (typeof(this.options.rateLimits?.fillPlainFields) === 'string'){
135
+ // if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
136
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
137
+ // return { error: "Rate limit exceeded" };
138
+ // }
139
+ // }
136
140
  if (STUB_MODE) {
137
141
  await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
138
142
  jobs.set(jobId, { status: 'completed', result: {} });
@@ -173,12 +177,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
173
177
  private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
174
178
  const selectedId = recordId;
175
179
  let isError = false;
176
- if (typeof(this.options.rateLimits?.generateImages) === 'string'){
177
- if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
178
- jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
179
- return { error: "Rate limit exceeded" };
180
- }
181
- }
180
+ // if (typeof(this.options.rateLimits?.generateImages) === 'string'){
181
+ // if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
182
+ // jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
183
+ // return { error: "Rate limit exceeded" };
184
+ // }
185
+ // }
182
186
  const start = +new Date();
183
187
  const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, selectedId)]);
184
188
  let attachmentFiles
@@ -638,25 +642,25 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
638
642
 
639
643
  switch(actionType) {
640
644
  case 'generate_images':
641
- setTimeout(async () => await this.initialImageGenerate(jobId, recordId, adminUser, headers), 100);
645
+ this.initialImageGenerate(jobId, recordId, adminUser, headers);
642
646
  break;
643
647
  case 'analyze_no_images':
644
- setTimeout(async () => await this.analyzeNoImages(jobId, recordId, adminUser, headers), 100);
648
+ this.analyzeNoImages(jobId, recordId, adminUser, headers);
645
649
  break;
646
650
  case 'analyze':
647
- setTimeout(async () => await this.analyze_image(jobId, recordId, adminUser, headers), 100);
651
+ this.analyze_image(jobId, recordId, adminUser, headers);
648
652
  break;
649
653
  case 'regenerate_images':
650
654
  if (!body.prompt || !body.fieldName) {
651
655
  jobs.set(jobId, { status: "failed", error: "Missing prompt or field name" });
652
656
  break;
653
657
  }
654
- setTimeout(async () => await this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers), 100);
658
+ this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers);
655
659
  break;
656
660
  default:
657
661
  jobs.set(jobId, { status: "failed", error: "Unknown action type" });
658
662
  }
659
- setTimeout(() => jobs.delete(jobId), 300_000);
663
+ setTimeout(() => jobs.delete(jobId), 1_800_000);
660
664
  return { ok: true, jobId };
661
665
  }
662
666
  });
@@ -679,5 +683,31 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
679
683
  });
680
684
 
681
685
 
686
+ server.endpoint({
687
+ method: 'POST',
688
+ path: `/plugin/${this.pluginInstanceId}/update-rate-limits`,
689
+ handler: async ({ body, adminUser, headers }) => {
690
+ const actionType = body.actionType;
691
+ if (actionType === 'analyze' && this.options.rateLimits?.fillFieldsFromImages) {
692
+ if (this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
693
+ return {ok: false, error: "Rate limit exceeded for image analyze" };
694
+ }
695
+ }
696
+ if (actionType === 'analyze_no_images' && this.options.rateLimits?.fillPlainFields) {
697
+ if (this.checkRateLimit("fillPlainFields" ,this.options.rateLimits.fillPlainFields, headers)) {
698
+ return {ok: false, error: "Rate limit exceeded for plain field analyze" };
699
+ }
700
+ }
701
+ if (actionType === 'generate_images' && this.options.rateLimits?.generateImages) {
702
+ if (this.checkRateLimit("generateImages" ,this.options.rateLimits.generateImages, headers)) {
703
+ return {ok: false, error: "Rate limit exceeded for image generation" };
704
+ }
705
+ }
706
+
707
+ return { ok: true };
708
+ }
709
+ });
710
+
711
+
682
712
  }
683
713
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/bulk-ai-flow",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },