fix: use configured cooldown as cap for rate limit wait times

- Cooldown now caps API-provided reset times instead of being a fallback
- Fixed misleading UI descriptions for cooldown settings
- Removed unused cooldownDurationMs from settings object
- Updated default fallback values in frontend to 10s

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Badri Narayanan S
2026-01-13 18:28:52 +05:30
parent 49e536e9a9
commit 632536e2d7
5 changed files with 31 additions and 20 deletions

View File

@@ -267,9 +267,10 @@ document.addEventListener('alpine:init', () => {
persistentSessions: "Persistent Sessions", persistentSessions: "Persistent Sessions",
persistTokenDesc: "Save OAuth sessions to disk for faster restarts", persistTokenDesc: "Save OAuth sessions to disk for faster restarts",
rateLimiting: "Account Rate Limiting & Timeouts", rateLimiting: "Account Rate Limiting & Timeouts",
defaultCooldown: "Default Cooldown Time", defaultCooldown: "Default Cooldown",
maxWaitThreshold: "Max Wait Threshold (Sticky)", defaultCooldownDesc: "Fallback cooldown when API doesn't provide a reset time.",
maxWaitDesc: "Maximum time to wait for a sticky account to reset before switching.", maxWaitThreshold: "Max Wait Before Error",
maxWaitDesc: "If all accounts are rate-limited longer than this, error immediately instead of waiting.",
saveConfigServer: "Save Configuration", saveConfigServer: "Save Configuration",
serverRestartAlert: "Changes saved to {path}. Restart server to apply some settings.", serverRestartAlert: "Changes saved to {path}. Restart server to apply some settings.",
changePassword: "Change WebUI Password", changePassword: "Change WebUI Password",
@@ -544,8 +545,9 @@ document.addEventListener('alpine:init', () => {
persistTokenDesc: "将登录会话保存到磁盘以实现快速重启", persistTokenDesc: "将登录会话保存到磁盘以实现快速重启",
rateLimiting: "账号限流与超时", rateLimiting: "账号限流与超时",
defaultCooldown: "默认冷却时间", defaultCooldown: "默认冷却时间",
maxWaitThreshold: "最大等待阈值 (粘性会话)", defaultCooldownDesc: "当 API 未提供重置时间时的备用冷却时间。",
maxWaitDesc: "粘性账号在失败或切换前等待重置的最长时间。", maxWaitThreshold: "最大等待阈值",
maxWaitDesc: "如果所有账号的限流时间超过此阈值,立即返回错误而非等待。",
saveConfigServer: "保存配置", saveConfigServer: "保存配置",
serverRestartAlert: "配置已保存至 {path}。部分更改可能需要重启服务器。", serverRestartAlert: "配置已保存至 {path}。部分更改可能需要重启服务器。",
changePassword: "修改 WebUI 密码", changePassword: "修改 WebUI 密码",

View File

@@ -1052,27 +1052,29 @@
<span class="label-text text-gray-400 text-xs" <span class="label-text text-gray-400 text-xs"
x-text="$store.global.t('defaultCooldown')">Default Cooldown</span> x-text="$store.global.t('defaultCooldown')">Default Cooldown</span>
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold" <span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
x-text="Math.round((serverConfig.defaultCooldownMs || 60000) / 1000) + 's'"></span> x-text="Math.round((serverConfig.defaultCooldownMs || 10000) / 1000) + 's'"></span>
</label> </label>
<div class="flex gap-3 items-center"> <div class="flex gap-3 items-center">
<input type="range" min="1000" max="300000" step="1000" <input type="range" min="1000" max="300000" step="1000"
class="custom-range custom-range-cyan flex-1" class="custom-range custom-range-cyan flex-1"
:value="serverConfig.defaultCooldownMs || 60000" :value="serverConfig.defaultCooldownMs || 10000"
:style="`background-size: ${((serverConfig.defaultCooldownMs || 60000) - 1000) / 2990}% 100%`" :style="`background-size: ${((serverConfig.defaultCooldownMs || 10000) - 1000) / 2990}% 100%`"
@input="toggleDefaultCooldownMs($event.target.value)" @input="toggleDefaultCooldownMs($event.target.value)"
aria-label="Default cooldown slider"> aria-label="Default cooldown slider">
<input type="number" min="1000" max="300000" step="1000" <input type="number" min="1000" max="300000" step="1000"
class="input input-xs input-bordered w-24 bg-space-800 border-space-border text-white font-mono text-center" class="input input-xs input-bordered w-24 bg-space-800 border-space-border text-white font-mono text-center"
:value="serverConfig.defaultCooldownMs || 60000" :value="serverConfig.defaultCooldownMs || 10000"
@change="toggleDefaultCooldownMs($event.target.value)" @change="toggleDefaultCooldownMs($event.target.value)"
aria-label="Default cooldown value"> aria-label="Default cooldown value">
</div> </div>
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
x-text="$store.global.t('defaultCooldownDesc')">Fallback cooldown when API doesn't provide a reset time.</p>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label pt-0"> <label class="label pt-0">
<span class="label-text text-gray-400 text-xs" <span class="label-text text-gray-400 text-xs"
x-text="$store.global.t('maxWaitThreshold')">Max Wait (Sticky)</span> x-text="$store.global.t('maxWaitThreshold')">Max Wait Before Error</span>
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold" <span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
x-text="((serverConfig.maxWaitBeforeErrorMs || 120000) >= 60000 ? Math.round((serverConfig.maxWaitBeforeErrorMs || 120000) / 60000) + 'm' : Math.round((serverConfig.maxWaitBeforeErrorMs || 120000) / 1000) + 's')"></span> x-text="((serverConfig.maxWaitBeforeErrorMs || 120000) >= 60000 ? Math.round((serverConfig.maxWaitBeforeErrorMs || 120000) / 60000) + 'm' : Math.round((serverConfig.maxWaitBeforeErrorMs || 120000) / 1000) + 's')"></span>
</label> </label>
@@ -1082,16 +1084,15 @@
:value="serverConfig.maxWaitBeforeErrorMs || 120000" :value="serverConfig.maxWaitBeforeErrorMs || 120000"
:style="`background-size: ${(serverConfig.maxWaitBeforeErrorMs || 120000) / 6000}% 100%`" :style="`background-size: ${(serverConfig.maxWaitBeforeErrorMs || 120000) / 6000}% 100%`"
@input="toggleMaxWaitBeforeErrorMs($event.target.value)" @input="toggleMaxWaitBeforeErrorMs($event.target.value)"
aria-label="Max wait threshold slider"> aria-label="Max wait before error slider">
<input type="number" min="0" max="600000" step="10000" <input type="number" min="0" max="600000" step="10000"
class="input input-xs input-bordered w-24 bg-space-800 border-space-border text-white font-mono text-center" class="input input-xs input-bordered w-24 bg-space-800 border-space-border text-white font-mono text-center"
:value="serverConfig.maxWaitBeforeErrorMs || 120000" :value="serverConfig.maxWaitBeforeErrorMs || 120000"
@change="toggleMaxWaitBeforeErrorMs($event.target.value)" @change="toggleMaxWaitBeforeErrorMs($event.target.value)"
aria-label="Max wait threshold value"> aria-label="Max wait before error value">
</div> </div>
<p class="text-[9px] text-gray-600 mt-1 leading-tight" <p class="text-[9px] text-gray-600 mt-1 leading-tight"
x-text="$store.global.t('maxWaitDesc')">Maximum time to wait for a sticky account to x-text="$store.global.t('maxWaitDesc')">If all accounts are rate-limited longer than this, error immediately.</p>
reset before switching.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -191,7 +191,7 @@ export class AccountManager {
* @param {string} [modelId] - Optional model ID to mark specific limit * @param {string} [modelId] - Optional model ID to mark specific limit
*/ */
markRateLimited(email, resetMs = null, modelId = null) { markRateLimited(email, resetMs = null, modelId = null) {
markLimited(this.#accounts, email, resetMs, this.#settings, modelId); markLimited(this.#accounts, email, resetMs, modelId);
this.saveToDisk(); this.saveToDisk();
} }

View File

@@ -110,16 +110,25 @@ export function resetAllRateLimits(accounts) {
* *
* @param {Array} accounts - Array of account objects * @param {Array} accounts - Array of account objects
* @param {string} email - Email of the account to mark * @param {string} email - Email of the account to mark
* @param {number|null} resetMs - Time in ms until rate limit resets * @param {number|null} resetMs - Time in ms until rate limit resets (from API)
* @param {Object} settings - Settings object with cooldownDurationMs
* @param {string} modelId - Model ID to mark rate limit for * @param {string} modelId - Model ID to mark rate limit for
* @returns {boolean} True if account was found and marked * @returns {boolean} True if account was found and marked
*/ */
export function markRateLimited(accounts, email, resetMs = null, settings = {}, modelId) { export function markRateLimited(accounts, email, resetMs = null, modelId) {
const account = accounts.find(a => a.email === email); const account = accounts.find(a => a.email === email);
if (!account) return false; if (!account) return false;
const cooldownMs = resetMs || settings.cooldownDurationMs || DEFAULT_COOLDOWN_MS; // Use configured cooldown as the maximum wait time
// If API returns a reset time, cap it at DEFAULT_COOLDOWN_MS
// If API doesn't return a reset time, use DEFAULT_COOLDOWN_MS
let cooldownMs;
if (resetMs && resetMs > 0) {
// API provided a reset time - cap it at configured maximum
cooldownMs = Math.min(resetMs, DEFAULT_COOLDOWN_MS);
} else {
// No reset time from API - use configured default
cooldownMs = DEFAULT_COOLDOWN_MS;
}
const resetTime = Date.now() + cooldownMs; const resetTime = Date.now() + cooldownMs;
if (!account.modelRateLimits) { if (!account.modelRateLimits) {

View File

@@ -142,7 +142,6 @@ function saveAccounts(accounts, settings = {}) {
modelRateLimits: acc.modelRateLimits || {} modelRateLimits: acc.modelRateLimits || {}
})), })),
settings: { settings: {
cooldownDurationMs: 60000,
maxRetries: 5, maxRetries: 5,
...settings ...settings
}, },