Housekeeping: Remove unnecessary test and temporary files

- 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 <noreply@anthropic.com>
This commit is contained in:
Adolfo Delorenzo 2025-06-03 09:24:44 -06:00
parent bcbac5c8b3
commit b5f2b53262
9 changed files with 12 additions and 3045 deletions

12
.gitignore vendored
View File

@ -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

View File

@ -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'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voice Language Translator</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
padding-top: 20px;
padding-bottom: 20px;
background-color: #f8f9fa;
}
.record-btn {
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
margin: 20px auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.record-btn:active {
transform: scale(0.95);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.recording {
background-color: #dc3545 !important;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.card {
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.card-header {
border-radius: 15px 15px 0 0 !important;
}
.language-select {
border-radius: 10px;
padding: 10px;
}
.text-display {
min-height: 100px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 10px;
margin-bottom: 15px;
}
.btn-action {
border-radius: 10px;
padding: 8px 15px;
margin: 5px;
}
.spinner-border {
width: 1rem;
height: 1rem;
margin-right: 5px;
}
.status-indicator {
font-size: 0.9rem;
font-style: italic;
color: #6c757d;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center mb-4">Voice Language Translator</h1>
<p class="text-center text-muted">Powered by Gemma 3, Whisper & Edge TTS</p>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Source</h5>
</div>
<div class="card-body">
<select id="sourceLanguage" class="form-select language-select mb-3">
{% for language in languages %}
<option value="{{ language }}">{{ language }}</option>
{% endfor %}
</select>
<div class="text-display" id="sourceText">
<p class="text-muted">Your transcribed text will appear here...</p>
</div>
<div class="d-flex justify-content-between">
<button id="playSource" class="btn btn-outline-primary btn-action" disabled>
<i class="fas fa-play"></i> Play
</button>
<button id="clearSource" class="btn btn-outline-secondary btn-action">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Translation</h5>
</div>
<div class="card-body">
<select id="targetLanguage" class="form-select language-select mb-3">
{% for language in languages %}
<option value="{{ language }}">{{ language }}</option>
{% endfor %}
</select>
<div class="text-display" id="translatedText">
<p class="text-muted">Translation will appear here...</p>
</div>
<div class="d-flex justify-content-between">
<button id="playTranslation" class="btn btn-outline-success btn-action" disabled>
<i class="fas fa-play"></i> Play
</button>
<button id="clearTranslation" class="btn btn-outline-secondary btn-action">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</div>
</div>
</div>
</div>
<div class="text-center">
<button id="recordBtn" class="btn btn-primary record-btn">
<i class="fas fa-microphone"></i>
</button>
<p class="status-indicator" id="statusIndicator">Click to start recording</p>
</div>
<div class="text-center mt-3">
<button id="translateBtn" class="btn btn-success" disabled>
<i class="fas fa-language"></i> Translate
</button>
</div>
<div class="mt-3">
<div class="progress d-none" id="progressContainer">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
</div>
<audio id="audioPlayer" style="display: none;"></audio>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 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');
// Set initial values
let isRecording = false;
let mediaRecorder = null;
let audioChunks = [];
let currentSourceText = '';
let currentTranslationText = '';
// 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() {
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() {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.addEventListener('dataavailable', event => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener('stop', () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
transcribeAudio(audioBlob);
});
mediaRecorder.start();
isRecording = true;
recordBtn.classList.add('recording');
recordBtn.classList.replace('btn-primary', 'btn-danger');
recordBtn.innerHTML = '<i class="fas fa-stop"></i>';
statusIndicator.textContent = 'Recording... Click to stop';
})
.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() {
mediaRecorder.stop();
isRecording = false;
recordBtn.classList.remove('recording');
recordBtn.classList.replace('btn-danger', 'btn-primary');
recordBtn.innerHTML = '<i class="fas fa-microphone"></i>';
statusIndicator.textContent = 'Processing audio...';
// Stop all audio tracks
mediaRecorder.stream.getTracks().forEach(track => track.stop());
}
// Function to transcribe audio
function transcribeAudio(audioBlob) {
const formData = new FormData();
formData.append('audio', audioBlob);
formData.append('source_lang', sourceLanguage.value);
showProgress();
fetch('/transcribe', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
hideProgress();
if (data.success) {
currentSourceText = data.text;
sourceText.innerHTML = `<p>${data.text}</p>`;
playSource.disabled = false;
translateBtn.disabled = false;
statusIndicator.textContent = 'Transcription complete';
} else {
sourceText.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
statusIndicator.textContent = 'Transcription failed';
}
})
.catch(error => {
hideProgress();
console.error('Transcription error:', error);
sourceText.innerHTML = `<p class="text-danger">Failed to transcribe audio. Please try again.</p>`;
statusIndicator.textContent = 'Transcription failed';
});
}
// Translate button click event
translateBtn.addEventListener('click', function() {
if (!currentSourceText) {
return;
}
statusIndicator.textContent = 'Translating...';
showProgress();
fetch('/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: currentSourceText,
source_lang: sourceLanguage.value,
target_lang: targetLanguage.value
})
})
.then(response => response.json())
.then(data => {
hideProgress();
if (data.success) {
currentTranslationText = data.translation;
translatedText.innerHTML = `<p>${data.translation}</p>`;
playTranslation.disabled = false;
statusIndicator.textContent = 'Translation complete';
} else {
translatedText.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
statusIndicator.textContent = 'Translation failed';
}
})
.catch(error => {
hideProgress();
console.error('Translation error:', error);
translatedText.innerHTML = `<p class="text-danger">Failed to translate. Please try again.</p>`;
statusIndicator.textContent = 'Translation failed';
});
});
// 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
function playAudio(text, language) {
showProgress();
fetch('/speak', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: text,
language: language
})
})
.then(response => response.json())
.then(data => {
hideProgress();
if (data.success) {
audioPlayer.src = data.audio_url;
audioPlayer.onended = function() {
statusIndicator.textContent = 'Ready';
};
audioPlayer.play();
} else {
statusIndicator.textContent = 'TTS failed';
alert('Failed to play audio: ' + data.error);
}
})
.catch(error => {
hideProgress();
console.error('TTS error:', error);
statusIndicator.textContent = 'TTS failed';
});
}
// Clear buttons
clearSource.addEventListener('click', function() {
sourceText.innerHTML = '<p class="text-muted">Your transcribed text will appear here...</p>';
currentSourceText = '';
playSource.disabled = true;
translateBtn.disabled = true;
});
clearTranslation.addEventListener('click', function() {
translatedText.innerHTML = '<p class="text-muted">Translation will appear here...</p>';
currentTranslationText = '';
playTranslation.disabled = true;
});
// 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;
}
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);
}
});
</script>
</body>
</html>
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/<filename>')
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"

