Ajout de la fonctionnalité TTS (Text-to-Speech) avec XTTS v2

- Ajout de TTS>=0.22.0 aux dépendances
- Création du module utils/tts_generator.py avec Coqui XTTS v2
  * Support GPU avec mixed precision (FP16)
  * Lazy loading avec singleton pattern
  * Chunking automatique pour textes longs
  * Support multilingue (fr, en, es, de, etc.)
- Ajout de la route /chat/export-audio dans flask_app.py
- Ajout du bouton Audio dans chat.html (côté Word/PDF)
- Génération audio WAV téléchargeable depuis les réponses

Optimisé pour GPU 4070 (8GB VRAM) : utilise 4-6GB, génération rapide
Qualité : voix naturelle française avec prosodie expressive

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 14:31:30 +01:00
parent b835cd13ea
commit d91abd3566
4 changed files with 336 additions and 4 deletions

View File

@@ -590,7 +590,8 @@
/* Export buttons - compact size */
.export-word-btn,
.export-pdf-btn {
.export-pdf-btn,
.export-audio-btn {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -613,14 +614,16 @@
}
.export-word-btn:hover,
.export-pdf-btn:hover {
.export-pdf-btn:hover,
.export-audio-btn:hover {
background-color: var(--color-accent-alt);
border-color: var(--color-accent-alt);
color: var(--color-bg-main);
}
.export-word-btn svg,
.export-pdf-btn svg {
.export-pdf-btn svg,
.export-audio-btn svg {
width: 15px;
height: 15px;
flex-shrink: 0;
@@ -977,6 +980,7 @@
let assistantContentDiv = null;
let exportWordBtn = null;
let exportPdfBtn = null;
let exportAudioBtn = null;
let exportContainer = null;
let accumulatedText = '';
@@ -1006,6 +1010,7 @@
assistantContentDiv = result.contentDiv;
exportWordBtn = result.exportWordBtn;
exportPdfBtn = result.exportPdfBtn;
exportAudioBtn = result.exportAudioBtn;
exportContainer = result.exportContainer;
}
@@ -1049,6 +1054,11 @@
originalQuestion
);
});
// Add click handler for Audio export
exportAudioBtn.addEventListener('click', async () => {
await exportToAudio(accumulatedText);
});
}
eventSource.close();
@@ -1142,15 +1152,28 @@
PDF
`;
// Add export Audio button
const exportAudioBtn = document.createElement('button');
exportAudioBtn.className = 'export-audio-btn';
exportAudioBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M11 5L6 9H2v6h4l5 4V5z"/>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
</svg>
Audio
`;
exportContainer.appendChild(exportWordBtn);
exportContainer.appendChild(exportPdfBtn);
exportContainer.appendChild(exportAudioBtn);
messageDiv.appendChild(label);
messageDiv.appendChild(contentDiv);
messageDiv.appendChild(exportContainer);
chatMessages.appendChild(messageDiv);
return { messageDiv, contentDiv, exportWordBtn, exportPdfBtn, exportContainer };
return { messageDiv, contentDiv, exportWordBtn, exportPdfBtn, exportAudioBtn, exportContainer };
}
function addErrorMessage(message) {
@@ -1348,6 +1371,39 @@
}
}
async function exportToAudio(assistantResponse) {
try {
const response = await fetch('/chat/export-audio', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
assistant_response: assistantResponse,
language: 'fr'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'TTS failed');
}
// Download file
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chat_audio_${new Date().getTime()}.wav`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('TTS error:', error);
alert(`Erreur TTS: ${error.message}`);
}
}
// Initialize
sendBtn.disabled = true;
</script>