Add health check endpoints and automatic language detection
Health Check Features (Item 12): - Added /health endpoint for basic health monitoring - Added /health/detailed for comprehensive component status - Added /health/ready for Kubernetes readiness probes - Added /health/live for liveness checks - Frontend health monitoring with auto-recovery - Clear stuck requests after 60 seconds - Visual health warnings when service is degraded - Monitoring script for external health checks Automatic Language Detection (Item 13): - Added "Auto-detect" option in source language dropdown - Whisper automatically detects language when auto-detect is selected - Shows detected language in UI after transcription - Updates language selector with detected language - Caches transcriptions with correct detected language 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		| @@ -106,6 +106,9 @@ function initApp(): void { | ||||
|      | ||||
|     // Initialize queue status updates | ||||
|     initQueueStatus(); | ||||
|      | ||||
|     // Start health monitoring | ||||
|     startHealthMonitoring(); | ||||
|  | ||||
|     // Update TTS server URL and API key | ||||
|     updateTtsServer.addEventListener('click', function() { | ||||
| @@ -153,6 +156,11 @@ function initApp(): void { | ||||
|  | ||||
|     // Event listeners for language selection | ||||
|     sourceLanguage.addEventListener('change', function() { | ||||
|         // Skip conflict check for auto-detect | ||||
|         if (sourceLanguage.value === 'auto') { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if (targetLanguage.value === sourceLanguage.value) { | ||||
|             for (let i = 0; i < targetLanguage.options.length; i++) { | ||||
|                 if (targetLanguage.options[i].value !== sourceLanguage.value) { | ||||
| @@ -383,10 +391,24 @@ function initApp(): void { | ||||
|              | ||||
|             if (data.success && data.text) { | ||||
|                 currentSourceText = data.text; | ||||
|                 sourceText.innerHTML = `<p class="fade-in">${data.text}</p>`; | ||||
|                  | ||||
|                 // Handle auto-detected language | ||||
|                 if (data.detected_language && sourceLanguage.value === 'auto') { | ||||
|                     // Update the source language selector | ||||
|                     sourceLanguage.value = data.detected_language; | ||||
|                      | ||||
|                     // Show detected language info | ||||
|                     sourceText.innerHTML = `<p class="fade-in">${data.text}</p> | ||||
|                         <small class="text-muted">Detected language: ${data.detected_language}</small>`; | ||||
|                      | ||||
|                     statusIndicator.textContent = `Transcription complete (${data.detected_language} detected)`; | ||||
|                 } else { | ||||
|                     sourceText.innerHTML = `<p class="fade-in">${data.text}</p>`; | ||||
|                     statusIndicator.textContent = 'Transcription complete'; | ||||
|                 } | ||||
|                  | ||||
|                 playSource.disabled = false; | ||||
|                 translateBtn.disabled = false; | ||||
|                 statusIndicator.textContent = 'Transcription complete'; | ||||
|                 statusIndicator.classList.remove('processing'); | ||||
|                 statusIndicator.classList.add('success'); | ||||
|                 setTimeout(() => statusIndicator.classList.remove('success'), 2000); | ||||
| @@ -394,7 +416,7 @@ function initApp(): void { | ||||
|                 // Cache the transcription in IndexedDB | ||||
|                 saveToIndexedDB('transcriptions', { | ||||
|                     text: data.text, | ||||
|                     language: sourceLanguage.value, | ||||
|                     language: data.detected_language || sourceLanguage.value, | ||||
|                     timestamp: new Date().toISOString() | ||||
|                 } as TranscriptionRecord); | ||||
|             } else { | ||||
| @@ -753,6 +775,99 @@ function initApp(): void { | ||||
|         // Initial update | ||||
|         updateQueueDisplay(); | ||||
|     } | ||||
|      | ||||
|     // Health monitoring and auto-recovery | ||||
|     function startHealthMonitoring(): void { | ||||
|         let consecutiveFailures = 0; | ||||
|         const maxConsecutiveFailures = 3; | ||||
|          | ||||
|         async function checkHealth(): Promise<void> { | ||||
|             try { | ||||
|                 const response = await fetch('/health', {  | ||||
|                     method: 'GET', | ||||
|                     signal: AbortSignal.timeout(5000) // 5 second timeout | ||||
|                 }); | ||||
|                  | ||||
|                 if (response.ok) { | ||||
|                     consecutiveFailures = 0; | ||||
|                      | ||||
|                     // Remove any health warning if shown | ||||
|                     const healthWarning = document.getElementById('healthWarning'); | ||||
|                     if (healthWarning) { | ||||
|                         healthWarning.style.display = 'none'; | ||||
|                     } | ||||
|                 } else { | ||||
|                     handleHealthCheckFailure(); | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 handleHealthCheckFailure(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         function handleHealthCheckFailure(): void { | ||||
|             consecutiveFailures++; | ||||
|             console.warn(`Health check failed (${consecutiveFailures}/${maxConsecutiveFailures})`); | ||||
|              | ||||
|             if (consecutiveFailures >= maxConsecutiveFailures) { | ||||
|                 showHealthWarning(); | ||||
|                  | ||||
|                 // Attempt auto-recovery | ||||
|                 attemptAutoRecovery(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         function showHealthWarning(): void { | ||||
|             let healthWarning = document.getElementById('healthWarning'); | ||||
|             if (!healthWarning) { | ||||
|                 healthWarning = document.createElement('div'); | ||||
|                 healthWarning.id = 'healthWarning'; | ||||
|                 healthWarning.className = 'alert alert-warning alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3'; | ||||
|                 healthWarning.style.zIndex = '9999'; | ||||
|                 healthWarning.innerHTML = ` | ||||
|                     <i class="fas fa-exclamation-triangle"></i> Service health check failed.  | ||||
|                     Some features may be unavailable.  | ||||
|                     <button type="button" class="btn-close" data-bs-dismiss="alert"></button> | ||||
|                 `; | ||||
|                 document.body.appendChild(healthWarning); | ||||
|             } | ||||
|             healthWarning.style.display = 'block'; | ||||
|         } | ||||
|          | ||||
|         async function attemptAutoRecovery(): Promise<void> { | ||||
|             console.log('Attempting auto-recovery...'); | ||||
|              | ||||
|             // Clear any stuck requests in the queue | ||||
|             const queue = RequestQueueManager.getInstance(); | ||||
|             queue.clearStuckRequests(); | ||||
|              | ||||
|             // Re-check TTS server | ||||
|             checkTtsServer(); | ||||
|              | ||||
|             // Try to reload service worker if available | ||||
|             if ('serviceWorker' in navigator) { | ||||
|                 try { | ||||
|                     const registration = await navigator.serviceWorker.getRegistration(); | ||||
|                     if (registration) { | ||||
|                         await registration.update(); | ||||
|                         console.log('Service worker updated'); | ||||
|                     } | ||||
|                 } catch (error) { | ||||
|                     console.error('Failed to update service worker:', error); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Reset failure counter after recovery attempt | ||||
|             setTimeout(() => { | ||||
|                 consecutiveFailures = 0; | ||||
|             }, 30000); // Wait 30 seconds before resetting | ||||
|         } | ||||
|          | ||||
|         // Check health every 30 seconds | ||||
|         setInterval(checkHealth, 30000); | ||||
|          | ||||
|         // Initial health check after 5 seconds | ||||
|         setTimeout(checkHealth, 5000); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -216,6 +216,31 @@ export class RequestQueueManager { | ||||
|         }); | ||||
|         this.queue = []; | ||||
|     } | ||||
|      | ||||
|     // Clear stuck requests (requests older than 60 seconds) | ||||
|     clearStuckRequests(): void { | ||||
|         const now = Date.now(); | ||||
|         const stuckThreshold = 60000; // 60 seconds | ||||
|          | ||||
|         // Clear stuck active requests | ||||
|         this.activeRequests.forEach((request, id) => { | ||||
|             if (now - request.timestamp > stuckThreshold) { | ||||
|                 console.warn(`Clearing stuck active request: ${request.type}`); | ||||
|                 request.reject(new Error('Request timeout - cleared by recovery')); | ||||
|                 this.activeRequests.delete(id); | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         // Clear old queued requests | ||||
|         this.queue = this.queue.filter(request => { | ||||
|             if (now - request.timestamp > stuckThreshold) { | ||||
|                 console.warn(`Clearing stuck queued request: ${request.type}`); | ||||
|                 request.reject(new Error('Request timeout - cleared by recovery')); | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // Update settings | ||||
|     updateSettings(settings: { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ export interface TranscriptionResponse { | ||||
|   success: boolean; | ||||
|   text?: string; | ||||
|   error?: string; | ||||
|   detected_language?: string; | ||||
| } | ||||
|  | ||||
| export interface TranslationResponse { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user