File diff suppressed because it is too large Load Diff

View File

@ -1,228 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CORS Test for Talk2Me</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.test-result {
margin: 10px 0;
padding: 10px;
border-radius: 5px;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #0056b3;
}
input {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
#results {
margin-top: 20px;
}
pre {
background-color: #f8f9fa;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
}
</style>
</head>
<body>
<h1>CORS Test for Talk2Me API</h1>
<p>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.</p>
<div>
<label for="apiUrl">API Base URL:</label>
<input type="text" id="apiUrl" placeholder="http://localhost:5005" value="http://localhost:5005">
</div>
<h2>Tests:</h2>
<button onclick="testHealthEndpoint()">Test Health Endpoint</button>
<button onclick="testPreflightRequest()">Test Preflight Request</button>
<button onclick="testTranscribeEndpoint()">Test Transcribe Endpoint (OPTIONS)</button>
<button onclick="testWithCredentials()">Test With Credentials</button>
<div id="results"></div>
<script>
function addResult(test, success, message, details = null) {
const resultsDiv = document.getElementById('results');
const resultDiv = document.createElement('div');
resultDiv.className = `test-result ${success ? 'success' : 'error'}`;
let html = `<strong>${test}:</strong> ${message}`;
if (details) {
html += `<pre>${JSON.stringify(details, null, 2)}</pre>`;
}
resultDiv.innerHTML = html;
resultsDiv.appendChild(resultDiv);
}
function getApiUrl() {
return document.getElementById('apiUrl').value.trim();
}
async function testHealthEndpoint() {
const apiUrl = getApiUrl();
try {
const response = await fetch(`${apiUrl}/health`, {
method: 'GET',
mode: 'cors',
headers: {
'Origin': window.location.origin
}
});
const data = await response.json();
// Check CORS headers
const corsHeaders = {
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
'Access-Control-Allow-Credentials': response.headers.get('Access-Control-Allow-Credentials')
};
addResult('Health Endpoint GET', true, 'Request successful', {
status: response.status,
data: data,
corsHeaders: corsHeaders
});
} catch (error) {
addResult('Health Endpoint GET', false, error.message);
}
}
async function testPreflightRequest() {
const apiUrl = getApiUrl();
try {
const response = await fetch(`${apiUrl}/api/push-public-key`, {
method: 'OPTIONS',
mode: 'cors',
headers: {
'Origin': window.location.origin,
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'content-type'
}
});
const corsHeaders = {
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
'Access-Control-Max-Age': response.headers.get('Access-Control-Max-Age')
};
addResult('Preflight Request', response.ok, `Status: ${response.status}`, corsHeaders);
} catch (error) {
addResult('Preflight Request', false, error.message);
}
}
async function testTranscribeEndpoint() {
const apiUrl = getApiUrl();
try {
const response = await fetch(`${apiUrl}/transcribe`, {
method: 'OPTIONS',
mode: 'cors',
headers: {
'Origin': window.location.origin,
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'content-type'
}
});
const corsHeaders = {
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
'Access-Control-Allow-Credentials': response.headers.get('Access-Control-Allow-Credentials')
};
addResult('Transcribe Endpoint OPTIONS', response.ok, `Status: ${response.status}`, corsHeaders);
} catch (error) {
addResult('Transcribe Endpoint OPTIONS', false, error.message);
}
}
async function testWithCredentials() {
const apiUrl = getApiUrl();
try {
const response = await fetch(`${apiUrl}/health`, {
method: 'GET',
mode: 'cors',
credentials: 'include',
headers: {
'Origin': window.location.origin
}
});
const data = await response.json();
addResult('Request with Credentials', true, 'Request successful', {
status: response.status,
credentialsIncluded: true,
data: data
});
} catch (error) {
addResult('Request with Credentials', false, error.message);
}
}
// Clear results before running new tests
function clearResults() {
document.getElementById('results').innerHTML = '';
}
// Add event listeners
document.querySelectorAll('button').forEach(button => {
button.addEventListener('click', (e) => {
if (!e.target.textContent.includes('Test')) return;
clearResults();
});
});
// Show current origin
window.addEventListener('load', () => {
const info = document.createElement('div');
info.style.marginBottom = '20px';
info.style.padding = '10px';
info.style.backgroundColor = '#e9ecef';
info.style.borderRadius = '5px';
info.innerHTML = `<strong>Current Origin:</strong> ${window.location.origin}<br>
<strong>Protocol:</strong> ${window.location.protocol}<br>
<strong>Note:</strong> For effective CORS testing, open this file from a different origin than your API server.`;
document.body.insertBefore(info, document.querySelector('h2'));
});
</script>
</body>
</html>

View File

@ -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()

View File

@ -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()

View File

@ -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!")

View File

@ -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())

Binary file not shown.