From b5f2b53262ebff7c0414a09c28473e7f73150cf3 Mon Sep 17 00:00:00 2001 From: Adolfo Delorenzo Date: Tue, 3 Jun 2025 09:24:44 -0600 Subject: [PATCH] Housekeeping: Remove unnecessary test and temporary files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed test scripts (test_*.py, tts-debug-script.py) - Removed test output files (tts_test_output.mp3, test-cors.html) - Removed redundant static/js/app.js (using TypeScript dist/ instead) - Removed outdated setup-script.sh - Removed Python cache directory (__pycache__) - Removed Claude IDE local settings (.claude/) - Updated .gitignore with better patterns for: - Test files - Debug scripts - Claude IDE settings - Standalone compiled JS This cleanup reduces repository size and removes temporary/debug files that shouldn't be version controlled. šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 12 + setup-script.sh | 776 ---------------------- static/js/app.js | 1385 --------------------------------------- test-cors.html | 228 ------- test_error_logging.py | 168 ----- test_session_manager.py | 264 -------- test_size_limits.py | 146 ----- tts-debug-script.py | 78 --- tts_test_output.mp3 | Bin 9648 -> 0 bytes 9 files changed, 12 insertions(+), 3045 deletions(-) delete mode 100755 setup-script.sh delete mode 100644 static/js/app.js delete mode 100644 test-cors.html delete mode 100755 test_error_logging.py delete mode 100644 test_session_manager.py delete mode 100755 test_size_limits.py delete mode 100644 tts-debug-script.py delete mode 100644 tts_test_output.mp3 diff --git a/.gitignore b/.gitignore index 6a33621..3139bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,15 @@ vapid_public.pem .master_key secrets.db *.key + +# Test files +test_*.py +*_test_output.* +test-*.html +*-debug-script.py + +# Claude IDE +.claude/ + +# Standalone compiled JS (use dist/ instead) +static/js/app.js diff --git a/setup-script.sh b/setup-script.sh deleted file mode 100755 index 2331c45..0000000 --- a/setup-script.sh +++ /dev/null @@ -1,776 +0,0 @@ -#!/bin/bash - -# Create necessary directories -mkdir -p templates static/{css,js} - -# Move HTML template to templates directory -cat > templates/index.html << 'EOL' - - - - - - Voice Language Translator - - - - - -
-

Voice Language Translator

-

Powered by Gemma 3, Whisper & Edge TTS

- -
-
-
-
-
Source
-
-
- -
-

Your transcribed text will appear here...

-
-
- - -
-
-
-
- -
-
-
-
Translation
-
-
- -
-

Translation will appear here...

-
-
- - -
-
-
-
-
- -
- -

Click to start recording

-
- -
- -
- -
-
-
-
-
- - -
- - - - - -EOL - -# Create app.py -cat > app.py << 'EOL' -import os -import time -import tempfile -import requests -import json -from flask import Flask, render_template, request, jsonify, Response, send_file -import whisper -import torch -import ollama -import logging - -app = Flask(__name__) -app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp() -app.config['TTS_SERVER'] = os.environ.get('TTS_SERVER_URL', 'http://localhost:5050/v1/audio/speech') -app.config['TTS_API_KEY'] = os.environ.get('TTS_API_KEY', 'your_api_key_here') - -# Add a route to check TTS server status -@app.route('/check_tts_server', methods=['GET']) -def check_tts_server(): - try: - # Try a simple HTTP request to the TTS server - response = requests.get(app.config['TTS_SERVER'].rsplit('/api/generate', 1)[0] + '/status', timeout=5) - if response.status_code == 200: - return jsonify({ - 'status': 'online', - 'url': app.config['TTS_SERVER'] - }) - else: - return jsonify({ - 'status': 'error', - 'message': f'TTS server returned status code {response.status_code}', - 'url': app.config['TTS_SERVER'] - }) - except requests.exceptions.RequestException as e: - return jsonify({ - 'status': 'error', - 'message': f'Cannot connect to TTS server: {str(e)}', - 'url': app.config['TTS_SERVER'] - }) - -# Initialize logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Load Whisper model -logger.info("Loading Whisper model...") -whisper_model = whisper.load_model("base") -logger.info("Whisper model loaded successfully") - -# Supported languages -SUPPORTED_LANGUAGES = { - "ar": "Arabic", - "hy": "Armenian", - "az": "Azerbaijani", - "en": "English", - "fr": "French", - "ka": "Georgian", - "kk": "Kazakh", - "zh": "Mandarin", - "fa": "Farsi", - "pt": "Portuguese", - "ru": "Russian", - "es": "Spanish", - "tr": "Turkish", - "uz": "Uzbek" -} - -# Map language names to language codes -LANGUAGE_TO_CODE = {v: k for k, v in SUPPORTED_LANGUAGES.items()} - -# Map language names to OpenAI TTS voice options -LANGUAGE_TO_VOICE = { - "Arabic": "alloy", # Using OpenAI general voices - "Armenian": "echo", # as OpenAI doesn't have specific voices - "Azerbaijani": "nova", # for all these languages - "English": "echo", # We'll use the available voices - "French": "alloy", # and rely on the translation being - "Georgian": "fable", # in the correct language text - "Kazakh": "onyx", - "Mandarin": "shimmer", - "Farsi": "nova", - "Portuguese": "alloy", - "Russian": "echo", - "Spanish": "nova", - "Turkish": "fable", - "Uzbek": "onyx" -} - -@app.route('/') -def index(): - return render_template('index.html', languages=sorted(SUPPORTED_LANGUAGES.values())) - -@app.route('/transcribe', methods=['POST']) -def transcribe(): - if 'audio' not in request.files: - return jsonify({'error': 'No audio file provided'}), 400 - - audio_file = request.files['audio'] - source_lang = request.form.get('source_lang', '') - - # Save the audio file temporarily - temp_path = os.path.join(app.config['UPLOAD_FOLDER'], 'input_audio.wav') - audio_file.save(temp_path) - - try: - # Use Whisper for transcription - result = whisper_model.transcribe( - temp_path, - language=LANGUAGE_TO_CODE.get(source_lang, None) - ) - transcribed_text = result["text"] - - return jsonify({ - 'success': True, - 'text': transcribed_text - }) - except Exception as e: - logger.error(f"Transcription error: {str(e)}") - return jsonify({'error': f'Transcription failed: {str(e)}'}), 500 - finally: - # Clean up the temporary file - if os.path.exists(temp_path): - os.remove(temp_path) - -@app.route('/translate', methods=['POST']) -def translate(): - try: - data = request.json - text = data.get('text', '') - source_lang = data.get('source_lang', '') - target_lang = data.get('target_lang', '') - - if not text or not source_lang or not target_lang: - return jsonify({'error': 'Missing required parameters'}), 400 - - # Create a prompt for Gemma 3 translation - prompt = f""" - Translate the following text from {source_lang} to {target_lang}: - - "{text}" - - Provide only the translation without any additional text. - """ - - # Use Ollama to interact with Gemma 3 - response = ollama.chat( - model="gemma3", - messages=[ - { - "role": "user", - "content": prompt - } - ] - ) - - translated_text = response['message']['content'].strip() - - return jsonify({ - 'success': True, - 'translation': translated_text - }) - except Exception as e: - logger.error(f"Translation error: {str(e)}") - return jsonify({'error': f'Translation failed: {str(e)}'}), 500 - -@app.route('/speak', methods=['POST']) -def speak(): - try: - data = request.json - text = data.get('text', '') - language = data.get('language', '') - - if not text or not language: - return jsonify({'error': 'Missing required parameters'}), 400 - - voice = LANGUAGE_TO_VOICE.get(language) - if not voice: - return jsonify({'error': 'Unsupported language for TTS'}), 400 - - # Get TTS server URL from environment or config - tts_server_url = app.config['TTS_SERVER'] - - try: - # Request TTS from the Edge TTS server - logger.info(f"Sending TTS request to {tts_server_url}") - tts_response = requests.post( - tts_server_url, - json={ - 'text': text, - 'voice': voice, - 'output_format': 'mp3' - }, - timeout=10 # Add timeout - ) - - logger.info(f"TTS response status: {tts_response.status_code}") - - if tts_response.status_code != 200: - error_msg = f'TTS request failed with status {tts_response.status_code}' - logger.error(error_msg) - - # Try to get error details from response if possible - try: - error_details = tts_response.json() - logger.error(f"Error details: {error_details}") - except: - pass - - return jsonify({'error': error_msg}), 500 - - # The response contains the audio data directly - temp_audio_path = os.path.join(app.config['UPLOAD_FOLDER'], f'output_{int(time.time())}.mp3') - with open(temp_audio_path, 'wb') as f: - f.write(tts_response.content) - - return jsonify({ - 'success': True, - 'audio_url': f'/get_audio/{os.path.basename(temp_audio_path)}' - }) - except requests.exceptions.RequestException as e: - error_msg = f'Failed to connect to TTS server: {str(e)}' - logger.error(error_msg) - return jsonify({'error': error_msg}), 500 - except Exception as e: - logger.error(f"TTS error: {str(e)}") - return jsonify({'error': f'TTS failed: {str(e)}'}), 500 - -@app.route('/get_audio/') -def get_audio(filename): - try: - file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) - return send_file(file_path, mimetype='audio/mpeg') - except Exception as e: - logger.error(f"Audio retrieval error: {str(e)}") - return jsonify({'error': f'Audio retrieval failed: {str(e)}'}), 500 - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=8000, debug=True) -EOL - -# Create requirements.txt -cat > requirements.txt << 'EOL' -flask==2.3.2 -requests==2.31.0 -openai-whisper==20231117 -torch==2.1.0 -ollama==0.1.5 -EOL - -# Create README.md -cat > README.md << 'EOL' -# Voice Language Translator - -A mobile-friendly web application that translates spoken language between multiple languages using: -- Gemma 3 open-source LLM via Ollama for translation -- OpenAI Whisper for speech-to-text -- OpenAI Edge TTS for text-to-speech - -## Supported Languages - -- Arabic -- Armenian -- Azerbaijani -- English -- French -- Georgian -- Kazakh -- Mandarin -- Farsi -- Portuguese -- Russian -- Spanish -- Turkish -- Uzbek - -## Setup Instructions - -1. Install the required Python packages: - ``` - pip install -r requirements.txt - ``` - -2. Make sure you have Ollama installed and the Gemma 3 model loaded: - ``` - ollama pull gemma3 - ``` - -3. Ensure your OpenAI Edge TTS server is running on port 5050. - -4. Run the application: - ``` - python app.py - ``` - -5. Open your browser and navigate to: - ``` - http://localhost:8000 - ``` - -## Usage - -1. Select your source language from the dropdown menu -2. Press the microphone button and speak -3. Press the button again to stop recording -4. Wait for the transcription to complete -5. Select your target language -6. Press the "Translate" button -7. Use the play buttons to hear the original or translated text - -## Technical Details - -- The app uses Flask for the web server -- Audio is processed client-side using the MediaRecorder API -- Whisper for speech recognition with language hints -- Ollama provides access to the Gemma 3 model for translation -- OpenAI Edge TTS delivers natural-sounding speech output - -## Mobile Support - -The interface is fully responsive and designed to work well on mobile devices. -EOL - -# Make the script executable -chmod +x app.py - -echo "Setup complete! Run the app with: python app.py" diff --git a/static/js/app.js b/static/js/app.js deleted file mode 100644 index d866387..0000000 --- a/static/js/app.js +++ /dev/null @@ -1,1385 +0,0 @@ -import { TranslationCache } from './translationCache'; -import { RequestQueueManager } from './requestQueue'; -import { ErrorBoundary } from './errorBoundary'; -import { Validator } from './validator'; -import { StreamingTranslation } from './streamingTranslation'; -import { PerformanceMonitor } from './performanceMonitor'; -import { SpeakerManager } from './speakerManager'; -import { ConnectionManager } from './connectionManager'; -import { ConnectionUI } from './connectionUI'; -// import { apiClient } from './apiClient'; // Available for cross-origin requests -// Initialize error boundary -const errorBoundary = ErrorBoundary.getInstance(); -// Initialize connection management -ConnectionManager.getInstance(); // Initialize connection manager -const connectionUI = ConnectionUI.getInstance(); -// Configure API client if needed for cross-origin requests -// import { apiClient } from './apiClient'; -// apiClient.configure({ baseUrl: 'https://api.talk2me.com', credentials: 'include' }); -document.addEventListener('DOMContentLoaded', function () { - // Set up global error handler - errorBoundary.setGlobalErrorHandler((error, errorInfo) => { - console.error('Global error caught:', error); - // Show user-friendly message based on component - if (errorInfo.component === 'transcription') { - const statusIndicator = document.getElementById('statusIndicator'); - if (statusIndicator) { - statusIndicator.textContent = 'Transcription failed. Please try again.'; - statusIndicator.classList.add('text-danger'); - } - } - else if (errorInfo.component === 'translation') { - const translatedText = document.getElementById('translatedText'); - if (translatedText) { - translatedText.innerHTML = '

