Apex / templates /index.html
ernestmindres's picture
Update templates/index.html
3e2ce14 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TTS Offline - Verba</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 20px;
background-color: #f4f7f6;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
color: #007bff;
text-align: center;
margin-bottom: 30px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
textarea, select, input[type="file"] {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 6px;
box-sizing: border-box;
}
textarea {
resize: vertical;
min-height: 150px;
}
.file-upload-container {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
border-radius: 6px;
margin-bottom: 20px;
}
button {
background-color: #28a745;
color: white;
padding: 12px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s ease;
width: 100%;
font-weight: bold;
}
button:hover {
background-color: #218838;
}
#status-message {
margin-top: 20px;
padding: 10px;
border-radius: 6px;
text-align: center;
display: none;
}
.status-loading { background-color: #ffc107; color: #333; }
.status-error { background-color: #dc3545; color: white; }
.status-success { background-color: #28a745; color: white; }
#audio-player {
width: 100%;
margin-top: 20px;
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>🗣️ Lecteur à Voix Haute (Offline TTS)</h1>
<form id="tts-form">
<label for="text_input">Texte Manuel :</label>
<textarea id="text_input" name="text_input" placeholder="Entrez le texte à lire ici..."></textarea>
<div class="file-upload-container">
<label for="file_input">Ou Télécharger un Fichier (.txt seulement) :</label>
<input type="file" id="file_input" name="file_input" accept=".txt">
</div>
<label for="language_select">Langue de Lecture :</label>
<select id="language_select" name="language_select">
{% for name, id in languages.items() %}
<option value="{{ id }}">{{ name }}</option>
{% endfor %}
</select>
<button type="submit" id="run-button">
<span id="run-icon">▶️</span>
<span id="run-text">Lire le Texte</span>
</button>
</form>
<div id="status-message"></div>
<audio id="audio-player" controls></audio>
</div>
<script>
document.getElementById('tts-form').addEventListener('submit', async function(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const runButton = document.getElementById('run-button');
const statusMessage = document.getElementById('status-message');
const audioPlayer = document.getElementById('audio-player');
// --- Logique de validation et de nettoyage ---
const textInput = formData.get('text_input').trim();
const fileInput = document.getElementById('file_input').files[0];
if (!textInput && !fileInput) {
displayStatus('Veuillez entrer du texte ou sélectionner un fichier.', 'error');
return;
}
if (textInput && fileInput) {
displayStatus('Veuillez choisir entre le texte manuel ET le fichier, pas les deux.', 'error');
return;
}
// --- Affichage du statut de chargement ---
runButton.disabled = true;
document.getElementById('run-text').textContent = 'Génération de l\'Audio...';
document.getElementById('run-icon').textContent = '⏳';
displayStatus('Génération de l\'audio en cours. Veuillez patienter...', 'loading');
audioPlayer.style.display = 'none';
audioPlayer.removeAttribute('src');
try {
// --- Appel Asynchrone (Fetch) ---
const response = await fetch('/read', {
method: 'POST',
body: formData
});
if (response.ok) {
// La réponse est un fichier audio (audio/wav)
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
// Configuration et lecture
audioPlayer.src = audioUrl;
audioPlayer.style.display = 'block';
audioPlayer.play();
displayStatus('Lecture de l\'audio démarrée !', 'success');
} else {
// Erreur du serveur (400, 500, etc.)
const errorText = await response.text();
displayStatus('Erreur (' + response.status + '): ' + errorText, 'error');
}
} catch (error) {
// Erreur réseau ou autre
console.error('Erreur Fetch:', error);
displayStatus('Erreur de connexion ou du serveur.', 'error');
} finally {
// --- Réinitialisation du bouton ---
runButton.disabled = false;
document.getElementById('run-text').textContent = 'Lire le Texte';
document.getElementById('run-icon').textContent = '▶️';
}
});
function displayStatus(message, type) {
const statusMessage = document.getElementById('status-message');
statusMessage.textContent = message;
statusMessage.className = ''; // Réinitialiser les classes
statusMessage.classList.add('status-' + type);
statusMessage.style.display = 'block';
}
</script>
</body>
</html>