feat: add configurable account selection strategies
Refactor account selection into a strategy pattern with three options: - Sticky: cache-optimized, stays on same account until rate-limited - Round-robin: load-balanced, rotates every request - Hybrid (default): smart distribution using health scores, token buckets, and LRU The hybrid strategy uses multiple signals for optimal account selection: health tracking for reliability, client-side token buckets for rate limiting, and LRU freshness to prefer rested accounts. Includes WebUI settings for strategy selection and unit tests. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -248,5 +248,102 @@ window.Components.serverConfig = () => ({
|
||||
const { MAX_WAIT_MIN, MAX_WAIT_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('maxWaitBeforeErrorMs', value, 'Max Wait Threshold',
|
||||
(v) => window.Validators.validateTimeout(v, MAX_WAIT_MIN, MAX_WAIT_MAX));
|
||||
},
|
||||
|
||||
toggleRateLimitDedupWindowMs(value) {
|
||||
const { RATE_LIMIT_DEDUP_MIN, RATE_LIMIT_DEDUP_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('rateLimitDedupWindowMs', value, 'Rate Limit Dedup Window',
|
||||
(v) => window.Validators.validateTimeout(v, RATE_LIMIT_DEDUP_MIN, RATE_LIMIT_DEDUP_MAX));
|
||||
},
|
||||
|
||||
toggleMaxConsecutiveFailures(value) {
|
||||
const { MAX_CONSECUTIVE_FAILURES_MIN, MAX_CONSECUTIVE_FAILURES_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('maxConsecutiveFailures', value, 'Max Consecutive Failures',
|
||||
(v) => window.Validators.validateRange(v, MAX_CONSECUTIVE_FAILURES_MIN, MAX_CONSECUTIVE_FAILURES_MAX, 'Max Consecutive Failures'));
|
||||
},
|
||||
|
||||
toggleExtendedCooldownMs(value) {
|
||||
const { EXTENDED_COOLDOWN_MIN, EXTENDED_COOLDOWN_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('extendedCooldownMs', value, 'Extended Cooldown',
|
||||
(v) => window.Validators.validateTimeout(v, EXTENDED_COOLDOWN_MIN, EXTENDED_COOLDOWN_MAX));
|
||||
},
|
||||
|
||||
toggleCapacityRetryDelayMs(value) {
|
||||
const { CAPACITY_RETRY_DELAY_MIN, CAPACITY_RETRY_DELAY_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('capacityRetryDelayMs', value, 'Capacity Retry Delay',
|
||||
(v) => window.Validators.validateTimeout(v, CAPACITY_RETRY_DELAY_MIN, CAPACITY_RETRY_DELAY_MAX));
|
||||
},
|
||||
|
||||
toggleMaxCapacityRetries(value) {
|
||||
const { MAX_CAPACITY_RETRIES_MIN, MAX_CAPACITY_RETRIES_MAX } = window.AppConstants.VALIDATION;
|
||||
this.saveConfigField('maxCapacityRetries', value, 'Max Capacity Retries',
|
||||
(v) => window.Validators.validateRange(v, MAX_CAPACITY_RETRIES_MIN, MAX_CAPACITY_RETRIES_MAX, 'Max Capacity Retries'));
|
||||
},
|
||||
|
||||
// Toggle Account Selection Strategy
|
||||
async toggleStrategy(strategy) {
|
||||
const store = Alpine.store('global');
|
||||
const validStrategies = ['sticky', 'round-robin', 'hybrid'];
|
||||
|
||||
if (!validStrategies.includes(strategy)) {
|
||||
store.showToast(store.t('invalidStrategy'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimistic update
|
||||
const previousValue = this.serverConfig.accountSelection?.strategy || 'hybrid';
|
||||
if (!this.serverConfig.accountSelection) {
|
||||
this.serverConfig.accountSelection = {};
|
||||
}
|
||||
this.serverConfig.accountSelection.strategy = strategy;
|
||||
|
||||
try {
|
||||
const { response, newPassword } = await window.utils.request('/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accountSelection: { strategy } })
|
||||
}, store.webuiPassword);
|
||||
|
||||
if (newPassword) store.webuiPassword = newPassword;
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status === 'ok') {
|
||||
const strategyLabel = this.getStrategyLabel(strategy);
|
||||
store.showToast(store.t('strategyUpdated', { strategy: strategyLabel }), 'success');
|
||||
await this.fetchServerConfig(); // Confirm server state
|
||||
} else {
|
||||
throw new Error(data.error || store.t('failedToUpdateStrategy'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Rollback on error
|
||||
if (!this.serverConfig.accountSelection) {
|
||||
this.serverConfig.accountSelection = {};
|
||||
}
|
||||
this.serverConfig.accountSelection.strategy = previousValue;
|
||||
store.showToast(store.t('failedToUpdateStrategy') + ': ' + e.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Get display label for a strategy
|
||||
getStrategyLabel(strategy) {
|
||||
const store = Alpine.store('global');
|
||||
const labels = {
|
||||
'sticky': store.t('strategyStickyLabel'),
|
||||
'round-robin': store.t('strategyRoundRobinLabel'),
|
||||
'hybrid': store.t('strategyHybridLabel')
|
||||
};
|
||||
return labels[strategy] || strategy;
|
||||
},
|
||||
|
||||
// Get description for current strategy
|
||||
currentStrategyDescription() {
|
||||
const store = Alpine.store('global');
|
||||
const strategy = this.serverConfig.accountSelection?.strategy || 'hybrid';
|
||||
const descriptions = {
|
||||
'sticky': store.t('strategyStickyDesc'),
|
||||
'round-robin': store.t('strategyRoundRobinDesc'),
|
||||
'hybrid': store.t('strategyHybridDesc')
|
||||
};
|
||||
return descriptions[strategy] || '';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -67,7 +67,27 @@ window.AppConstants.VALIDATION = {
|
||||
|
||||
// Max wait threshold (1 - 30 minutes)
|
||||
MAX_WAIT_MIN: 60000,
|
||||
MAX_WAIT_MAX: 1800000
|
||||
MAX_WAIT_MAX: 1800000,
|
||||
|
||||
// Rate limit dedup window (1 - 30 seconds)
|
||||
RATE_LIMIT_DEDUP_MIN: 1000,
|
||||
RATE_LIMIT_DEDUP_MAX: 30000,
|
||||
|
||||
// Consecutive failures (1 - 10)
|
||||
MAX_CONSECUTIVE_FAILURES_MIN: 1,
|
||||
MAX_CONSECUTIVE_FAILURES_MAX: 10,
|
||||
|
||||
// Extended cooldown (10 seconds - 5 minutes)
|
||||
EXTENDED_COOLDOWN_MIN: 10000,
|
||||
EXTENDED_COOLDOWN_MAX: 300000,
|
||||
|
||||
// Capacity retry delay (500ms - 10 seconds)
|
||||
CAPACITY_RETRY_DELAY_MIN: 500,
|
||||
CAPACITY_RETRY_DELAY_MAX: 10000,
|
||||
|
||||
// Capacity retries (1 - 10)
|
||||
MAX_CAPACITY_RETRIES_MIN: 1,
|
||||
MAX_CAPACITY_RETRIES_MAX: 10
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -237,6 +237,18 @@ window.translations.en = {
|
||||
defaultCooldownDesc: "Fallback cooldown when API doesn't provide a reset time.",
|
||||
maxWaitThreshold: "Max Wait Before Error",
|
||||
maxWaitDesc: "If all accounts are rate-limited longer than this, error immediately instead of waiting.",
|
||||
// Error Handling Tuning
|
||||
errorHandlingTuning: "Error Handling Tuning",
|
||||
rateLimitDedupWindow: "Rate Limit Dedup Window",
|
||||
rateLimitDedupWindowDesc: "Prevents concurrent retry storms when multiple requests hit rate limits simultaneously.",
|
||||
maxConsecutiveFailures: "Max Consecutive Failures",
|
||||
maxConsecutiveFailuresDesc: "Number of consecutive failures before applying extended cooldown to an account.",
|
||||
extendedCooldown: "Extended Cooldown",
|
||||
extendedCooldownDesc: "Cooldown duration applied after max consecutive failures reached.",
|
||||
capacityRetryDelay: "Capacity Retry Delay",
|
||||
capacityRetryDelayDesc: "Delay before retrying when model capacity is exhausted (not quota).",
|
||||
maxCapacityRetries: "Max Capacity Retries",
|
||||
maxCapacityRetriesDesc: "Maximum retries for capacity exhaustion before switching accounts.",
|
||||
saveConfigServer: "Save Configuration",
|
||||
serverRestartAlert: "Changes saved to {path}. Restart server to apply some settings.",
|
||||
changePassword: "Change WebUI Password",
|
||||
@@ -318,6 +330,18 @@ window.translations.en = {
|
||||
failedToUpdateModelConfig: "Failed to update model config",
|
||||
fieldUpdated: "{displayName} updated to {value}",
|
||||
failedToUpdateField: "Failed to update {displayName}",
|
||||
// Account Selection Strategy
|
||||
accountSelectionStrategy: "Account Selection Strategy",
|
||||
selectionStrategy: "Selection Strategy",
|
||||
strategyStickyLabel: "Sticky (Cache Optimized)",
|
||||
strategyRoundRobinLabel: "Round Robin (Load Balanced)",
|
||||
strategyHybridLabel: "Hybrid (Smart Distribution)",
|
||||
strategyStickyDesc: "Stays on same account until rate-limited. Best for prompt caching.",
|
||||
strategyRoundRobinDesc: "Rotates to next account on every request. Maximum throughput.",
|
||||
strategyHybridDesc: "Smart selection based on health, tokens, and freshness.",
|
||||
strategyUpdated: "Strategy updated to: {strategy}",
|
||||
failedToUpdateStrategy: "Failed to update strategy",
|
||||
invalidStrategy: "Invalid strategy selected",
|
||||
// Validation Messages
|
||||
mustBeValidNumber: "{fieldName} must be a valid number",
|
||||
mustBeAtLeast: "{fieldName} must be at least {min}",
|
||||
|
||||
@@ -270,6 +270,18 @@ window.translations.id = {
|
||||
defaultCooldownDesc: "Cooldown bawaan jika API tidak memberikan waktu reset.",
|
||||
maxWaitThreshold: "Batas Tunggu Maksimal",
|
||||
maxWaitDesc: "Jika semua akun terkena rate limit lebih lama dari ini, langsung gagal.",
|
||||
// Error Handling Tuning
|
||||
errorHandlingTuning: "Penyetelan Penanganan Error",
|
||||
rateLimitDedupWindow: "Jendela Deduplikasi Rate Limit",
|
||||
rateLimitDedupWindowDesc: "Mencegah badai retry ketika beberapa permintaan terkena rate limit bersamaan.",
|
||||
maxConsecutiveFailures: "Maks. Kegagalan Berturut-turut",
|
||||
maxConsecutiveFailuresDesc: "Jumlah kegagalan berturut-turut sebelum menerapkan cooldown diperpanjang.",
|
||||
extendedCooldown: "Cooldown Diperpanjang",
|
||||
extendedCooldownDesc: "Durasi cooldown setelah mencapai maks. kegagalan berturut-turut.",
|
||||
capacityRetryDelay: "Jeda Retry Kapasitas",
|
||||
capacityRetryDelayDesc: "Jeda sebelum retry saat kapasitas model habis (bukan kuota).",
|
||||
maxCapacityRetries: "Maks. Retry Kapasitas",
|
||||
maxCapacityRetriesDesc: "Maksimum retry untuk kehabisan kapasitas sebelum ganti akun.",
|
||||
saveConfigServer: "Simpan Konfigurasi",
|
||||
serverRestartAlert: "Tersimpan ke {path}. Restart server untuk menerapkan.",
|
||||
|
||||
@@ -368,4 +380,17 @@ window.translations.id = {
|
||||
mustBeAtMost: "{fieldName} maksimal {max}",
|
||||
cannotBeEmpty: "{fieldName} tidak boleh kosong",
|
||||
mustBeTrueOrFalse: "Nilai harus true atau false",
|
||||
|
||||
// Account Selection Strategy translations
|
||||
accountSelectionStrategy: "Strategi Pemilihan Akun",
|
||||
selectionStrategy: "Strategi Pemilihan",
|
||||
strategyStickyLabel: "Tetap (Optimisasi Cache)",
|
||||
strategyRoundRobinLabel: "Bergilir (Load Balanced)",
|
||||
strategyHybridLabel: "Hibrida (Distribusi Cerdas)",
|
||||
strategyStickyDesc: "Tetap di akun yang sama hingga terkena rate limit. Terbaik untuk cache prompt.",
|
||||
strategyRoundRobinDesc: "Berputar ke akun berikutnya setiap permintaan. Throughput maksimum.",
|
||||
strategyHybridDesc: "Pemilihan cerdas berdasarkan kesehatan, token, dan kesegaran.",
|
||||
strategyUpdated: "Strategi diubah ke: {strategy}",
|
||||
failedToUpdateStrategy: "Gagal memperbarui strategi",
|
||||
invalidStrategy: "Strategi tidak valid dipilih",
|
||||
};
|
||||
|
||||
@@ -212,8 +212,21 @@ window.translations.pt = {
|
||||
persistTokenDesc: "Salvar sessões OAuth no disco para reinicializações mais rápidas",
|
||||
rateLimiting: "Limitação de Taxa de Conta & Timeouts",
|
||||
defaultCooldown: "Tempo de Resfriamento Padrão",
|
||||
defaultCooldownDesc: "Resfriamento de fallback quando a API não fornece tempo de reset.",
|
||||
maxWaitThreshold: "Limiar Máximo de Espera (Sticky)",
|
||||
maxWaitDesc: "Tempo máximo para aguardar uma conta sticky resetar antes de trocar.",
|
||||
// Ajuste de Tratamento de Erros
|
||||
errorHandlingTuning: "Ajuste de Tratamento de Erros",
|
||||
rateLimitDedupWindow: "Janela de Deduplicação de Rate Limit",
|
||||
rateLimitDedupWindowDesc: "Previne tempestades de retry quando múltiplas requisições atingem rate limits simultaneamente.",
|
||||
maxConsecutiveFailures: "Máx. Falhas Consecutivas",
|
||||
maxConsecutiveFailuresDesc: "Número de falhas consecutivas antes de aplicar resfriamento estendido.",
|
||||
extendedCooldown: "Resfriamento Estendido",
|
||||
extendedCooldownDesc: "Duração do resfriamento aplicado após atingir máx. de falhas consecutivas.",
|
||||
capacityRetryDelay: "Atraso de Retry de Capacidade",
|
||||
capacityRetryDelayDesc: "Atraso antes de tentar novamente quando capacidade do modelo está esgotada (não quota).",
|
||||
maxCapacityRetries: "Máx. Retries de Capacidade",
|
||||
maxCapacityRetriesDesc: "Máximo de retries para esgotamento de capacidade antes de trocar conta.",
|
||||
saveConfigServer: "Salvar Configuração",
|
||||
serverRestartAlert: "Alterações salvas em {path}. Reinicie o servidor para aplicar algumas configurações.",
|
||||
changePassword: "Alterar Senha da WebUI",
|
||||
@@ -258,4 +271,17 @@ window.translations.pt = {
|
||||
gemini1mDesc: "Adiciona sufixo [1m] aos modelos Gemini para suporte a janela de contexto de 1M.",
|
||||
gemini1mWarning: "⚠ Contexto grande pode reduzir o desempenho do Gemini-3-Pro.",
|
||||
clickToSet: "Clique para configurar...",
|
||||
|
||||
// Account Selection Strategy translations
|
||||
accountSelectionStrategy: "Estratégia de Seleção de Conta",
|
||||
selectionStrategy: "Estratégia de Seleção",
|
||||
strategyStickyLabel: "Fixo (Otimizado para Cache)",
|
||||
strategyRoundRobinLabel: "Rodízio (Balanceamento de Carga)",
|
||||
strategyHybridLabel: "Híbrido (Distribuição Inteligente)",
|
||||
strategyStickyDesc: "Permanece na mesma conta até atingir limite. Melhor para cache de prompts.",
|
||||
strategyRoundRobinDesc: "Alterna para próxima conta a cada requisição. Máximo throughput.",
|
||||
strategyHybridDesc: "Seleção inteligente baseada em saúde, tokens e frescor.",
|
||||
strategyUpdated: "Estratégia atualizada para: {strategy}",
|
||||
failedToUpdateStrategy: "Falha ao atualizar estratégia",
|
||||
invalidStrategy: "Estratégia inválida selecionada",
|
||||
};
|
||||
|
||||
@@ -216,8 +216,21 @@ window.translations.tr = {
|
||||
persistTokenDesc: "Daha hızlı yeniden başlatmalar için OAuth oturumlarını diske kaydet",
|
||||
rateLimiting: "Hesap Hız Sınırlama ve Zaman Aşımları",
|
||||
defaultCooldown: "Varsayılan Soğuma Süresi",
|
||||
defaultCooldownDesc: "API sıfırlama zamanı sağlamadığında yedek soğuma süresi.",
|
||||
maxWaitThreshold: "Maksimum Bekleme Eşiği (Yapışkan)",
|
||||
maxWaitDesc: "Yapışkan bir hesabın değiştirmeden önce sıfırlanması için beklenecek maksimum süre.",
|
||||
// Hata İşleme Ayarları
|
||||
errorHandlingTuning: "Hata İşleme Ayarları",
|
||||
rateLimitDedupWindow: "Hız Sınırı Tekilleştirme Penceresi",
|
||||
rateLimitDedupWindowDesc: "Birden fazla istek aynı anda hız sınırına ulaştığında yeniden deneme fırtınasını önler.",
|
||||
maxConsecutiveFailures: "Maks. Ardışık Başarısızlık",
|
||||
maxConsecutiveFailuresDesc: "Uzatılmış soğuma uygulamadan önce ardışık başarısızlık sayısı.",
|
||||
extendedCooldown: "Uzatılmış Soğuma",
|
||||
extendedCooldownDesc: "Maks. ardışık başarısızlık sonrası uygulanan soğuma süresi.",
|
||||
capacityRetryDelay: "Kapasite Yeniden Deneme Gecikmesi",
|
||||
capacityRetryDelayDesc: "Model kapasitesi tükendiğinde (kota değil) yeniden denemeden önceki gecikme.",
|
||||
maxCapacityRetries: "Maks. Kapasite Yeniden Denemesi",
|
||||
maxCapacityRetriesDesc: "Hesap değiştirmeden önce kapasite tükenmesi için maksimum yeniden deneme.",
|
||||
saveConfigServer: "Yapılandırmayı Kaydet",
|
||||
serverRestartAlert: "Değişiklikler {path} konumuna kaydedildi. Bazı ayarları uygulamak için sunucuyu yeniden başlatın.",
|
||||
changePassword: "WebUI Parolasını Değiştir",
|
||||
@@ -313,4 +326,17 @@ window.translations.tr = {
|
||||
|
||||
// TODO: Missing translation - Server config (exists in EN but missing here)
|
||||
// defaultCooldownDesc: "Fallback cooldown when API doesn't provide a reset time.",
|
||||
|
||||
// Account Selection Strategy translations
|
||||
accountSelectionStrategy: "Hesap Seçim Stratejisi",
|
||||
selectionStrategy: "Seçim Stratejisi",
|
||||
strategyStickyLabel: "Sabit (Önbellek Optimizasyonu)",
|
||||
strategyRoundRobinLabel: "Döngüsel (Yük Dengeleme)",
|
||||
strategyHybridLabel: "Hibrit (Akıllı Dağıtım)",
|
||||
strategyStickyDesc: "Hız sınırına ulaşılana kadar aynı hesapta kalır. Önbellek için en iyisi.",
|
||||
strategyRoundRobinDesc: "Her istekte bir sonraki hesaba geçer. Maksimum verimlilik.",
|
||||
strategyHybridDesc: "Sağlık, token ve tazeliğe dayalı akıllı seçim.",
|
||||
strategyUpdated: "Strateji şu şekilde güncellendi: {strategy}",
|
||||
failedToUpdateStrategy: "Strateji güncellenemedi",
|
||||
invalidStrategy: "Geçersiz strateji seçildi",
|
||||
};
|
||||
|
||||
@@ -237,6 +237,18 @@ window.translations.zh = {
|
||||
defaultCooldownDesc: "当 API 未提供重置时间时的备用冷却时间。",
|
||||
maxWaitThreshold: "最大等待阈值",
|
||||
maxWaitDesc: "如果所有账号的限流时间超过此阈值,立即返回错误而非等待。",
|
||||
// 错误处理调优
|
||||
errorHandlingTuning: "错误处理调优",
|
||||
rateLimitDedupWindow: "限流去重窗口",
|
||||
rateLimitDedupWindowDesc: "当多个请求同时触发限流时,防止并发重试风暴。",
|
||||
maxConsecutiveFailures: "最大连续失败次数",
|
||||
maxConsecutiveFailuresDesc: "触发扩展冷却前允许的连续失败次数。",
|
||||
extendedCooldown: "扩展冷却时间",
|
||||
extendedCooldownDesc: "达到最大连续失败后应用的冷却时长。",
|
||||
capacityRetryDelay: "容量重试延迟",
|
||||
capacityRetryDelayDesc: "模型容量耗尽(非配额)时重试前的延迟。",
|
||||
maxCapacityRetries: "最大容量重试次数",
|
||||
maxCapacityRetriesDesc: "容量耗尽时在切换账号前的最大重试次数。",
|
||||
saveConfigServer: "保存配置",
|
||||
serverRestartAlert: "配置已保存至 {path}。部分更改可能需要重启服务器。",
|
||||
changePassword: "修改 WebUI 密码",
|
||||
@@ -329,4 +341,17 @@ window.translations.zh = {
|
||||
// mustBeAtMost: "{fieldName} must be at most {max}",
|
||||
// cannotBeEmpty: "{fieldName} cannot be empty",
|
||||
// mustBeTrueOrFalse: "Value must be true or false",
|
||||
|
||||
// Account Selection Strategy translations
|
||||
accountSelectionStrategy: "账户选择策略",
|
||||
selectionStrategy: "选择策略",
|
||||
strategyStickyLabel: "固定 (缓存优化)",
|
||||
strategyRoundRobinLabel: "轮询 (负载均衡)",
|
||||
strategyHybridLabel: "混合 (智能分配)",
|
||||
strategyStickyDesc: "保持使用同一账户直到被限速。最适合提示词缓存。",
|
||||
strategyRoundRobinDesc: "每次请求轮换到下一个账户。最大吞吐量。",
|
||||
strategyHybridDesc: "基于健康度、令牌和新鲜度的智能选择。",
|
||||
strategyUpdated: "策略已更新为: {strategy}",
|
||||
failedToUpdateStrategy: "更新策略失败",
|
||||
invalidStrategy: "选择了无效的策略",
|
||||
};
|
||||
|
||||
@@ -936,6 +936,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🔀 Account Selection Strategy -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-2 mb-1 px-1">
|
||||
<span class="text-[10px] uppercase text-gray-500 font-bold tracking-widest"
|
||||
x-text="$store.global.t('accountSelectionStrategy')">Account Selection Strategy</span>
|
||||
<div class="h-px flex-1 bg-space-border/30"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-control view-card border-space-border/50 hover:border-neon-cyan/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-sm font-medium text-gray-200"
|
||||
x-text="$store.global.t('selectionStrategy')">Selection Strategy</span>
|
||||
<span class="text-[11px] text-gray-500"
|
||||
x-text="currentStrategyDescription()">How accounts are selected for requests</span>
|
||||
</div>
|
||||
<select
|
||||
class="select bg-space-800 border-space-border text-gray-200 focus:border-neon-cyan focus:ring-neon-cyan/20 w-64"
|
||||
:value="serverConfig.accountSelection?.strategy || 'hybrid'"
|
||||
@change="toggleStrategy($el.value)"
|
||||
aria-label="Account selection strategy">
|
||||
<option value="hybrid" x-text="$store.global.t('strategyHybridLabel')">Hybrid (Smart Distribution)</option>
|
||||
<option value="sticky" x-text="$store.global.t('strategyStickyLabel')">Sticky (Cache Optimized)</option>
|
||||
<option value="round-robin" x-text="$store.global.t('strategyRoundRobinLabel')">Round Robin (Load Balanced)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ▼ Advanced Tuning (Fixed Logic) -->
|
||||
<div class="view-card !p-0 border-space-border/50">
|
||||
<div class="flex items-center justify-between p-4 cursor-pointer hover:bg-white/5 transition-colors"
|
||||
@@ -1095,6 +1124,134 @@
|
||||
x-text="$store.global.t('maxWaitDesc')">If all accounts are rate-limited longer than this, error immediately.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Handling Tuning -->
|
||||
<div class="space-y-4 pt-2 border-t border-space-border/10">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-[10px] text-gray-500 font-bold uppercase tracking-widest"
|
||||
x-text="$store.global.t('errorHandlingTuning')">Error Handling Tuning</span>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label pt-0">
|
||||
<span class="label-text text-gray-400 text-xs"
|
||||
x-text="$store.global.t('rateLimitDedupWindow')">Rate Limit Dedup Window</span>
|
||||
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
||||
x-text="Math.round((serverConfig.rateLimitDedupWindowMs || 5000) / 1000) + 's'"></span>
|
||||
</label>
|
||||
<div class="flex gap-3 items-center">
|
||||
<input type="range" min="1000" max="30000" step="1000"
|
||||
class="custom-range custom-range-cyan flex-1"
|
||||
:value="serverConfig.rateLimitDedupWindowMs || 5000"
|
||||
:style="`background-size: ${((serverConfig.rateLimitDedupWindowMs || 5000) - 1000) / 290}% 100%`"
|
||||
@input="toggleRateLimitDedupWindowMs($event.target.value)"
|
||||
aria-label="Rate limit dedup window slider">
|
||||
<input type="number" min="1000" max="30000" step="1000"
|
||||
class="input input-xs input-bordered w-20 bg-space-800 border-space-border text-white font-mono text-center"
|
||||
:value="serverConfig.rateLimitDedupWindowMs || 5000"
|
||||
@change="toggleRateLimitDedupWindowMs($event.target.value)"
|
||||
aria-label="Rate limit dedup window value">
|
||||
</div>
|
||||
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
|
||||
x-text="$store.global.t('rateLimitDedupWindowDesc')">Prevents concurrent retry storms.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label pt-0">
|
||||
<span class="label-text text-gray-400 text-xs"
|
||||
x-text="$store.global.t('maxConsecutiveFailures')">Max Consecutive Failures</span>
|
||||
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
||||
x-text="serverConfig.maxConsecutiveFailures || 3"></span>
|
||||
</label>
|
||||
<div class="flex gap-3 items-center">
|
||||
<input type="range" min="1" max="10" step="1"
|
||||
class="custom-range custom-range-cyan flex-1"
|
||||
:value="serverConfig.maxConsecutiveFailures || 3"
|
||||
:style="`background-size: ${((serverConfig.maxConsecutiveFailures || 3) - 1) / 0.09}% 100%`"
|
||||
@input="toggleMaxConsecutiveFailures($event.target.value)"
|
||||
aria-label="Max consecutive failures slider">
|
||||
<input type="number" min="1" max="10" step="1"
|
||||
class="input input-xs input-bordered w-16 bg-space-800 border-space-border text-white font-mono text-center"
|
||||
:value="serverConfig.maxConsecutiveFailures || 3"
|
||||
@change="toggleMaxConsecutiveFailures($event.target.value)"
|
||||
aria-label="Max consecutive failures value">
|
||||
</div>
|
||||
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
|
||||
x-text="$store.global.t('maxConsecutiveFailuresDesc')">Failures before extended cooldown.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label pt-0">
|
||||
<span class="label-text text-gray-400 text-xs"
|
||||
x-text="$store.global.t('extendedCooldown')">Extended Cooldown</span>
|
||||
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
||||
x-text="((serverConfig.extendedCooldownMs || 60000) >= 60000 ? Math.round((serverConfig.extendedCooldownMs || 60000) / 60000) + 'm' : Math.round((serverConfig.extendedCooldownMs || 60000) / 1000) + 's')"></span>
|
||||
</label>
|
||||
<div class="flex gap-3 items-center">
|
||||
<input type="range" min="10000" max="300000" step="10000"
|
||||
class="custom-range custom-range-cyan flex-1"
|
||||
:value="serverConfig.extendedCooldownMs || 60000"
|
||||
:style="`background-size: ${((serverConfig.extendedCooldownMs || 60000) - 10000) / 2900}% 100%`"
|
||||
@input="toggleExtendedCooldownMs($event.target.value)"
|
||||
aria-label="Extended cooldown slider">
|
||||
<input type="number" min="10000" max="300000" step="10000"
|
||||
class="input input-xs input-bordered w-24 bg-space-800 border-space-border text-white font-mono text-center"
|
||||
:value="serverConfig.extendedCooldownMs || 60000"
|
||||
@change="toggleExtendedCooldownMs($event.target.value)"
|
||||
aria-label="Extended cooldown value">
|
||||
</div>
|
||||
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
|
||||
x-text="$store.global.t('extendedCooldownDesc')">Applied after max consecutive failures.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label pt-0">
|
||||
<span class="label-text text-gray-400 text-xs"
|
||||
x-text="$store.global.t('capacityRetryDelay')">Capacity Retry Delay</span>
|
||||
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
||||
x-text="Math.round((serverConfig.capacityRetryDelayMs || 2000) / 1000) + 's'"></span>
|
||||
</label>
|
||||
<div class="flex gap-3 items-center">
|
||||
<input type="range" min="500" max="10000" step="500"
|
||||
class="custom-range custom-range-cyan flex-1"
|
||||
:value="serverConfig.capacityRetryDelayMs || 2000"
|
||||
:style="`background-size: ${((serverConfig.capacityRetryDelayMs || 2000) - 500) / 95}% 100%`"
|
||||
@input="toggleCapacityRetryDelayMs($event.target.value)"
|
||||
aria-label="Capacity retry delay slider">
|
||||
<input type="number" min="500" max="10000" step="500"
|
||||
class="input input-xs input-bordered w-20 bg-space-800 border-space-border text-white font-mono text-center"
|
||||
:value="serverConfig.capacityRetryDelayMs || 2000"
|
||||
@change="toggleCapacityRetryDelayMs($event.target.value)"
|
||||
aria-label="Capacity retry delay value">
|
||||
</div>
|
||||
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
|
||||
x-text="$store.global.t('capacityRetryDelayDesc')">Delay for capacity (not quota) issues.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label pt-0">
|
||||
<span class="label-text text-gray-400 text-xs"
|
||||
x-text="$store.global.t('maxCapacityRetries')">Max Capacity Retries</span>
|
||||
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
||||
x-text="serverConfig.maxCapacityRetries || 3"></span>
|
||||
</label>
|
||||
<div class="flex gap-3 items-center">
|
||||
<input type="range" min="1" max="10" step="1"
|
||||
class="custom-range custom-range-cyan flex-1"
|
||||
:value="serverConfig.maxCapacityRetries || 3"
|
||||
:style="`background-size: ${((serverConfig.maxCapacityRetries || 3) - 1) / 0.09}% 100%`"
|
||||
@input="toggleMaxCapacityRetries($event.target.value)"
|
||||
aria-label="Max capacity retries slider">
|
||||
<input type="number" min="1" max="10" step="1"
|
||||
class="input input-xs input-bordered w-16 bg-space-800 border-space-border text-white font-mono text-center"
|
||||
:value="serverConfig.maxCapacityRetries || 3"
|
||||
@change="toggleMaxCapacityRetries($event.target.value)"
|
||||
aria-label="Max capacity retries value">
|
||||
</div>
|
||||
<p class="text-[9px] text-gray-600 mt-1 leading-tight"
|
||||
x-text="$store.global.t('maxCapacityRetriesDesc')">Retries before switching accounts.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user