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 8c3093c..0000000 Binary files a/tts_test_output.mp3 and /dev/null differ