Translation failed. Please try again.

'; - } - } - }); - // Wrap initialization functions with error boundaries - const safeRegisterServiceWorker = errorBoundary.wrapAsync(registerServiceWorker, 'service-worker', async () => console.warn('Service worker registration failed, continuing without PWA features')); - const safeInitApp = errorBoundary.wrap(initApp, 'app-init', () => console.error('App initialization failed')); - const safeInitInstallPrompt = errorBoundary.wrap(initInstallPrompt, 'install-prompt', () => console.warn('Install prompt initialization failed')); - // Register service worker - if ('serviceWorker' in navigator) { - safeRegisterServiceWorker(); - } - // Initialize app - safeInitApp(); - // Check for PWA installation prompts - safeInitInstallPrompt(); -}); -// Service Worker Registration -async function registerServiceWorker() { - try { - const registration = await navigator.serviceWorker.register('/service-worker.js'); - console.log('Service Worker registered with scope:', registration.scope); - // Setup periodic sync if available - if ('periodicSync' in registration && registration.periodicSync) { - // Request permission for background sync - const status = await navigator.permissions.query({ - name: 'periodic-background-sync', - }); - if (status.state === 'granted') { - try { - // Register for background sync to check for updates - await registration.periodicSync.register('translation-updates', { - minInterval: 24 * 60 * 60 * 1000, // once per day - }); - console.log('Periodic background sync registered'); - } - catch (error) { - console.error('Periodic background sync could not be registered:', error); - } - } - } - // Setup push notification if available - if ('PushManager' in window) { - setupPushNotifications(registration); - } - } - catch (error) { - console.error('Service Worker registration failed:', error); - } -} -// Initialize the main application -function initApp() { - // DOM elements - const recordBtn = document.getElementById('recordBtn'); - const translateBtn = document.getElementById('translateBtn'); - const sourceText = document.getElementById('sourceText'); - const translatedText = document.getElementById('translatedText'); - const sourceLanguage = document.getElementById('sourceLanguage'); - const targetLanguage = document.getElementById('targetLanguage'); - const playSource = document.getElementById('playSource'); - const playTranslation = document.getElementById('playTranslation'); - const clearSource = document.getElementById('clearSource'); - const clearTranslation = document.getElementById('clearTranslation'); - const statusIndicator = document.getElementById('statusIndicator'); - const progressContainer = document.getElementById('progressContainer'); - const progressBar = document.getElementById('progressBar'); - const audioPlayer = document.getElementById('audioPlayer'); - const ttsServerAlert = document.getElementById('ttsServerAlert'); - const ttsServerMessage = document.getElementById('ttsServerMessage'); - const ttsServerUrl = document.getElementById('ttsServerUrl'); - const ttsApiKey = document.getElementById('ttsApiKey'); - const updateTtsServer = document.getElementById('updateTtsServer'); - const loadingOverlay = document.getElementById('loadingOverlay'); - const loadingText = document.getElementById('loadingText'); - // Set initial values - let isRecording = false; - let mediaRecorder = null; - let audioChunks = []; - let currentSourceText = ''; - let currentTranslationText = ''; - let currentTtsServerUrl = ''; - // Performance monitoring - const performanceMonitor = PerformanceMonitor.getInstance(); - // Speaker management - const speakerManager = SpeakerManager.getInstance(); - let multiSpeakerEnabled = false; - // Check TTS server status on page load - checkTtsServer(); - // Check for saved translations in IndexedDB - loadSavedTranslations(); - // Initialize queue status updates - initQueueStatus(); - // Start health monitoring - startHealthMonitoring(); - // Initialize multi-speaker mode - initMultiSpeakerMode(); - // Multi-speaker mode implementation - function initMultiSpeakerMode() { - const multiSpeakerToggle = document.getElementById('toggleMultiSpeaker'); - const multiSpeakerStatus = document.getElementById('multiSpeakerStatus'); - const speakerToolbar = document.getElementById('speakerToolbar'); - const conversationView = document.getElementById('conversationView'); - const multiSpeakerModeCheckbox = document.getElementById('multiSpeakerMode'); - // Load saved preference - multiSpeakerEnabled = localStorage.getItem('multiSpeakerMode') === 'true'; - if (multiSpeakerModeCheckbox) { - multiSpeakerModeCheckbox.checked = multiSpeakerEnabled; - } - // Show/hide multi-speaker UI based on setting - if (multiSpeakerEnabled) { - speakerToolbar.style.display = 'block'; - conversationView.style.display = 'block'; - multiSpeakerStatus.textContent = 'ON'; - } - // Toggle multi-speaker mode - multiSpeakerToggle?.addEventListener('click', () => { - multiSpeakerEnabled = !multiSpeakerEnabled; - multiSpeakerStatus.textContent = multiSpeakerEnabled ? 'ON' : 'OFF'; - if (multiSpeakerEnabled) { - speakerToolbar.style.display = 'block'; - conversationView.style.display = 'block'; - // Add default speaker if none exist - if (speakerManager.getAllSpeakers().length === 0) { - const defaultSpeaker = speakerManager.addSpeaker('Speaker 1', sourceLanguage.value); - speakerManager.setActiveSpeaker(defaultSpeaker.id); - updateSpeakerUI(); - } - } - else { - speakerToolbar.style.display = 'none'; - conversationView.style.display = 'none'; - } - localStorage.setItem('multiSpeakerMode', multiSpeakerEnabled.toString()); - if (multiSpeakerModeCheckbox) { - multiSpeakerModeCheckbox.checked = multiSpeakerEnabled; - } - }); - // Add speaker button - document.getElementById('addSpeakerBtn')?.addEventListener('click', () => { - const name = prompt('Enter speaker name:'); - if (name) { - const speaker = speakerManager.addSpeaker(name, sourceLanguage.value); - speakerManager.setActiveSpeaker(speaker.id); - updateSpeakerUI(); - } - }); - // Update speaker UI - function updateSpeakerUI() { - const speakerList = document.getElementById('speakerList'); - speakerList.innerHTML = ''; - speakerManager.getAllSpeakers().forEach(speaker => { - const btn = document.createElement('button'); - btn.className = `speaker-button ${speaker.isActive ? 'active' : ''}`; - btn.style.borderColor = speaker.color; - btn.style.backgroundColor = speaker.isActive ? speaker.color : 'white'; - btn.style.color = speaker.isActive ? 'white' : speaker.color; - btn.innerHTML = ` - ${speaker.avatar} - ${speaker.name} - `; - btn.addEventListener('click', () => { - speakerManager.setActiveSpeaker(speaker.id); - updateSpeakerUI(); - }); - speakerList.appendChild(btn); - }); - } - // Export conversation - document.getElementById('exportConversation')?.addEventListener('click', () => { - const text = speakerManager.exportConversation(targetLanguage.value); - const blob = new Blob([text], { type: 'text/plain' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `conversation_${new Date().toISOString()}.txt`; - a.click(); - URL.revokeObjectURL(url); - }); - // Clear conversation - document.getElementById('clearConversation')?.addEventListener('click', () => { - if (confirm('Clear all conversation history?')) { - speakerManager.clearConversation(); - updateConversationView(); - } - }); - // Update conversation view - function updateConversationView() { - const conversationContent = document.getElementById('conversationContent'); - const entries = speakerManager.getConversationInLanguage(targetLanguage.value); - conversationContent.innerHTML = entries.map(entry => ` -
-
- - ${entry.speakerName.substr(0, 2).toUpperCase()} - - ${entry.speakerName} - ${new Date(entry.timestamp).toLocaleTimeString()} -
-
- ${Validator.sanitizeHTML(entry.text)} -
-
- `).join(''); - // Scroll to bottom - conversationContent.scrollTop = conversationContent.scrollHeight; - } - // Store reference to update function for use in transcription - window.updateConversationView = updateConversationView; - window.updateSpeakerUI = updateSpeakerUI; - } - // Update TTS server URL and API key - updateTtsServer.addEventListener('click', function () { - const newUrl = ttsServerUrl.value.trim(); - const newApiKey = ttsApiKey.value.trim(); - if (!newUrl && !newApiKey) { - alert('Please provide at least one value to update'); - return; - } - const updateData = {}; - // Validate URL - if (newUrl) { - const validatedUrl = Validator.validateURL(newUrl); - if (!validatedUrl) { - alert('Invalid server URL. Please enter a valid HTTP/HTTPS URL.'); - return; - } - updateData.server_url = validatedUrl; - } - // Validate API key - if (newApiKey) { - const validatedKey = Validator.validateAPIKey(newApiKey); - if (!validatedKey) { - alert('Invalid API key format. API keys should be 20-128 characters and contain only letters, numbers, dashes, and underscores.'); - return; - } - updateData.api_key = validatedKey; - } - fetch('/update_tts_config', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(updateData) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - statusIndicator.textContent = 'TTS configuration updated'; - // Save URL to localStorage but not the API key for security - if (newUrl) - localStorage.setItem('ttsServerUrl', newUrl); - // Check TTS server with new configuration - checkTtsServer(); - } - else { - alert('Failed to update TTS configuration: ' + data.error); - } - }) - .catch(error => { - console.error('Failed to update TTS config:', error); - alert('Failed to update TTS configuration. See console for details.'); - }); - }); - // Make sure target language is different from source - if (targetLanguage.options[0].value === sourceLanguage.value) { - targetLanguage.selectedIndex = 1; - } - // 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) { - targetLanguage.selectedIndex = i; - break; - } - } - } - }); - targetLanguage.addEventListener('change', function () { - if (targetLanguage.value === sourceLanguage.value) { - for (let i = 0; i < sourceLanguage.options.length; i++) { - if (sourceLanguage.options[i].value !== targetLanguage.value) { - sourceLanguage.selectedIndex = i; - break; - } - } - } - }); - // Record button click event - recordBtn.addEventListener('click', function () { - if (isRecording) { - stopRecording(); - } - else { - startRecording(); - } - }); - // Function to start recording - function startRecording() { - // Request audio with specific constraints for better compression - const audioConstraints = { - audio: { - channelCount: 1, // Mono audio (reduces size by 50%) - sampleRate: 16000, // Lower sample rate for speech (16kHz is enough for speech) - echoCancellation: true, - noiseSuppression: true, - autoGainControl: true - } - }; - navigator.mediaDevices.getUserMedia(audioConstraints) - .then(stream => { - // Use webm/opus for better compression (if supported) - const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') - ? 'audio/webm;codecs=opus' - : 'audio/webm'; - const options = { - mimeType: mimeType, - audioBitsPerSecond: 32000 // Low bitrate for speech (32 kbps) - }; - try { - mediaRecorder = new MediaRecorder(stream, options); - } - catch (e) { - // Fallback to default if options not supported - console.warn('Compression options not supported, using defaults'); - mediaRecorder = new MediaRecorder(stream); - } - audioChunks = []; - mediaRecorder.addEventListener('dataavailable', event => { - audioChunks.push(event.data); - }); - mediaRecorder.addEventListener('stop', async () => { - // Create blob with appropriate MIME type - const mimeType = mediaRecorder?.mimeType || 'audio/webm'; - const audioBlob = new Blob(audioChunks, { type: mimeType }); - // Log compression results - const sizeInKB = (audioBlob.size / 1024).toFixed(2); - console.log(`Audio compressed to ${sizeInKB} KB (${mimeType})`); - // If the audio is still too large, we can compress it further - if (audioBlob.size > 500 * 1024) { // If larger than 500KB - statusIndicator.textContent = 'Compressing audio...'; - const compressedBlob = await compressAudioBlob(audioBlob); - transcribeAudio(compressedBlob); - } - else { - transcribeAudio(audioBlob); - } - }); - mediaRecorder.start(); - isRecording = true; - recordBtn.classList.add('recording'); - recordBtn.classList.replace('btn-primary', 'btn-danger'); - recordBtn.innerHTML = '
'; - statusIndicator.textContent = 'Recording... Click to stop'; - statusIndicator.classList.add('processing'); - }) - .catch(error => { - console.error('Error accessing microphone:', error); - alert('Error accessing microphone. Please make sure you have given permission for microphone access.'); - }); - } - // Function to stop recording - function stopRecording() { - if (!mediaRecorder) - return; - mediaRecorder.stop(); - isRecording = false; - recordBtn.classList.remove('recording'); - recordBtn.classList.replace('btn-danger', 'btn-primary'); - recordBtn.innerHTML = ''; - statusIndicator.textContent = 'Processing audio...'; - statusIndicator.classList.add('processing'); - showLoadingOverlay('Transcribing your speech...'); - // Stop all audio tracks - mediaRecorder.stream.getTracks().forEach(track => track.stop()); - } - // Function to compress audio blob if needed - async function compressAudioBlob(blob) { - return new Promise((resolve) => { - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const reader = new FileReader(); - reader.onload = async (e) => { - try { - const arrayBuffer = e.target?.result; - const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); - // Downsample to 16kHz mono - const offlineContext = new OfflineAudioContext(1, audioBuffer.duration * 16000, 16000); - const source = offlineContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(offlineContext.destination); - source.start(); - const compressedBuffer = await offlineContext.startRendering(); - // Convert to WAV format - const wavBlob = audioBufferToWav(compressedBuffer); - const compressedSizeKB = (wavBlob.size / 1024).toFixed(2); - console.log(`Further compressed to ${compressedSizeKB} KB`); - resolve(wavBlob); - } - catch (error) { - console.error('Compression failed, using original:', error); - resolve(blob); // Return original if compression fails - } - }; - reader.readAsArrayBuffer(blob); - }); - } - // Convert AudioBuffer to WAV format - function audioBufferToWav(buffer) { - const length = buffer.length * buffer.numberOfChannels * 2; - const arrayBuffer = new ArrayBuffer(44 + length); - const view = new DataView(arrayBuffer); - // WAV header - const writeString = (offset, string) => { - for (let i = 0; i < string.length; i++) { - view.setUint8(offset + i, string.charCodeAt(i)); - } - }; - writeString(0, 'RIFF'); - view.setUint32(4, 36 + length, true); - writeString(8, 'WAVE'); - writeString(12, 'fmt '); - view.setUint32(16, 16, true); - view.setUint16(20, 1, true); - view.setUint16(22, buffer.numberOfChannels, true); - view.setUint32(24, buffer.sampleRate, true); - view.setUint32(28, buffer.sampleRate * buffer.numberOfChannels * 2, true); - view.setUint16(32, buffer.numberOfChannels * 2, true); - view.setUint16(34, 16, true); - writeString(36, 'data'); - view.setUint32(40, length, true); - // Convert float samples to 16-bit PCM - let offset = 44; - for (let i = 0; i < buffer.length; i++) { - for (let channel = 0; channel < buffer.numberOfChannels; channel++) { - const sample = Math.max(-1, Math.min(1, buffer.getChannelData(channel)[i])); - view.setInt16(offset, sample * 0x7FFF, true); - offset += 2; - } - } - return new Blob([arrayBuffer], { type: 'audio/wav' }); - } - // Function to transcribe audio - const transcribeAudioBase = async function (audioBlob) { - // Validate audio file - const validation = Validator.validateAudioFile(new File([audioBlob], 'audio.webm', { type: audioBlob.type })); - if (!validation.valid) { - statusIndicator.textContent = validation.error || 'Invalid audio file'; - statusIndicator.classList.add('text-danger'); - hideProgress(); - hideLoadingOverlay(); - return; - } - // Validate language code - const validatedLang = Validator.validateLanguageCode(sourceLanguage.value, Array.from(sourceLanguage.options).map(opt => opt.value)); - if (!validatedLang && sourceLanguage.value !== 'auto') { - statusIndicator.textContent = 'Invalid source language selected'; - statusIndicator.classList.add('text-danger'); - hideProgress(); - hideLoadingOverlay(); - return; - } - const formData = new FormData(); - formData.append('audio', audioBlob, Validator.sanitizeFilename('audio.webm')); - formData.append('source_lang', validatedLang || 'auto'); - // Log upload size - const sizeInKB = (audioBlob.size / 1024).toFixed(2); - console.log(`Uploading ${sizeInKB} KB of audio data`); - showProgress(); - try { - // Use request queue for throttling - const queue = RequestQueueManager.getInstance(); - const data = await queue.enqueue('transcribe', async () => { - const response = await fetch('/transcribe', { - method: 'POST', - body: formData - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }, 8 // Higher priority for transcription - ); - hideProgress(); - if (data.success && data.text) { - // Sanitize the transcribed text - const sanitizedText = Validator.sanitizeText(data.text); - currentSourceText = sanitizedText; - // Handle multi-speaker mode - if (multiSpeakerEnabled) { - const activeSpeaker = speakerManager.getActiveSpeaker(); - if (activeSpeaker) { - const entry = speakerManager.addConversationEntry(activeSpeaker.id, sanitizedText, data.detected_language || sourceLanguage.value); - // Auto-translate for all other speakers' languages - const allLanguages = new Set(speakerManager.getAllSpeakers().map(s => s.language)); - allLanguages.delete(entry.originalLanguage); - allLanguages.forEach(async (lang) => { - try { - const response = await fetch('/translate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - text: sanitizedText, - source_lang: entry.originalLanguage, - target_lang: lang - }) - }); - if (response.ok) { - const result = await response.json(); - if (result.success && result.translation) { - speakerManager.addTranslation(entry.id, lang, result.translation); - if (window.updateConversationView) { - window.updateConversationView(); - } - } - } - } - catch (error) { - console.error(`Failed to translate to ${lang}:`, error); - } - }); - // Update conversation view - if (window.updateConversationView) { - window.updateConversationView(); - } - } - } - // 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 with sanitized HTML - sourceText.innerHTML = `

