- Added visual queue status display showing pending and active requests - Updates in real-time (every 500ms) to show current queue state - Only visible when there are requests in queue or being processed - Helps users understand system load and request processing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
236 lines
7.3 KiB
TypeScript
236 lines
7.3 KiB
TypeScript
// Request queue and throttling manager
|
|
export interface QueuedRequest {
|
|
id: string;
|
|
type: 'transcribe' | 'translate' | 'tts';
|
|
request: () => Promise<any>;
|
|
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<string, QueuedRequest> = 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<T>(
|
|
type: 'transcribe' | 'translate' | 'tts',
|
|
request: () => Promise<T>,
|
|
priority: number = 5
|
|
): Promise<T> {
|
|
// 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<void> {
|
|
if (this.isProcessing) return;
|
|
this.isProcessing = true;
|
|
|
|
while (true) {
|
|
await this.processQueue();
|
|
await this.delay(100); // Check queue every 100ms
|
|
}
|
|
}
|
|
|
|
private async processQueue(): Promise<void> {
|
|
// 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<void> {
|
|
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;
|
|
}
|
|
}
|
|
} |