diff --git a/public/scripts/poe.js b/public/scripts/poe.js index 4273a6e06..cf0e97613 100644 --- a/public/scripts/poe.js +++ b/public/scripts/poe.js @@ -14,7 +14,7 @@ import { secret_state, writeSecret, } from "./secrets.js"; -import { delay, splitRecursive } from "./utils.js"; +import { RateLimiter, delay, splitRecursive } from "./utils.js"; export { is_get_status_poe, @@ -66,6 +66,8 @@ let is_get_status_poe = false; let is_poe_button_press = false; let abortControllerSuggest = null; +const rateLimiter = new RateLimiter((60 / 10 * 1000)); // 10 requests per minute + function loadPoeSettings(settings) { if (settings.poe_settings) { Object.assign(poe_settings, settings.poe_settings); @@ -334,6 +336,8 @@ async function sendMessage(prompt, withStreaming, withSuggestions, signal) { signal = new AbortController().signal; } + await rateLimiter.waitForResolve(signal); + const body = JSON.stringify({ bot: poe_settings.bot, streaming: withStreaming && poe_settings.streaming, diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 552a3b693..3cd12c7c3 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -435,3 +435,39 @@ export function getCharaFilename() { export function escapeRegex(string) { return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); } + +export class RateLimiter { + constructor(intervalMillis) { + this._intervalMillis = intervalMillis; + this._lastResolveTime = 0; + this._pendingResolve = Promise.resolve(); + } + + _waitRemainingTime(abortSignal) { + const currentTime = Date.now(); + const elapsedTime = currentTime - this._lastResolveTime; + const remainingTime = Math.max(0, this._intervalMillis - elapsedTime); + + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + resolve(); + }, remainingTime); + + if (abortSignal) { + abortSignal.addEventListener('abort', () => { + clearTimeout(timeoutId); + reject(new Error('Aborted')); + }); + } + }); + } + + async waitForResolve(abortSignal) { + await this._pendingResolve; + this._pendingResolve = this._waitRemainingTime(abortSignal); + + // Update the last resolve time + this._lastResolveTime = Date.now() + this._intervalMillis; + console.debug(`RateLimiter.waitForResolve() ${this._lastResolveTime}`); + } +}