${Validator.sanitizeHTML(sanitizedText)}

- Detected language: ${Validator.sanitizeHTML(data.detected_language)}`; - statusIndicator.textContent = `Transcription complete (${data.detected_language} detected)`; - } - else { - sourceText.innerHTML = `

${Validator.sanitizeHTML(sanitizedText)}

`; - statusIndicator.textContent = 'Transcription complete'; - } - playSource.disabled = false; - translateBtn.disabled = false; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('success'); - setTimeout(() => statusIndicator.classList.remove('success'), 2000); - // Cache the transcription in IndexedDB - saveToIndexedDB('transcriptions', { - text: data.text, - language: data.detected_language || sourceLanguage.value, - timestamp: new Date().toISOString() - }); - } - else { - sourceText.innerHTML = `

Error: ${data.error}

`; - statusIndicator.textContent = 'Transcription failed'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('error'); - setTimeout(() => statusIndicator.classList.remove('error'), 2000); - } - } - catch (error) { - hideProgress(); - console.error('Transcription error:', error); - if (error.message?.includes('Rate limit')) { - sourceText.innerHTML = `

Too many requests. Please wait a moment.

`; - statusIndicator.textContent = 'Rate limit - please wait'; - } - else if (error.message?.includes('connection') || error.message?.includes('network')) { - sourceText.innerHTML = `

