From 272a05b3fed28262005713bd9c829135095fb684 Mon Sep 17 00:00:00 2001 From: StillHammer Date: Tue, 2 Dec 2025 20:24:50 +0800 Subject: [PATCH] Suppression rate limiters inutiles et optimisation polling LLM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problèmes résolus: - Polling toutes les 3s vers /api/llm/limit (1200 req/h par user) - Rate limiters par IP bloquaient légitimement les traductions - globalLimiter (200/15min) et translationLimiter (10/min) redondants Changements: - Suppression setInterval 3s dans index.html - Mise à jour compteur LLM uniquement après traductions - Suppression globalLimiter et translationLimiter - Garde uniquement checkLLMLimit() (par API key, 20/jour) - Fix affichage: utilise data.remaining de l'API Résultat: système de quotas simple, clair et fonctionnel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ConfluentTranslator/public/index.html | 30 ++++++++++----------------- ConfluentTranslator/rateLimiter.js | 30 ++------------------------- ConfluentTranslator/server.js | 14 ++++++------- 3 files changed, 20 insertions(+), 54 deletions(-) diff --git a/ConfluentTranslator/public/index.html b/ConfluentTranslator/public/index.html index 4357336..5cca0e2 100644 --- a/ConfluentTranslator/public/index.html +++ b/ConfluentTranslator/public/index.html @@ -884,9 +884,8 @@ } else { // User with limited requests counter.style.display = 'block'; - const used = data.used || 0; const limit = data.limit || 20; - const remaining = limit - used; + const remaining = data.remaining !== undefined ? data.remaining : (limit - (data.used || 0)); text.textContent = `Requêtes LLM restantes: ${remaining}/${limit}`; } } catch (error) { @@ -953,23 +952,8 @@ } }); - // Refresh LLM limit counter every 3 seconds if logged in - let limitErrorCount = 0; - const limitInterval = setInterval(() => { - if (getApiKey()) { - updateLLMLimit().catch(err => { - limitErrorCount++; - // Stop polling after 5 consecutive errors - if (limitErrorCount >= 5) { - console.warn('Too many errors loading LLM limit, stopping auto-refresh'); - clearInterval(limitInterval); - } - }); - } else { - // Reset error count if not logged in - limitErrorCount = 0; - } - }, 3000); + // Note: LLM limit counter is updated after each translation + // No need for automatic polling every few seconds }); // Authenticated fetch wrapper with auto-logout on 401/403 @@ -1421,6 +1405,10 @@ } else { document.getElementById('layer1-content').innerHTML = `Erreur: ${data.error}`; } + + // Update LLM limit counter after translation + updateLLMLimit().catch(err => console.error('Error updating LLM limit:', err)); + } catch (error) { document.getElementById('layer1-content').innerHTML = `Erreur: ${error.message}`; } finally { @@ -1533,6 +1521,10 @@ } else { document.getElementById('cf2fr-layer1-content').innerHTML = `Erreur LLM: ${llmData.error}`; } + + // Update LLM limit counter after translation + updateLLMLimit().catch(err => console.error('Error updating LLM limit:', err)); + } catch (error) { document.getElementById('cf2fr-layer1-content').innerHTML = `Erreur: ${error.message}`; } finally { diff --git a/ConfluentTranslator/rateLimiter.js b/ConfluentTranslator/rateLimiter.js index 5dfc2f7..83861c7 100644 --- a/ConfluentTranslator/rateLimiter.js +++ b/ConfluentTranslator/rateLimiter.js @@ -1,32 +1,8 @@ const rateLimit = require('express-rate-limit'); -// Rate limiter global par IP -const globalLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 200, // max 200 requêtes par IP - standardHeaders: true, - legacyHeaders: false, - skip: (req) => { - // Skip pour les endpoints qui doivent être appelés très fréquemment - return req.path === '/api/llm/limit'; - }, - message: { error: 'Too many requests from this IP, please try again later.' } -}); - -// Rate limiter pour les traductions (plus strict) -const translationLimiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 10, // max 10 traductions par minute - standardHeaders: true, - legacyHeaders: false, - skip: (req) => { - // Skip si l'utilisateur est admin - return req.user && req.user.role === 'admin'; - }, - message: { error: 'Too many translation requests. Please wait a moment.' } -}); - // Rate limiter pour les endpoints sensibles (admin) +// Note: Pour les traductions et requêtes LLM, on utilise checkLLMLimit() dans auth.js +// qui gère les limites par API key (plus flexible et précis que les rate limiters par IP) const adminLimiter = rateLimit({ windowMs: 5 * 60 * 1000, // 5 minutes max: 50, @@ -36,7 +12,5 @@ const adminLimiter = rateLimit({ }); module.exports = { - globalLimiter, - translationLimiter, adminLimiter }; diff --git a/ConfluentTranslator/server.js b/ConfluentTranslator/server.js index 3352e5f..4e2b854 100644 --- a/ConfluentTranslator/server.js +++ b/ConfluentTranslator/server.js @@ -17,7 +17,7 @@ const { translateConfluentToFrench, translateConfluentDetailed } = require('./co // Security modules const { authenticate, requireAdmin, createToken, listTokens, disableToken, enableToken, deleteToken, getGlobalStats, trackLLMUsage, checkLLMLimit } = require('./auth'); -const { globalLimiter, translationLimiter, adminLimiter } = require('./rateLimiter'); +const { adminLimiter } = require('./rateLimiter'); const { requestLogger, getLogs, getLogStats } = require('./logger'); const app = express(); @@ -26,7 +26,7 @@ const PORT = process.env.PORT || 3000; // Middlewares app.use(express.json()); app.use(requestLogger); // Log toutes les requêtes -app.use(globalLimiter); // Rate limiting global +// Rate limiting: on utilise uniquement checkLLMLimit() par API key, pas de rate limit global par IP // Route protégée pour admin.html (AVANT express.static) // Vérifie l'auth seulement si API key présente, sinon laisse passer (le JS client vérifiera) @@ -378,7 +378,7 @@ app.post('/api/analyze/coverage', authenticate, (req, res) => { }); // Translation endpoint (NOUVEAU SYSTÈME CONTEXTUEL) -app.post('/translate', authenticate, translationLimiter, async (req, res) => { +app.post('/translate', authenticate, async (req, res) => { const { text, target, provider, model, temperature = 1.0, useLexique = true, customAnthropicKey, customOpenAIKey } = req.body; if (!text || !target || !provider || !model) { @@ -586,7 +586,7 @@ function parseTranslationResponse(response) { } // Raw translation endpoint (for debugging - returns unprocessed LLM output) - SECURED -app.post('/api/translate/raw', authenticate, translationLimiter, async (req, res) => { +app.post('/api/translate/raw', authenticate, async (req, res) => { const { text, target, provider, model, useLexique = true, customAnthropicKey, customOpenAIKey } = req.body; if (!text || !target || !provider || !model) { @@ -700,7 +700,7 @@ app.post('/api/translate/raw', authenticate, translationLimiter, async (req, res }); // Batch translation endpoint - SECURED -app.post('/api/translate/batch', authenticate, translationLimiter, async (req, res) => { +app.post('/api/translate/batch', authenticate, async (req, res) => { const { words, target = 'ancien' } = req.body; if (!words || !Array.isArray(words)) { @@ -727,7 +727,7 @@ app.post('/api/translate/batch', authenticate, translationLimiter, async (req, r }); // Confluent → French translation endpoint (traduction brute) - SECURED -app.post('/api/translate/conf2fr', authenticate, translationLimiter, (req, res) => { +app.post('/api/translate/conf2fr', authenticate, (req, res) => { const { text, variant = 'ancien', detailed = false } = req.body; if (!text) { @@ -755,7 +755,7 @@ app.post('/api/translate/conf2fr', authenticate, translationLimiter, (req, res) }); // NEW: Confluent → French with LLM refinement -app.post('/api/translate/conf2fr/llm', authenticate, translationLimiter, async (req, res) => { +app.post('/api/translate/conf2fr/llm', authenticate, async (req, res) => { const { text, variant = 'ancien', provider = 'anthropic', model = 'claude-sonnet-4-20250514', customAnthropicKey, customOpenAIKey } = req.body; if (!text) {