🚀 Auto-deploy: Camila atualizado em 03/06/2026 10:52:52

This commit is contained in:
2026-06-03 10:52:52 +00:00
parent 0f3bf4c2dd
commit b744b71619
2 changed files with 180 additions and 34 deletions
+152 -34
View File
@@ -814,23 +814,79 @@ const initApp = () => {
}
};
// Falar texto com TTS nativa
const speakText = (text, btn) => {
let currentSpeakingBtn = null;
let currentAudio = null;
let ttsFallbackTimeout = null;
// Função auxiliar para parar qualquer áudio em andamento (tanto nativo quanto do backend)
const stopAllSpeech = () => {
// Parar áudio do backend
if (currentAudio) {
currentAudio.pause();
currentAudio = null;
}
// Parar SpeechSynthesis nativo
window.speechSynthesis.cancel();
// Limpar timeouts pendentes
if (ttsFallbackTimeout) {
clearTimeout(ttsFallbackTimeout);
ttsFallbackTimeout = null;
}
// Resetar estado do botão ativo anterior
if (currentSpeakingBtn) {
resetSpeakBtnState(currentSpeakingBtn);
currentSpeakingBtn = null;
}
};
const speakTextLocalFallback = (cleanText, btn) => {
console.log('[TTS] Acionando fallback local (voz nativa do navegador)...');
// Configura SpeechSynthesis nativo
const utterance = new SpeechSynthesisUtterance(cleanText);
utterance.lang = 'pt-BR';
utterance.rate = 1.15;
utterance.pitch = 1.0;
const voice = getBestVoice();
if (voice) utterance.voice = voice;
utterance.onstart = () => {
if (btn) {
btn.classList.add('speaking');
const span = btn.querySelector('span');
if (span) span.textContent = 'Parar';
}
};
const handleStop = (event) => {
console.log('TTS nativo finalizado ou com erro:', event.type);
if (currentSpeakingBtn === btn) {
resetSpeakBtnState(btn);
currentSpeakingBtn = null;
}
};
utterance.onend = handleStop;
utterance.onerror = handleStop;
window.speechSynthesis.speak(utterance);
};
// Falar texto com reprodução híbrida (prioriza Opção A: MsEdgeTTS com fallback local)
const speakText = async (text, btn) => {
if (!text) return;
// Se clicar no botão que já está ativamente falando, parar a reprodução
if (window.speechSynthesis.speaking && currentSpeakingBtn === btn) {
window.speechSynthesis.cancel();
resetSpeakBtnState(btn);
currentSpeakingBtn = null;
// Se clicar no botão que já está ativo, para tudo e retorna
if (currentSpeakingBtn === btn) {
stopAllSpeech();
return;
}
// Se houver outra fala em andamento ou fila travada, limpa tudo e reseta o botão antigo
window.speechSynthesis.cancel();
if (currentSpeakingBtn) {
resetSpeakBtnState(currentSpeakingBtn);
}
// Para qualquer reprodução anterior
stopAllSpeech();
currentSpeakingBtn = btn;
@@ -849,34 +905,96 @@ const initApp = () => {
return;
}
const utterance = new SpeechSynthesisUtterance(clean);
utterance.lang = 'pt-BR';
utterance.rate = 1.15;
utterance.pitch = 1.0;
// Coloca o botão em estado de "carregando"
if (btn) {
btn.classList.add('speaking');
const span = btn.querySelector('span');
if (span) span.textContent = 'Carregando...';
}
const voice = getBestVoice();
if (voice) utterance.voice = voice;
let fallbackTriggered = false;
utterance.onstart = () => {
if (btn) {
btn.classList.add('speaking');
const span = btn.querySelector('span');
if (span) span.textContent = 'Parar';
}
};
const handleStop = (event) => {
console.log('TTS finalizado ou com erro:', event.type);
// Configura o timeout de 15 segundos para o fallback nativo
ttsFallbackTimeout = setTimeout(() => {
if (currentSpeakingBtn === btn) {
resetSpeakBtnState(btn);
currentSpeakingBtn = null;
fallbackTriggered = true;
if (currentAudio) {
currentAudio.pause();
currentAudio = null;
}
speakTextLocalFallback(clean, btn);
}
};
}, 15000);
utterance.onend = handleStop;
utterance.onerror = handleStop;
try {
console.log('[TTS] Solicitando voz neural de alta qualidade ao backend...');
const response = await fetch('/api/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: clean })
});
window.speechSynthesis.speak(utterance);
if (!response.ok) {
throw new Error(`Erro no servidor: ${response.status}`);
}
const blob = await response.blob();
// Se o fallback já foi acionado durante a espera, ignora o retorno do áudio do backend
if (fallbackTriggered) {
return;
}
// Limpar o timeout de fallback já que o áudio respondeu a tempo
if (ttsFallbackTimeout) {
clearTimeout(ttsFallbackTimeout);
ttsFallbackTimeout = null;
}
const audioUrl = URL.createObjectURL(blob);
currentAudio = new Audio(audioUrl);
currentAudio.onplay = () => {
if (currentSpeakingBtn === btn) {
const span = btn.querySelector('span');
if (span) span.textContent = 'Parar';
}
};
const handleAudioStop = () => {
if (currentSpeakingBtn === btn) {
resetSpeakBtnState(btn);
currentSpeakingBtn = null;
currentAudio = null;
}
URL.revokeObjectURL(audioUrl);
};
currentAudio.onended = handleAudioStop;
currentAudio.onerror = (e) => {
console.error('[TTS] Erro ao reproduzir áudio do backend:', e);
if (currentSpeakingBtn === btn && !fallbackTriggered) {
fallbackTriggered = true;
speakTextLocalFallback(clean, btn);
}
};
await currentAudio.play();
} catch (err) {
console.error('[TTS] Falha ao obter áudio do backend, acionando fallback local imediatamente:', err);
// Limpa timeout e ativa fallback local imediatamente se der erro de rede
if (ttsFallbackTimeout) {
clearTimeout(ttsFallbackTimeout);
ttsFallbackTimeout = null;
}
if (currentSpeakingBtn === btn && !fallbackTriggered) {
fallbackTriggered = true;
speakTextLocalFallback(clean, btn);
}
}
};
// ==========================================================================