Connection error. Your request will be processed when connection is restored.

`; - statusIndicator.textContent = 'Connection error - queued'; - connectionUI.showTemporaryMessage('Request queued for when connection returns', 'warning'); - } - else if (!navigator.onLine) { - sourceText.innerHTML = `

You're offline. Request will be sent when connection is restored.

`; - statusIndicator.textContent = 'Offline - request queued'; - } - else { - sourceText.innerHTML = `

Failed to transcribe audio. Please try again.

`; - statusIndicator.textContent = 'Transcription failed'; - } - } - }; - // Wrap transcribe function with error boundary - const transcribeAudio = errorBoundary.wrapAsync(transcribeAudioBase, 'transcription', async () => { - hideProgress(); - hideLoadingOverlay(); - sourceText.innerHTML = '

Transcription failed. Please try again.

'; - statusIndicator.textContent = 'Transcription error - please retry'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('error'); - }); - // Translate button click event - translateBtn.addEventListener('click', errorBoundary.wrapAsync(async function () { - if (!currentSourceText) { - return; - } - // Check if streaming is enabled - const streamingEnabled = localStorage.getItem('streamingTranslation') !== 'false'; - // Check if offline mode is enabled - const offlineModeEnabled = localStorage.getItem('offlineMode') !== 'false'; - if (offlineModeEnabled) { - statusIndicator.textContent = 'Checking cache...'; - statusIndicator.classList.add('processing'); - // Check cache first - const cachedTranslation = await TranslationCache.getCachedTranslation(currentSourceText, sourceLanguage.value, targetLanguage.value); - if (cachedTranslation) { - // Use cached translation - console.log('Using cached translation'); - currentTranslationText = cachedTranslation; - translatedText.innerHTML = `

${cachedTranslation} (cached)

`; - playTranslation.disabled = false; - statusIndicator.textContent = 'Translation complete (from cache)'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('success'); - setTimeout(() => statusIndicator.classList.remove('success'), 2000); - return; - } - } - // No cache hit, proceed with API call - statusIndicator.textContent = 'Translating...'; - // Use streaming if enabled - if (streamingEnabled && navigator.onLine) { - // Clear previous translation - translatedText.innerHTML = '

'; - const streamingTextElement = translatedText.querySelector('.streaming-text'); - let accumulatedText = ''; - // Show minimal loading indicator for streaming - statusIndicator.classList.add('processing'); - const streamingTranslation = new StreamingTranslation( - // onChunk - append text as it arrives - (chunk) => { - accumulatedText += chunk; - streamingTextElement.textContent = accumulatedText; - streamingTextElement.classList.add('streaming-active'); - }, - // onComplete - finalize the translation - async (fullText) => { - const sanitizedTranslation = Validator.sanitizeText(fullText); - currentTranslationText = sanitizedTranslation; - streamingTextElement.textContent = sanitizedTranslation; - streamingTextElement.classList.remove('streaming-active'); - playTranslation.disabled = false; - statusIndicator.textContent = 'Translation complete'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('success'); - setTimeout(() => statusIndicator.classList.remove('success'), 2000); - // Cache the translation - if (offlineModeEnabled) { - await TranslationCache.cacheTranslation(currentSourceText, sourceLanguage.value, sanitizedTranslation, targetLanguage.value); - } - // Save to history - saveToIndexedDB('translations', { - sourceText: currentSourceText, - sourceLanguage: sourceLanguage.value, - targetText: sanitizedTranslation, - targetLanguage: targetLanguage.value, - timestamp: new Date().toISOString() - }); - }, - // onError - handle streaming errors - (error) => { - translatedText.innerHTML = `

Error: ${Validator.sanitizeHTML(error)}

`; - statusIndicator.textContent = 'Translation failed'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('error'); - }, - // onStart - () => { - console.log('Starting streaming translation'); - }); - try { - await streamingTranslation.startStreaming(currentSourceText, sourceLanguage.value, targetLanguage.value, true // use streaming - ); - } - catch (error) { - console.error('Streaming translation failed:', error); - // Fall back to regular translation is handled internally - } - return; // Exit early for streaming - } - // Regular non-streaming translation - showProgress(); - showLoadingOverlay('Translating to ' + targetLanguage.value + '...'); - // Validate input text size - if (!Validator.validateRequestSize({ text: currentSourceText }, 100)) { - translatedText.innerHTML = '

Text is too long to translate. Please shorten it.

'; - statusIndicator.textContent = 'Text too long'; - hideProgress(); - hideLoadingOverlay(); - return; - } - // Validate language codes - const validatedSourceLang = Validator.validateLanguageCode(sourceLanguage.value, Array.from(sourceLanguage.options).map(opt => opt.value)); - const validatedTargetLang = Validator.validateLanguageCode(targetLanguage.value, Array.from(targetLanguage.options).map(opt => opt.value)); - if (!validatedTargetLang) { - translatedText.innerHTML = '

Invalid target language selected

'; - statusIndicator.textContent = 'Invalid language'; - hideProgress(); - hideLoadingOverlay(); - return; - } - const requestBody = { - text: Validator.sanitizeText(currentSourceText), - source_lang: validatedSourceLang || 'auto', - target_lang: validatedTargetLang - }; - try { - // Start performance timing for regular translation - performanceMonitor.startTimer('regular_translation'); - // Use request queue for throttling - const queue = RequestQueueManager.getInstance(); - const data = await queue.enqueue('translate', async () => { - const response = await fetch('/translate', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(requestBody) - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }, 5 // Normal priority for translation - ); - hideProgress(); - if (data.success && data.translation) { - // End performance timing - performanceMonitor.endTimer('regular_translation'); - // Sanitize the translated text - const sanitizedTranslation = Validator.sanitizeText(data.translation); - currentTranslationText = sanitizedTranslation; - translatedText.innerHTML = `

