|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const ttsForm = document.getElementById('ttsForm'); |
|
const podcastForm = document.getElementById('podcastForm'); |
|
const textInput = document.getElementById('textInput'); |
|
const scenarioInput = document.getElementById('scenarioInput'); |
|
const voiceSelect = document.getElementById('voiceSelect'); |
|
const generateBtn = document.getElementById('generateBtn'); |
|
const generatePodcastBtn = document.getElementById('generatePodcastBtn'); |
|
const audioCard = document.getElementById('audioCard'); |
|
const audioPlayer = document.getElementById('audioPlayer'); |
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
const newGenerationBtn = document.getElementById('newGenerationBtn'); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
const progressBar = document.getElementById('progressBar'); |
|
const progressBarInner = progressBar.querySelector('.progress-bar'); |
|
const progressText = document.getElementById('progressText'); |
|
const errorAlert = document.getElementById('errorAlert'); |
|
const errorMessage = document.getElementById('errorMessage'); |
|
|
|
|
|
let currentAudioUrl = null; |
|
let currentAudioFilename = null; |
|
|
|
|
|
const exampleScenario = { |
|
"title": "Le management participatif", |
|
"language": "fr-FR", |
|
"characters": [ |
|
{ |
|
"name": "Manager", |
|
"voice": "Kore", |
|
"text": "Bonjour Sophie. J'aimerais avoir ton avis sur le nouveau planning que nous voulons mettre en place." |
|
}, |
|
{ |
|
"name": "Sophie", |
|
"voice": "Puck", |
|
"text": "Bonjour ! Merci de me demander. Je pense que ce nouveau planning pourrait être bénéfique, mais nous devons ajuster les horaires de l'équipe technique." |
|
}, |
|
{ |
|
"name": "Manager", |
|
"voice": "Kore", |
|
"text": "Bonne remarque. Peux-tu organiser une réunion avec eux pour en discuter ?" |
|
}, |
|
{ |
|
"name": "Sophie", |
|
"voice": "Puck", |
|
"text": "Avec plaisir. Je les réunis demain matin." |
|
} |
|
] |
|
}; |
|
|
|
scenarioInput.value = JSON.stringify(exampleScenario, null, 2); |
|
|
|
|
|
ttsForm.addEventListener('submit', async function(e) { |
|
e.preventDefault(); |
|
|
|
const text = textInput.value.trim(); |
|
const voice = voiceSelect.value; |
|
|
|
|
|
if (!text) { |
|
showError('Veuillez entrer du texte à convertir en parole.'); |
|
return; |
|
} |
|
|
|
|
|
setLoadingState(true); |
|
|
|
try { |
|
|
|
const response = await fetch('/generate', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
text: text, |
|
voice: voice |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (!response.ok) { |
|
throw new Error(data.error || 'Échec de génération de la parole'); |
|
} |
|
|
|
|
|
currentAudioUrl = data.audioUrl; |
|
currentAudioFilename = currentAudioUrl.split('/').pop(); |
|
|
|
|
|
audioPlayer.src = currentAudioUrl; |
|
audioPlayer.load(); |
|
|
|
|
|
audioCard.classList.remove('d-none'); |
|
audioCard.classList.add('fade-in'); |
|
|
|
|
|
try { |
|
await audioPlayer.play(); |
|
} catch (playError) { |
|
console.log('Lecture automatique empêchée par le navigateur.', playError); |
|
} |
|
|
|
} catch (error) { |
|
showError(error.message || 'Une erreur inattendue est survenue. Veuillez réessayer.'); |
|
} finally { |
|
setLoadingState(false); |
|
} |
|
}); |
|
|
|
|
|
podcastForm.addEventListener('submit', async function(e) { |
|
e.preventDefault(); |
|
|
|
let scenarioText = scenarioInput.value.trim(); |
|
|
|
|
|
if (!scenarioText) { |
|
showError('Veuillez entrer un scénario JSON.'); |
|
return; |
|
} |
|
|
|
let scenario; |
|
try { |
|
scenario = JSON.parse(scenarioText); |
|
|
|
|
|
if (!scenario.characters || !Array.isArray(scenario.characters) || scenario.characters.length === 0) { |
|
throw new Error('Le scénario doit contenir un tableau "characters" avec au moins un personnage.'); |
|
} |
|
|
|
for (const character of scenario.characters) { |
|
if (!character.name || !character.voice || !character.text) { |
|
throw new Error('Chaque personnage doit avoir un nom, une voix et du texte.'); |
|
} |
|
} |
|
|
|
} catch (error) { |
|
showError(`Erreur dans le format JSON: ${error.message}`); |
|
return; |
|
} |
|
|
|
|
|
setLoadingState(true, true); |
|
|
|
try { |
|
|
|
const response = await fetch('/generate-podcast', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify(scenario) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (!response.ok) { |
|
throw new Error(data.error || 'Échec de démarrage de la génération du podcast'); |
|
} |
|
|
|
|
|
const statusPoller = setInterval(async () => { |
|
try { |
|
const statusResponse = await fetch('/podcast-status'); |
|
const statusData = await statusResponse.json(); |
|
|
|
if (statusData.status === 'in_progress') { |
|
|
|
updateProgress( |
|
statusData.current, |
|
statusData.total, |
|
statusData.message || 'Génération en cours...' |
|
); |
|
} else if (statusData.status === 'complete') { |
|
|
|
clearInterval(statusPoller); |
|
|
|
|
|
if (statusData.audioUrl) { |
|
currentAudioUrl = statusData.audioUrl; |
|
currentAudioFilename = currentAudioUrl.split('/').pop(); |
|
|
|
|
|
audioPlayer.src = currentAudioUrl; |
|
audioPlayer.load(); |
|
|
|
|
|
audioCard.classList.remove('d-none'); |
|
audioCard.classList.add('fade-in'); |
|
|
|
|
|
try { |
|
await audioPlayer.play(); |
|
} catch (playError) { |
|
console.log('Lecture automatique empêchée par le navigateur.', playError); |
|
} |
|
|
|
|
|
setLoadingState(false); |
|
} else { |
|
throw new Error('Podcast généré mais URL audio manquante'); |
|
} |
|
} else if (statusData.status === 'error' || statusData.message.startsWith('Erreur')) { |
|
|
|
clearInterval(statusPoller); |
|
throw new Error(statusData.message || 'Erreur pendant la génération du podcast'); |
|
} |
|
} catch (pollError) { |
|
clearInterval(statusPoller); |
|
throw pollError; |
|
} |
|
}, 1000); |
|
|
|
} catch (error) { |
|
showError(error.message || 'Une erreur inattendue est survenue. Veuillez réessayer.'); |
|
setLoadingState(false); |
|
} |
|
}); |
|
|
|
|
|
downloadBtn.addEventListener('click', function() { |
|
if (currentAudioUrl) { |
|
const downloadLink = document.createElement('a'); |
|
downloadLink.href = `/download/${currentAudioFilename}`; |
|
downloadLink.download = 'gemini_podcast.wav'; |
|
document.body.appendChild(downloadLink); |
|
downloadLink.click(); |
|
document.body.removeChild(downloadLink); |
|
} |
|
}); |
|
|
|
|
|
newGenerationBtn.addEventListener('click', function() { |
|
|
|
audioCard.classList.add('d-none'); |
|
errorAlert.classList.add('d-none'); |
|
|
|
|
|
const activeTabPane = document.querySelector('.tab-pane.active'); |
|
if (activeTabPane.id === 'simple-tts') { |
|
textInput.focus(); |
|
} else { |
|
scenarioInput.focus(); |
|
} |
|
}); |
|
|
|
|
|
function showError(message) { |
|
errorMessage.textContent = message; |
|
errorAlert.classList.remove('d-none'); |
|
|
|
|
|
setTimeout(() => { |
|
errorAlert.classList.add('d-none'); |
|
}, 8000); |
|
} |
|
|
|
|
|
function setLoadingState(isLoading, withProgress = false) { |
|
if (isLoading) { |
|
generateBtn.disabled = true; |
|
generatePodcastBtn.disabled = true; |
|
loadingIndicator.classList.remove('d-none'); |
|
errorAlert.classList.add('d-none'); |
|
|
|
if (withProgress) { |
|
progressBar.classList.remove('d-none'); |
|
progressText.classList.remove('d-none'); |
|
updateProgress(0, 100, 'Préparation du podcast...'); |
|
} else { |
|
progressBar.classList.add('d-none'); |
|
progressText.classList.add('d-none'); |
|
} |
|
} else { |
|
generateBtn.disabled = false; |
|
generatePodcastBtn.disabled = false; |
|
loadingIndicator.classList.add('d-none'); |
|
progressBar.classList.add('d-none'); |
|
progressText.classList.add('d-none'); |
|
} |
|
} |
|
|
|
|
|
function updateProgress(current, total, message) { |
|
const percentage = Math.min(100, Math.max(0, Math.round((current / total) * 100))); |
|
progressBarInner.style.width = `${percentage}%`; |
|
progressBarInner.setAttribute('aria-valuenow', percentage); |
|
progressText.textContent = message || `Progression: ${percentage}%`; |
|
} |
|
|
|
|
|
audioPlayer.addEventListener('ended', function() { |
|
console.log('Audio playback completed'); |
|
}); |
|
}); |
|
|