// Request queue and throttling manager export interface QueuedRequest { id: string; type: 'transcribe' | 'translate' | 'tts'; request: () => Promise; resolve: (value: any) => void; reject: (reason?: any) => void; retryCount: number; priority: number; timestamp: number; } export class RequestQueueManager { private static instance: RequestQueueManager; private queue: QueuedRequest[] = []; private activeRequests: Map = new Map(); private maxConcurrent = 2; // Maximum concurrent requests private maxRetries = 3; private retryDelay = 1000; // Base retry delay in ms private isProcessing = false; // Rate limiting private requestHistory: number[] = []; private maxRequestsPerMinute = 30; private maxRequestsPerSecond = 2; private constructor() { // Start processing queue this.startProcessing(); } static getInstance(): RequestQueueManager { if (!RequestQueueManager.instance) { RequestQueueManager.instance = new RequestQueueManager(); } return RequestQueueManager.instance; } // Add request to queue async enqueue( type: 'transcribe' | 'translate' | 'tts', request: () => Promise, priority: number = 5 ): Promise { // Check rate limits if (!this.checkRateLimits()) { throw new Error('Rate limit exceeded. Please slow down.'); } return new Promise((resolve, reject) => { const id = this.generateId(); const queuedRequest: QueuedRequest = { id, type, request, resolve, reject, retryCount: 0, priority, timestamp: Date.now() }; // Add to queue based on priority this.addToQueue(queuedRequest); // Log queue status console.log(`Request queued: ${type}, Queue size: ${this.queue.length}, Active: ${this.activeRequests.size}`); }); } private addToQueue(request: QueuedRequest): void { // Insert based on priority (higher priority first) const insertIndex = this.queue.findIndex(item => item.priority < request.priority); if (insertIndex === -1) { this.queue.push(request); } else { this.queue.splice(insertIndex, 0, request); } } private checkRateLimits(): boolean { const now = Date.now(); // Clean old entries this.requestHistory = this.requestHistory.filter( time => now - time < 60000 // Keep last minute ); // Check per-second limit const lastSecond = this.requestHistory.filter( time => now - time < 1000 ).length; if (lastSecond >= this.maxRequestsPerSecond) { console.warn('Per-second rate limit reached'); return false; } // Check per-minute limit if (this.requestHistory.length >= this.maxRequestsPerMinute) { console.warn('Per-minute rate limit reached'); return false; } // Record this request this.requestHistory.push(now); return true; } private async startProcessing(): Promise { if (this.isProcessing) return; this.isProcessing = true; while (true) { await this.processQueue(); await this.delay(100); // Check queue every 100ms } } private async processQueue(): Promise { // Check if we can process more requests if (this.activeRequests.size >= this.maxConcurrent || this.queue.length === 0) { return; } // Get next request const request = this.queue.shift(); if (!request) return; // Mark as active this.activeRequests.set(request.id, request); try { // Execute request const result = await request.request(); request.resolve(result); console.log(`Request completed: ${request.type}`); } catch (error) { console.error(`Request failed: ${request.type}`, error); // Check if we should retry if (request.retryCount < this.maxRetries && this.shouldRetry(error)) { request.retryCount++; const delay = this.calculateRetryDelay(request.retryCount); console.log(`Retrying request ${request.type} in ${delay}ms (attempt ${request.retryCount})`); // Re-queue with delay setTimeout(() => { this.addToQueue(request); }, delay); } else { // Max retries reached or non-retryable error request.reject(error); } } finally { // Remove from active this.activeRequests.delete(request.id); } } private shouldRetry(error: any): boolean { // Retry on network errors or 5xx status codes if (error.message?.includes('network') || error.message?.includes('Network')) { return true; } if (error.status >= 500 && error.status < 600) { return true; } // Don't retry on client errors (4xx) if (error.status >= 400 && error.status < 500) { return false; } return true; } private calculateRetryDelay(retryCount: number): number { // Exponential backoff with jitter const baseDelay = this.retryDelay * Math.pow(2, retryCount - 1); const jitter = Math.random() * 0.3 * baseDelay; // 30% jitter return Math.min(baseDelay + jitter, 30000); // Max 30 seconds } private generateId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } // Get queue status getStatus(): { queueLength: number; activeRequests: number; requestsPerMinute: number; } { const now = Date.now(); const recentRequests = this.requestHistory.filter( time => now - time < 60000 ).length; return { queueLength: this.queue.length, activeRequests: this.activeRequests.size, requestsPerMinute: recentRequests }; } // Clear queue (for emergency use) clearQueue(): void { this.queue.forEach(request => { request.reject(new Error('Queue cleared')); }); this.queue = []; } // Update settings updateSettings(settings: { maxConcurrent?: number; maxRequestsPerMinute?: number; maxRequestsPerSecond?: number; }): void { if (settings.maxConcurrent !== undefined) { this.maxConcurrent = settings.maxConcurrent; } if (settings.maxRequestsPerMinute !== undefined) { this.maxRequestsPerMinute = settings.maxRequestsPerMinute; } if (settings.maxRequestsPerSecond !== undefined) { this.maxRequestsPerSecond = settings.maxRequestsPerSecond; } } }