${Validator.sanitizeHTML(sanitizedTranslation)}

`; - playTranslation.disabled = false; - statusIndicator.textContent = 'Translation complete'; - statusIndicator.classList.remove('processing'); - statusIndicator.classList.add('success'); - setTimeout(() => statusIndicator.classList.remove('success'), 2000); - // Cache the translation for offline use if enabled - if (offlineModeEnabled) { - await TranslationCache.cacheTranslation(currentSourceText, sourceLanguage.value, data.translation, targetLanguage.value); - } - // Also save to regular history - saveToIndexedDB('translations', { - sourceText: currentSourceText, - sourceLanguage: sourceLanguage.value, - targetText: data.translation, - targetLanguage: targetLanguage.value, - timestamp: new Date().toISOString() - }); - } - else { - translatedText.innerHTML = `

Error: ${data.error}

`; - statusIndicator.textContent = 'Translation failed'; - } - } - catch (error) { - hideProgress(); - console.error('Translation error:', error); - if (error.message?.includes('Rate limit')) { - translatedText.innerHTML = `

Too many requests. Please wait a moment.

`; - statusIndicator.textContent = 'Rate limit - please wait'; - } - else if (error.message?.includes('connection') || error.message?.includes('network')) { - translatedText.innerHTML = `

Connection error. Your translation will be processed when connection is restored.

`; - statusIndicator.textContent = 'Connection error - queued'; - connectionUI.showTemporaryMessage('Translation queued for when connection returns', 'warning'); - } - else if (!navigator.onLine) { - statusIndicator.textContent = 'Offline - checking cache...'; - translatedText.innerHTML = `

You're offline. Only cached translations are available.

`; - } - else { - translatedText.innerHTML = `

Failed to translate. Please try again.

