Skip to content

Commit e36c89b

Browse files
committed
fix: correct (backend) rate limiting
AdminForth/1520/get-resource-data-crashed-some
1 parent 26b3627 commit e36c89b

2 files changed

Lines changed: 59 additions & 21 deletions

File tree

custom/VisionAction.vue

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ const generationModeButtons = computed(() => {
482482
return arrayToReturn;
483483
});
484484
485-
async function regenerateRecord({ recordId }) {
485+
async function regenerateRecord({ recordId }: { recordId: string }) {
486486
487487
const promises = [];
488488
@@ -610,7 +610,8 @@ async function getListOfIds() {
610610
611611
612612
async function runAiActions() {
613-
if (!await checkRateLimits()) {
613+
const { ok, sessionIds } = await checkRateLimits();
614+
if (!ok || !sessionIds) {
614615
confirmDialog.value.close();
615616
return;
616617
}
@@ -625,7 +626,7 @@ async function runAiActions() {
625626
tableRef.value?.refresh();
626627
const limit = pLimit(props.meta.concurrencyLimit || 10);
627628
const tasks = recordIds.value
628-
.map(id => limit(() => processOneRecord(String(id))));
629+
.map(id => limit(() => processOneRecord(String(id), sessionIds)));
629630
await Promise.all(tasks);
630631
showGenerationFailureSummary();
631632
isActiveGeneration.value = false;
@@ -825,7 +826,7 @@ function createEmptyRecord(recordId: string | number): RecordState {
825826
};
826827
}
827828
828-
async function processOneRecord(recordId: string) {
829+
async function processOneRecord(recordId: string, sessionIds: string[]) {
829830
if (!checkIfDialogOpen()) {
830831
return;
831832
}
@@ -898,7 +899,7 @@ async function processOneRecord(recordId: string) {
898899
actions.push('analyze_no_images');
899900
}
900901
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)));
902903
if (!checkIfDialogOpen()) {
903904
return;
904905
}
@@ -908,17 +909,22 @@ async function processOneRecord(recordId: string) {
908909
touchRecords();
909910
}
910911
911-
async function checkRateLimits() {
912+
async function checkRateLimits(actions?: GenerationAction[]): Promise<{ ok: boolean; sessionIds?: string[] }> {
913+
let sessionIds: string[] = [];
912914
isCheckingRateLimits.value = true;
913915
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+
}
922928
}
923929
for (const actionType of actionsToCheck) {
924930
try {
@@ -933,23 +939,24 @@ async function checkRateLimits() {
933939
variant: 'danger',
934940
timeout: 'unlimited',
935941
});
936-
return false;
942+
return { ok: false };
937943
}
944+
sessionIds.push(rateLimitRes.sessionId);
938945
} catch (e) {
939946
adminforth.alert({
940947
message: t(`Error checking rate limit for "${getActionLabel(actionType)}" action.`),
941948
variant: 'danger',
942949
timeout: 'unlimited',
943950
});
944951
isCheckingRateLimits.value = false;
945-
return false;
952+
return { ok: false };
946953
}
947954
}
948955
isCheckingRateLimits.value = false;
949-
return true;
956+
return { ok: true, sessionIds };
950957
}
951958
952-
async function runActionForRecord(record: RecordState, actionType: GenerationAction, forceFilterFilledFields?: boolean) {
959+
async function runActionForRecord(record: RecordState, actionType: GenerationAction, sessionIds: string[], forceFilterFilledFields?: boolean) {
953960
if (!checkIfDialogOpen()) {
954961
return;
955962
}
@@ -982,6 +989,7 @@ async function runActionForRecord(record: RecordState, actionType: GenerationAct
982989
recordId: record.id,
983990
...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
984991
filterFilledFields: forceFilterFilledFields !== undefined ? forceFilterFilledFields: !overwriteExistingValues.value,
992+
sessionIds
985993
},
986994
silentError: true,
987995
});
@@ -1438,6 +1446,10 @@ async function uploadImage(imgBlob, id, fieldName) {
14381446
}
14391447
14401448
async function regenerateImages({ recordId }: { recordId: any }) {
1449+
const {ok, sessionIds} = await checkRateLimits(['generate_images']);
1450+
if (!ok || !sessionIds) {
1451+
return;
1452+
}
14411453
if (coreStore.isInternetError) {
14421454
adminforth.alert({
14431455
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 }) {
14581470
await runActionForRecord(
14591471
record,
14601472
'generate_images',
1473+
sessionIds,
14611474
false
14621475
);
14631476
@@ -1616,6 +1629,12 @@ async function regenerateCell(recordInfo: any) {
16161629
});
16171630
return;
16181631
}
1632+
1633+
const {ok, sessionIds} = await checkRateLimits(['analyze_no_images', 'analyze']);
1634+
if (!ok || !sessionIds) {
1635+
return;
1636+
}
1637+
16191638
const record = recordsById.get(String(recordInfo.recordId));
16201639
if (!record) {
16211640
return;
@@ -1658,6 +1677,7 @@ async function regenerateCell(recordInfo: any) {
16581677
action: actionType,
16591678
actionType: "regenerate_cell",
16601679
prompt: generationPromptsForField[recordInfo.fieldName] || null,
1680+
sessionIds
16611681
},
16621682
silentError: true,
16631683
});

index.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
1313
totalCalls: number;
1414
totalDuration: number;
1515
rateLimiters: Record<string, RateLimiter> = {};
16+
sessionIds: Set<string> = new Set();
1617

1718
constructor(options: PluginOptions) {
1819
super(options, import.meta.url);
@@ -21,6 +22,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
2122
// for calculating average time
2223
this.totalCalls = 0;
2324
this.totalDuration = 0;
25+
26+
// for rate limiting
27+
this.sessionIds = new Set();
2428
}
2529

2630
// Compile Handlebars templates in outputFields using record fields as context
@@ -970,7 +974,19 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
970974
method: 'POST',
971975
path: `/plugin/${this.pluginInstanceId}/create-job`,
972976
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+
}
974990
const jobId = randomUUID();
975991
jobs.set(jobId, { status: "in_progress" });
976992
if (!actionType) {
@@ -1056,6 +1072,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10561072
path: `/plugin/${this.pluginInstanceId}/update-rate-limits`,
10571073
handler: async ({ body, adminUser, headers }) => {
10581074
const actionType = body.actionType;
1075+
const sessionId = randomUUID();
1076+
this.sessionIds.add(sessionId);
10591077
if (actionType === 'analyze' && this.options.rateLimits?.fillFieldsFromImages) {
10601078
if (await this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
10611079
return {ok: false, error: "Rate limit exceeded for image analyze" };
@@ -1071,8 +1089,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10711089
return {ok: false, error: "Rate limit exceeded for image generation" };
10721090
}
10731091
}
1074-
1075-
return { ok: true };
1092+
setTimeout(() => this.sessionIds.delete(sessionId), 300_000);
1093+
return { ok: true, sessionId };
10761094
}
10771095
});
10781096

0 commit comments

Comments
 (0)