`; - statusIndicator.textContent = 'Translation failed'; - } - } - }, 'translation', async () => { - hideProgress(); - hideLoadingOverlay(); - translatedText.innerHTML = '

Translation failed. Please try again.

'; - statusIndicator.textContent = 'Translation error - please retry'; - })); - // Play source text - playSource.addEventListener('click', function () { - if (!currentSourceText) - return; - playAudio(currentSourceText, sourceLanguage.value); - statusIndicator.textContent = 'Playing source audio...'; - }); - // Play translation - playTranslation.addEventListener('click', function () { - if (!currentTranslationText) - return; - playAudio(currentTranslationText, targetLanguage.value); - statusIndicator.textContent = 'Playing translation audio...'; - }); - // Function to play audio via TTS - const playAudioBase = async function (text, language) { - showProgress(); - showLoadingOverlay('Generating audio...'); - const requestBody = { - text: text, - language: language - }; - try { - // Use request queue for throttling - const queue = RequestQueueManager.getInstance(); - const data = await queue.enqueue('tts', async () => { - const response = await fetch('/speak', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(requestBody) - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }, 3 // Lower priority for TTS - ); - hideProgress(); - if (data.success && data.audio_url) { - audioPlayer.src = data.audio_url; - audioPlayer.onloadeddata = function () { - hideLoadingOverlay(); - // Show audio playing animation - const playingAnimation = '
'; - statusIndicator.innerHTML = playingAnimation + ' Playing audio...'; - }; - audioPlayer.onended = function () { - statusIndicator.innerHTML = ''; - statusIndicator.textContent = 'Ready'; - statusIndicator.classList.remove('processing'); - }; - audioPlayer.play(); - } - else { - statusIndicator.textContent = 'TTS failed'; - // Show TTS server alert with error message - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-success'); - ttsServerAlert.classList.add('alert-warning'); - ttsServerMessage.textContent = data.error || 'TTS failed'; - alert('Failed to play audio: ' + data.error); - // Check TTS server status again - checkTtsServer(); - } - } - catch (error) { - hideProgress(); - console.error('TTS error:', error); - if (error.message?.includes('Rate limit')) { - statusIndicator.textContent = 'Too many requests - please wait'; - alert('Too many requests. Please wait a moment before trying again.'); - } - else if (error.message?.includes('connection') || error.message?.includes('network')) { - statusIndicator.textContent = 'Connection error - audio generation queued'; - connectionUI.showTemporaryMessage('Audio generation queued for when connection returns', 'warning'); - // Show TTS server alert - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-success'); - ttsServerAlert.classList.add('alert-warning'); - ttsServerMessage.textContent = 'Connection error - request queued'; - } - else if (!navigator.onLine) { - statusIndicator.textContent = 'Offline - audio generation unavailable'; - alert('Audio generation requires an internet connection.'); - } - else { - statusIndicator.textContent = 'TTS failed'; - // Show TTS server alert - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-success'); - ttsServerAlert.classList.add('alert-warning'); - ttsServerMessage.textContent = 'Failed to connect to TTS server'; - } - } - }; - // Wrap playAudio function with error boundary - const playAudio = errorBoundary.wrapAsync(playAudioBase, 'tts', async () => { - hideProgress(); - hideLoadingOverlay(); - statusIndicator.textContent = 'Audio playback failed'; - alert('Failed to generate audio. Please check your TTS server connection.'); - }); - // Clear buttons - clearSource.addEventListener('click', function () { - sourceText.innerHTML = '

Your transcribed text will appear here...

'; - currentSourceText = ''; - playSource.disabled = true; - translateBtn.disabled = true; - }); - clearTranslation.addEventListener('click', function () { - translatedText.innerHTML = '

Translation will appear here...

'; - currentTranslationText = ''; - playTranslation.disabled = true; - }); - // Function to check TTS server status - function checkTtsServer() { - fetch('/check_tts_server') - .then(response => response.json()) - .then(data => { - currentTtsServerUrl = data.url; - ttsServerUrl.value = currentTtsServerUrl; - // Load saved API key if available - const savedApiKey = localStorage.getItem('ttsApiKeySet'); - if (savedApiKey === 'true') { - ttsApiKey.placeholder = '••••••• (API key saved)'; - } - if (data.status === 'error' || data.status === 'auth_error') { - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-success'); - ttsServerAlert.classList.add('alert-warning'); - ttsServerMessage.textContent = data.message; - if (data.status === 'auth_error') { - ttsServerMessage.textContent = 'Authentication error with TTS server. Please check your API key.'; - } - } - else { - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-warning'); - ttsServerAlert.classList.add('alert-success'); - ttsServerMessage.textContent = 'TTS server is online and ready.'; - setTimeout(() => { - ttsServerAlert.classList.add('d-none'); - }, 3000); - } - }) - .catch(error => { - console.error('Failed to check TTS server:', error); - ttsServerAlert.classList.remove('d-none'); - ttsServerAlert.classList.remove('alert-success'); - ttsServerAlert.classList.add('alert-warning'); - ttsServerMessage.textContent = 'Failed to check TTS server status.'; - }); - } - // Progress indicator functions - function showProgress() { - progressContainer.classList.remove('d-none'); - let progress = 0; - const interval = setInterval(() => { - progress += 5; - if (progress > 90) { - clearInterval(interval); - } - progressBar.style.width = `${progress}%`; - }, 100); - progressBar.dataset.interval = interval.toString(); - } - function hideProgress() { - const interval = progressBar.dataset.interval; - if (interval) { - clearInterval(Number(interval)); - } - progressBar.style.width = '100%'; - setTimeout(() => { - progressContainer.classList.add('d-none'); - progressBar.style.width = '0%'; - }, 500); - hideLoadingOverlay(); - } - function showLoadingOverlay(text) { - loadingText.textContent = text; - loadingOverlay.classList.add('active'); - } - function hideLoadingOverlay() { - loadingOverlay.classList.remove('active'); - } - // Initialize queue status display - function initQueueStatus() { - const queueStatus = document.getElementById('queueStatus'); - const queueLength = document.getElementById('queueLength'); - const activeRequests = document.getElementById('activeRequests'); - const queue = RequestQueueManager.getInstance(); - // Update queue status display - function updateQueueDisplay() { - const status = queue.getStatus(); - if (status.queueLength > 0 || status.activeRequests > 0) { - queueStatus.style.display = 'block'; - queueLength.textContent = status.queueLength.toString(); - activeRequests.textContent = status.activeRequests.toString(); - } - else { - queueStatus.style.display = 'none'; - } - } - // Poll for status updates - setInterval(updateQueueDisplay, 500); - // Initial update - updateQueueDisplay(); - } - // Health monitoring and auto-recovery - function startHealthMonitoring() { - let consecutiveFailures = 0; - const maxConsecutiveFailures = 3; - async function checkHealth() { - 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() { - consecutiveFailures++; - console.warn(`Health check failed (${consecutiveFailures}/${maxConsecutiveFailures})`); - if (consecutiveFailures >= maxConsecutiveFailures) { - showHealthWarning(); - // Attempt auto-recovery - attemptAutoRecovery(); - } - } - function showHealthWarning() { - 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 = ` - Service health check failed. - Some features may be unavailable. - - `; - document.body.appendChild(healthWarning); - } - healthWarning.style.display = 'block'; - } - async function attemptAutoRecovery() { - 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); - } -} -// IndexedDB functions for offline data storage -function openIndexedDB() { - return new Promise((resolve, reject) => { - const request = indexedDB.open('VoiceTranslatorDB', 1); - request.onupgradeneeded = (event) => { - const db = event.target.result; - // Create stores for transcriptions and translations - if (!db.objectStoreNames.contains('transcriptions')) { - db.createObjectStore('transcriptions', { keyPath: 'timestamp' }); - } - if (!db.objectStoreNames.contains('translations')) { - db.createObjectStore('translations', { keyPath: 'timestamp' }); - } - }; - request.onsuccess = (event) => { - resolve(event.target.result); - }; - request.onerror = (event) => { - reject('IndexedDB error: ' + event.target.error); - }; - }); -} -function saveToIndexedDB(storeName, data) { - openIndexedDB().then(db => { - const transaction = db.transaction([storeName], 'readwrite'); - const store = transaction.objectStore(storeName); - store.add(data); - }).catch(error => { - console.error('Error saving to IndexedDB:', error); - }); -} -function loadSavedTranslations() { - openIndexedDB().then(db => { - const transaction = db.transaction(['translations'], 'readonly'); - const store = transaction.objectStore('translations'); - const request = store.getAll(); - request.onsuccess = (event) => { - const translations = event.target.result; - if (translations && translations.length > 0) { - // Could add a history section or recently used translations - console.log('Loaded saved translations:', translations.length); - } - }; - }).catch(error => { - console.error('Error loading from IndexedDB:', error); - }); -} -// PWA installation prompt -function initInstallPrompt() { - let deferredPrompt = null; - const installButton = document.createElement('button'); - installButton.style.display = 'none'; - installButton.classList.add('btn', 'btn-success', 'fixed-bottom', 'm-3'); - installButton.innerHTML = 'Install Voice Translator '; - document.body.appendChild(installButton); - window.addEventListener('beforeinstallprompt', (e) => { - // Prevent Chrome 67 and earlier from automatically showing the prompt - e.preventDefault(); - // Stash the event so it can be triggered later - deferredPrompt = e; - // Update UI to notify the user they can add to home screen - installButton.style.display = 'block'; - installButton.addEventListener('click', () => { - // Hide our user interface that shows our install button - installButton.style.display = 'none'; - // Show the prompt - if (deferredPrompt) { - deferredPrompt.prompt(); - // Wait for the user to respond to the prompt - deferredPrompt.userChoice.then((choiceResult) => { - if (choiceResult.outcome === 'accepted') { - console.log('User accepted the install prompt'); - } - else { - console.log('User dismissed the install prompt'); - } - deferredPrompt = null; - }); - } - }); - }); -} -// Push notification setup -function setupPushNotifications(swRegistration) { - // Initialize notification UI - initNotificationUI(swRegistration); - // Check saved preference - const notificationsEnabled = localStorage.getItem('notificationsEnabled'); - if (notificationsEnabled === 'true' && Notification.permission === 'granted') { - subscribeToPushManager(swRegistration); - } -} -function initNotificationUI(swRegistration) { - const notificationPrompt = document.getElementById('notificationPrompt'); - const enableNotificationsBtn = document.getElementById('enableNotifications'); - const notificationToggle = document.getElementById('notificationToggle'); - const saveSettingsBtn = document.getElementById('saveSettings'); - // Check if we should show the prompt - const notificationsDismissed = localStorage.getItem('notificationsDismissed'); - const notificationsEnabled = localStorage.getItem('notificationsEnabled'); - if (!notificationsDismissed && !notificationsEnabled && Notification.permission === 'default') { - // Show toast after 5 seconds - setTimeout(() => { - const toast = new window.bootstrap.Toast(notificationPrompt); - toast.show(); - }, 5000); - } - // Update toggle state - notificationToggle.checked = notificationsEnabled === 'true'; - // Enable notifications button - enableNotificationsBtn?.addEventListener('click', async () => { - const permission = await Notification.requestPermission(); - if (permission === 'granted') { - localStorage.setItem('notificationsEnabled', 'true'); - notificationToggle.checked = true; - await subscribeToPushManager(swRegistration); - const toast = new window.bootstrap.Toast(notificationPrompt); - toast.hide(); - // Simple alert for mobile compatibility - setTimeout(() => { - alert('Notifications enabled successfully!'); - }, 100); - } - }); - // Notification toggle - notificationToggle?.addEventListener('change', async () => { - if (notificationToggle.checked) { - if (Notification.permission === 'default') { - const permission = await Notification.requestPermission(); - if (permission !== 'granted') { - notificationToggle.checked = false; - return; - } - } - localStorage.setItem('notificationsEnabled', 'true'); - await subscribeToPushManager(swRegistration); - } - else { - localStorage.setItem('notificationsEnabled', 'false'); - await unsubscribeFromPushManager(swRegistration); - } - }); - // Save settings - saveSettingsBtn?.addEventListener('click', () => { - const notifyTranscription = document.getElementById('notifyTranscription').checked; - const notifyTranslation = document.getElementById('notifyTranslation').checked; - const notifyErrors = document.getElementById('notifyErrors').checked; - const streamingTranslation = document.getElementById('streamingTranslation').checked; - const multiSpeakerMode = document.getElementById('multiSpeakerMode').checked; - localStorage.setItem('notifyTranscription', notifyTranscription.toString()); - localStorage.setItem('notifyTranslation', notifyTranslation.toString()); - localStorage.setItem('notifyErrors', notifyErrors.toString()); - localStorage.setItem('streamingTranslation', streamingTranslation.toString()); - localStorage.setItem('multiSpeakerMode', multiSpeakerMode.toString()); - // Update multi-speaker mode if changed - const previousMultiSpeakerMode = localStorage.getItem('multiSpeakerMode') === 'true'; - if (multiSpeakerMode !== previousMultiSpeakerMode) { - window.location.reload(); // Reload to apply changes - } - // Show inline success message - const saveStatus = document.getElementById('settingsSaveStatus'); - if (saveStatus) { - saveStatus.style.display = 'block'; - // Hide after 2 seconds and close modal - setTimeout(() => { - saveStatus.style.display = 'none'; - const modal = window.bootstrap.Modal.getInstance(document.getElementById('settingsModal')); - modal.hide(); - }, 1500); - } - }); - // Load saved preferences - const notifyTranscription = document.getElementById('notifyTranscription'); - const notifyTranslation = document.getElementById('notifyTranslation'); - const notifyErrors = document.getElementById('notifyErrors'); - const streamingTranslation = document.getElementById('streamingTranslation'); - notifyTranscription.checked = localStorage.getItem('notifyTranscription') !== 'false'; - notifyTranslation.checked = localStorage.getItem('notifyTranslation') !== 'false'; - notifyErrors.checked = localStorage.getItem('notifyErrors') === 'true'; - streamingTranslation.checked = localStorage.getItem('streamingTranslation') !== 'false'; - // Initialize cache management UI - initCacheManagement(); -} -async function initCacheManagement() { - const cacheCount = document.getElementById('cacheCount'); - const cacheSize = document.getElementById('cacheSize'); - const offlineMode = document.getElementById('offlineMode'); - const clearCacheBtn = document.getElementById('clearCache'); - // Load cache stats - async function updateCacheStats() { - const stats = await TranslationCache.getCacheStats(); - cacheCount.textContent = stats.totalEntries.toString(); - cacheSize.textContent = `${(stats.totalSize / 1024).toFixed(1)} KB`; - } - // Initial load - updateCacheStats(); - // Load offline mode preference - offlineMode.checked = localStorage.getItem('offlineMode') !== 'false'; - // Toggle offline mode - offlineMode?.addEventListener('change', () => { - localStorage.setItem('offlineMode', offlineMode.checked.toString()); - }); - // Clear cache button - clearCacheBtn?.addEventListener('click', async () => { - if (confirm('Are you sure you want to clear all cached translations?')) { - await TranslationCache.clearCache(); - await updateCacheStats(); - alert('Translation cache cleared successfully!'); - } - }); - // Update stats when modal is shown - const settingsModal = document.getElementById('settingsModal'); - settingsModal?.addEventListener('show.bs.modal', updateCacheStats); -} -async function subscribeToPushManager(swRegistration) { - try { - // Get the server's public key - const response = await fetch('/api/push-public-key'); - const data = await response.json(); - // Convert the base64 string to Uint8Array - function urlBase64ToUint8Array(base64String) { - const padding = '='.repeat((4 - base64String.length % 4) % 4); - const base64 = (base64String + padding) - .replace(/-/g, '+') - .replace(/_/g, '/'); - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; - } - const convertedVapidKey = urlBase64ToUint8Array(data.publicKey); - // Subscribe to push notifications - const subscription = await swRegistration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: convertedVapidKey - }); - // Send the subscription details to the server - await fetch('/api/push-subscribe', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(subscription) - }); - console.log('User is subscribed to push notifications'); - } - catch (error) { - console.error('Failed to subscribe to push notifications:', error); - } -} -async function unsubscribeFromPushManager(swRegistration) { - try { - const subscription = await swRegistration.pushManager.getSubscription(); - if (subscription) { - // Unsubscribe from server - await fetch('/api/push-unsubscribe', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(subscription) - }); - // Unsubscribe locally - await subscription.unsubscribe(); - console.log('User is unsubscribed from push notifications'); - } - } - catch (error) { - console.error('Failed to unsubscribe from push notifications:', error); - } -} -//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/test-cors.html b/test-cors.html deleted file mode 100644 index 9be8aa7..0000000 --- a/test-cors.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - CORS Test for Talk2Me - - - -

CORS Test for Talk2Me API

- -

This page tests CORS configuration for the Talk2Me API. Open this file from a different origin (e.g., file:// or a different port) to test cross-origin requests.

- -
- - -
- -

Tests:

- - - - - - -
- - - - \ No newline at end of file diff --git a/test_error_logging.py b/test_error_logging.py deleted file mode 100755 index 0149a19..0000000 --- a/test_error_logging.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for error logging system -""" -import logging -import json -import os -import time -from error_logger import ErrorLogger, log_errors, log_performance, get_logger - -def test_basic_logging(): - """Test basic logging functionality""" - print("\n=== Testing Basic Logging ===") - - # Get logger - logger = get_logger('test') - - # Test different log levels - logger.debug("This is a debug message") - logger.info("This is an info message") - logger.warning("This is a warning message") - logger.error("This is an error message") - - print("āœ“ Basic logging test completed") - -def test_error_logging(): - """Test error logging with exceptions""" - print("\n=== Testing Error Logging ===") - - @log_errors('test.functions') - def failing_function(): - raise ValueError("This is a test error") - - try: - failing_function() - except ValueError: - print("āœ“ Error was logged") - - # Check if error log exists - if os.path.exists('logs/errors.log'): - print("āœ“ Error log file created") - - # Read last line - with open('logs/errors.log', 'r') as f: - lines = f.readlines() - if lines: - try: - error_entry = json.loads(lines[-1]) - print(f"āœ“ Error logged with level: {error_entry.get('level')}") - print(f"āœ“ Error type: {error_entry.get('exception', {}).get('type')}") - except json.JSONDecodeError: - print("āœ— Error log entry is not valid JSON") - else: - print("āœ— Error log file not created") - -def test_performance_logging(): - """Test performance logging""" - print("\n=== Testing Performance Logging ===") - - @log_performance('test_operation') - def slow_function(): - time.sleep(0.1) # Simulate slow operation - return "result" - - result = slow_function() - print(f"āœ“ Function returned: {result}") - - # Check performance log - if os.path.exists('logs/performance.log'): - print("āœ“ Performance log file created") - - # Read last line - with open('logs/performance.log', 'r') as f: - lines = f.readlines() - if lines: - try: - perf_entry = json.loads(lines[-1]) - duration = perf_entry.get('extra_fields', {}).get('duration_ms', 0) - print(f"āœ“ Performance logged with duration: {duration}ms") - except json.JSONDecodeError: - print("āœ— Performance log entry is not valid JSON") - else: - print("āœ— Performance log file not created") - -def test_structured_logging(): - """Test structured logging format""" - print("\n=== Testing Structured Logging ===") - - logger = get_logger('test.structured') - - # Log with extra fields - logger.info("Structured log test", extra={ - 'extra_fields': { - 'user_id': 123, - 'action': 'test_action', - 'metadata': {'key': 'value'} - } - }) - - # Check main log - if os.path.exists('logs/talk2me.log'): - with open('logs/talk2me.log', 'r') as f: - lines = f.readlines() - if lines: - try: - # Find our test entry - for line in reversed(lines): - entry = json.loads(line) - if entry.get('message') == 'Structured log test': - print("āœ“ Structured log entry found") - print(f"āœ“ Contains timestamp: {'timestamp' in entry}") - print(f"āœ“ Contains hostname: {'hostname' in entry}") - print(f"āœ“ Contains extra fields: {'user_id' in entry}") - break - except json.JSONDecodeError: - print("āœ— Log entry is not valid JSON") - -def test_log_rotation(): - """Test log rotation settings""" - print("\n=== Testing Log Rotation ===") - - # Check if log files exist and their sizes - log_files = { - 'talk2me.log': 'logs/talk2me.log', - 'errors.log': 'logs/errors.log', - 'access.log': 'logs/access.log', - 'security.log': 'logs/security.log', - 'performance.log': 'logs/performance.log' - } - - for name, path in log_files.items(): - if os.path.exists(path): - size = os.path.getsize(path) - print(f"āœ“ {name}: {size} bytes") - else: - print(f"- {name}: not created yet") - -def main(): - """Run all tests""" - print("Error Logging System Tests") - print("==========================") - - # Create a test Flask app - from flask import Flask - app = Flask(__name__) - app.config['LOG_LEVEL'] = 'DEBUG' - app.config['FLASK_ENV'] = 'testing' - - # Initialize error logger - error_logger = ErrorLogger(app) - - # Run tests - test_basic_logging() - test_error_logging() - test_performance_logging() - test_structured_logging() - test_log_rotation() - - print("\nāœ… All tests completed!") - print("\nCheck the logs directory for generated log files:") - print("- logs/talk2me.log - Main application log") - print("- logs/errors.log - Error log with stack traces") - print("- logs/performance.log - Performance metrics") - print("- logs/access.log - HTTP access log") - print("- logs/security.log - Security events") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test_session_manager.py b/test_session_manager.py deleted file mode 100644 index db86484..0000000 --- a/test_session_manager.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for session management system -""" -import unittest -import tempfile -import shutil -import time -import os -from session_manager import SessionManager, UserSession, SessionResource -from flask import Flask, g, session - -class TestSessionManager(unittest.TestCase): - def setUp(self): - """Set up test fixtures""" - self.temp_dir = tempfile.mkdtemp() - self.config = { - 'max_session_duration': 3600, - 'max_idle_time': 900, - 'max_resources_per_session': 5, # Small limit for testing - 'max_bytes_per_session': 1024 * 1024, # 1MB for testing - 'cleanup_interval': 1, # 1 second for faster testing - 'session_storage_path': self.temp_dir - } - self.manager = SessionManager(self.config) - - def tearDown(self): - """Clean up test fixtures""" - shutil.rmtree(self.temp_dir, ignore_errors=True) - - def test_create_session(self): - """Test session creation""" - session = self.manager.create_session( - session_id='test-123', - user_id='user-1', - ip_address='127.0.0.1', - user_agent='Test Agent' - ) - - self.assertEqual(session.session_id, 'test-123') - self.assertEqual(session.user_id, 'user-1') - self.assertEqual(session.ip_address, '127.0.0.1') - self.assertEqual(session.user_agent, 'Test Agent') - self.assertEqual(len(session.resources), 0) - - def test_get_session(self): - """Test session retrieval""" - self.manager.create_session(session_id='test-456') - session = self.manager.get_session('test-456') - - self.assertIsNotNone(session) - self.assertEqual(session.session_id, 'test-456') - - # Non-existent session - session = self.manager.get_session('non-existent') - self.assertIsNone(session) - - def test_add_resource(self): - """Test adding resources to session""" - self.manager.create_session(session_id='test-789') - - # Add a resource - resource = self.manager.add_resource( - session_id='test-789', - resource_type='audio_file', - resource_id='audio-1', - path='/tmp/test.wav', - size_bytes=1024, - metadata={'format': 'wav'} - ) - - self.assertIsNotNone(resource) - self.assertEqual(resource.resource_id, 'audio-1') - self.assertEqual(resource.resource_type, 'audio_file') - self.assertEqual(resource.size_bytes, 1024) - - # Check session updated - session = self.manager.get_session('test-789') - self.assertEqual(len(session.resources), 1) - self.assertEqual(session.total_bytes_used, 1024) - - def test_resource_limits(self): - """Test resource limit enforcement""" - self.manager.create_session(session_id='test-limits') - - # Add resources up to limit - for i in range(5): - self.manager.add_resource( - session_id='test-limits', - resource_type='temp_file', - resource_id=f'file-{i}', - size_bytes=100 - ) - - session = self.manager.get_session('test-limits') - self.assertEqual(len(session.resources), 5) - - # Add one more - should remove oldest - self.manager.add_resource( - session_id='test-limits', - resource_type='temp_file', - resource_id='file-new', - size_bytes=100 - ) - - session = self.manager.get_session('test-limits') - self.assertEqual(len(session.resources), 5) # Still 5 - self.assertNotIn('file-0', session.resources) # Oldest removed - self.assertIn('file-new', session.resources) # New one added - - def test_size_limits(self): - """Test size limit enforcement""" - self.manager.create_session(session_id='test-size') - - # Add a large resource - self.manager.add_resource( - session_id='test-size', - resource_type='audio_file', - resource_id='large-1', - size_bytes=500 * 1024 # 500KB - ) - - # Add another large resource - self.manager.add_resource( - session_id='test-size', - resource_type='audio_file', - resource_id='large-2', - size_bytes=600 * 1024 # 600KB - would exceed 1MB limit - ) - - session = self.manager.get_session('test-size') - # First resource should be removed to make space - self.assertNotIn('large-1', session.resources) - self.assertIn('large-2', session.resources) - self.assertLessEqual(session.total_bytes_used, 1024 * 1024) - - def test_remove_resource(self): - """Test resource removal""" - self.manager.create_session(session_id='test-remove') - self.manager.add_resource( - session_id='test-remove', - resource_type='temp_file', - resource_id='to-remove', - size_bytes=1000 - ) - - # Remove resource - success = self.manager.remove_resource('test-remove', 'to-remove') - self.assertTrue(success) - - # Check it's gone - session = self.manager.get_session('test-remove') - self.assertEqual(len(session.resources), 0) - self.assertEqual(session.total_bytes_used, 0) - - def test_cleanup_session(self): - """Test session cleanup""" - # Create session with resources - self.manager.create_session(session_id='test-cleanup') - - # Create actual temp file - temp_file = os.path.join(self.temp_dir, 'test-file.txt') - with open(temp_file, 'w') as f: - f.write('test content') - - self.manager.add_resource( - session_id='test-cleanup', - resource_type='temp_file', - path=temp_file, - size_bytes=12 - ) - - # Cleanup session - success = self.manager.cleanup_session('test-cleanup') - self.assertTrue(success) - - # Check session is gone - session = self.manager.get_session('test-cleanup') - self.assertIsNone(session) - - # Check file is deleted - self.assertFalse(os.path.exists(temp_file)) - - def test_session_info(self): - """Test session info retrieval""" - self.manager.create_session( - session_id='test-info', - ip_address='192.168.1.1' - ) - - self.manager.add_resource( - session_id='test-info', - resource_type='audio_file', - size_bytes=2048 - ) - - info = self.manager.get_session_info('test-info') - self.assertIsNotNone(info) - self.assertEqual(info['session_id'], 'test-info') - self.assertEqual(info['ip_address'], '192.168.1.1') - self.assertEqual(info['resource_count'], 1) - self.assertEqual(info['total_bytes_used'], 2048) - - def test_stats(self): - """Test statistics calculation""" - # Create multiple sessions - for i in range(3): - self.manager.create_session(session_id=f'test-stats-{i}') - self.manager.add_resource( - session_id=f'test-stats-{i}', - resource_type='temp_file', - size_bytes=1000 - ) - - stats = self.manager.get_stats() - self.assertEqual(stats['active_sessions'], 3) - self.assertEqual(stats['active_resources'], 3) - self.assertEqual(stats['active_bytes'], 3000) - self.assertEqual(stats['total_sessions_created'], 3) - - def test_metrics_export(self): - """Test metrics export""" - self.manager.create_session(session_id='test-metrics') - metrics = self.manager.export_metrics() - - self.assertIn('sessions', metrics) - self.assertIn('resources', metrics) - self.assertIn('limits', metrics) - self.assertEqual(metrics['sessions']['active'], 1) - -class TestFlaskIntegration(unittest.TestCase): - def setUp(self): - """Set up Flask app for testing""" - self.app = Flask(__name__) - self.app.config['TESTING'] = True - self.app.config['SECRET_KEY'] = 'test-secret' - self.temp_dir = tempfile.mkdtemp() - self.app.config['UPLOAD_FOLDER'] = self.temp_dir - - # Initialize session manager - from session_manager import init_app - init_app(self.app) - - self.client = self.app.test_client() - self.ctx = self.app.test_request_context() - self.ctx.push() - - def tearDown(self): - """Clean up""" - self.ctx.pop() - shutil.rmtree(self.temp_dir, ignore_errors=True) - - def test_before_request_handler(self): - """Test Flask before_request integration""" - with self.client: - # Make a request - response = self.client.get('/') - - # Session should be created - with self.client.session_transaction() as sess: - self.assertIn('session_id', sess) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/test_size_limits.py b/test_size_limits.py deleted file mode 100755 index ed563f2..0000000 --- a/test_size_limits.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for request size limits -""" -import requests -import json -import io -import os - -BASE_URL = "http://localhost:5005" - -def test_json_size_limit(): - """Test JSON payload size limit""" - print("\n=== Testing JSON Size Limit ===") - - # Create a large JSON payload (over 1MB) - large_data = { - "text": "x" * (2 * 1024 * 1024), # 2MB of text - "source_lang": "English", - "target_lang": "Spanish" - } - - try: - response = requests.post(f"{BASE_URL}/translate", json=large_data) - print(f"Status: {response.status_code}") - if response.status_code == 413: - print(f"āœ“ Correctly rejected large JSON: {response.json()}") - else: - print(f"āœ— Should have rejected large JSON") - except Exception as e: - print(f"Error: {e}") - -def test_audio_size_limit(): - """Test audio file size limit""" - print("\n=== Testing Audio Size Limit ===") - - # Create a fake large audio file (over 25MB) - large_audio = io.BytesIO(b"x" * (30 * 1024 * 1024)) # 30MB - - files = { - 'audio': ('large_audio.wav', large_audio, 'audio/wav') - } - data = { - 'source_lang': 'English' - } - - try: - response = requests.post(f"{BASE_URL}/transcribe", files=files, data=data) - print(f"Status: {response.status_code}") - if response.status_code == 413: - print(f"āœ“ Correctly rejected large audio: {response.json()}") - else: - print(f"āœ— Should have rejected large audio") - except Exception as e: - print(f"Error: {e}") - -def test_valid_requests(): - """Test that valid-sized requests are accepted""" - print("\n=== Testing Valid Size Requests ===") - - # Small JSON payload - small_data = { - "text": "Hello world", - "source_lang": "English", - "target_lang": "Spanish" - } - - try: - response = requests.post(f"{BASE_URL}/translate", json=small_data) - print(f"Small JSON - Status: {response.status_code}") - if response.status_code != 413: - print("āœ“ Small JSON accepted") - else: - print("āœ— Small JSON should be accepted") - except Exception as e: - print(f"Error: {e}") - - # Small audio file - small_audio = io.BytesIO(b"RIFF" + b"x" * 1000) # 1KB fake WAV - files = { - 'audio': ('small_audio.wav', small_audio, 'audio/wav') - } - data = { - 'source_lang': 'English' - } - - try: - response = requests.post(f"{BASE_URL}/transcribe", files=files, data=data) - print(f"Small audio - Status: {response.status_code}") - if response.status_code != 413: - print("āœ“ Small audio accepted") - else: - print("āœ— Small audio should be accepted") - except Exception as e: - print(f"Error: {e}") - -def test_admin_endpoints(): - """Test admin endpoints for size limits""" - print("\n=== Testing Admin Endpoints ===") - - headers = {'X-Admin-Token': os.environ.get('ADMIN_TOKEN', 'default-admin-token')} - - # Get current limits - try: - response = requests.get(f"{BASE_URL}/admin/size-limits", headers=headers) - print(f"Get limits - Status: {response.status_code}") - if response.status_code == 200: - limits = response.json() - print(f"āœ“ Current limits: {limits['limits_human']}") - else: - print(f"āœ— Failed to get limits: {response.text}") - except Exception as e: - print(f"Error: {e}") - - # Update limits - new_limits = { - "max_audio_size": "30MB", - "max_json_size": 2097152 # 2MB in bytes - } - - try: - response = requests.post(f"{BASE_URL}/admin/size-limits", - json=new_limits, headers=headers) - print(f"\nUpdate limits - Status: {response.status_code}") - if response.status_code == 200: - result = response.json() - print(f"āœ“ Updated limits: {result['new_limits_human']}") - else: - print(f"āœ— Failed to update limits: {response.text}") - except Exception as e: - print(f"Error: {e}") - -if __name__ == "__main__": - print("Request Size Limit Tests") - print("========================") - print(f"Testing against: {BASE_URL}") - print("\nMake sure the Flask app is running on port 5005") - - input("\nPress Enter to start tests...") - - test_valid_requests() - test_json_size_limit() - test_audio_size_limit() - test_admin_endpoints() - - print("\nāœ… All tests completed!") \ No newline at end of file diff --git a/tts-debug-script.py b/tts-debug-script.py deleted file mode 100644 index 0366cb9..0000000 --- a/tts-debug-script.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -""" -TTS Debug Script - Tests connection to the OpenAI TTS server -""" - -import os -import sys -import json -import requests -from argparse import ArgumentParser - -def test_tts_connection(server_url, api_key, text="Hello, this is a test message"): - """Test connection to the TTS server""" - - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {api_key}" - } - - payload = { - "input": text, - "voice": "echo", - "response_format": "mp3", - "speed": 1.0 - } - - print(f"Sending request to: {server_url}") - print(f"Headers: {headers}") - print(f"Payload: {json.dumps(payload, indent=2)}") - - try: - response = requests.post( - server_url, - headers=headers, - json=payload, - timeout=15 - ) - - print(f"Response status code: {response.status_code}") - - if response.status_code == 200: - print("Success! Received audio data") - # Save to file - output_file = "tts_test_output.mp3" - with open(output_file, "wb") as f: - f.write(response.content) - print(f"Saved audio to {output_file}") - return True - else: - print("Error in response") - try: - error_data = response.json() - print(f"Error details: {json.dumps(error_data, indent=2)}") - except: - print(f"Raw response: {response.text[:500]}") - return False - - except Exception as e: - print(f"Error during request: {str(e)}") - return False - -def main(): - parser = ArgumentParser(description="Test connection to OpenAI TTS server") - parser.add_argument("--url", default="http://localhost:5050/v1/audio/speech", help="TTS server URL") - parser.add_argument("--key", default=os.environ.get("TTS_API_KEY", ""), help="API key") - parser.add_argument("--text", default="Hello, this is a test message", help="Text to synthesize") - - args = parser.parse_args() - - if not args.key: - print("Error: API key is required. Use --key argument or set TTS_API_KEY environment variable.") - return 1 - - success = test_tts_connection(args.url, args.key, args.text) - return 0 if success else 1 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tts_test_output.mp3 b/tts_test_output.mp3 deleted file mode 100644 index 8c3093c9cf039a2c7310ec5e5a18e52b96be9404..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9648 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