🎤 feat: Sistema de conversa por voz - auto-TTS quando mensagem vem do microfone, envio automático, melhor visibilidade do ícone

This commit is contained in:
2026-05-26 19:55:52 +00:00
parent 6c35f7ed1b
commit 0047614bf7
2 changed files with 34 additions and 10 deletions
+24 -3
View File
@@ -288,6 +288,7 @@ const initApp = () => {
// Variáveis de Reconhecimento de Voz // Variáveis de Reconhecimento de Voz
let recognition = null; let recognition = null;
let isListening = false; let isListening = false;
let lastInputWasVoice = false; // Rastrear se a última mensagem veio do microfone
// ========================================================================== // ==========================================================================
// THEME TOGGLE — Claro/Escuro // THEME TOGGLE — Claro/Escuro
@@ -488,13 +489,14 @@ const initApp = () => {
}); });
// ========================================================================== // ==========================================================================
// VOZ — WEB SPEECH API (ditado) // VOZ — WEB SPEECH API (ditado + conversa por voz)
// ========================================================================== // ==========================================================================
const initVoiceRecognition = () => { const initVoiceRecognition = () => {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) { if (!SpeechRecognition) {
btnVoice.style.display = 'none'; btnVoice.style.display = 'none';
console.warn('SpeechRecognition não suportado neste navegador.');
return; return;
} }
@@ -509,6 +511,16 @@ const initApp = () => {
userInput.value = transcript; userInput.value = transcript;
userInput.dispatchEvent(new Event('input')); userInput.dispatchEvent(new Event('input'));
setVoiceState(false); setVoiceState(false);
// Marca que o input veio do microfone para auto-responder por voz
lastInputWasVoice = true;
// Envia automaticamente a mensagem ditada
if (transcript.trim()) {
setTimeout(() => {
chatForm.dispatchEvent(new Event('submit', { cancelable: true }));
}, 300);
}
}; };
recognition.onerror = (event) => { recognition.onerror = (event) => {
@@ -525,10 +537,10 @@ const initApp = () => {
isListening = listening; isListening = listening;
if (listening) { if (listening) {
btnVoice.classList.add('listening'); btnVoice.classList.add('listening');
btnVoice.setAttribute('title', 'Clique para parar'); btnVoice.setAttribute('title', 'Clique para parar de ouvir');
} else { } else {
btnVoice.classList.remove('listening'); btnVoice.classList.remove('listening');
btnVoice.setAttribute('title', 'Ditar mensagem'); btnVoice.setAttribute('title', 'Conversar por voz');
} }
}; };
@@ -1685,6 +1697,15 @@ const initApp = () => {
scrollToBottom(); scrollToBottom();
// AUTO-TTS: Se a mensagem veio do microfone, ler a resposta em voz alta automaticamente
if (lastInputWasVoice && assistantContent && !isMediaResponse) {
console.log('[VOZ] Mensagem veio do microfone — respondendo por voz automaticamente.');
setTimeout(() => {
speakText(assistantContent, speakBtn);
}, 400);
}
lastInputWasVoice = false;
// Salvar resposta no estado e localStorage // Salvar resposta no estado e localStorage
const assistantMsg = { role: 'assistant', content: assistantContent }; const assistantMsg = { role: 'assistant', content: assistantContent };
chat.messages.push(assistantMsg); chat.messages.push(assistantMsg);
+10 -7
View File
@@ -1223,10 +1223,11 @@ code {
.btn-voice { .btn-voice {
background: none !important; background: none !important;
border: none !important; border: none !important;
color: var(--text-muted) !important; color: var(--text-secondary, #a3a3a3) !important;
border-radius: 50% !important; border-radius: 50% !important;
width: 32px !important; width: 36px !important;
height: 32px !important; height: 36px !important;
min-width: 36px !important;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -1237,17 +1238,19 @@ code {
} }
.btn-voice svg { .btn-voice svg {
width: 18px; width: 20px;
height: 18px; height: 20px;
} }
.btn-voice:hover { .btn-voice:hover {
color: var(--text-primary) !important; color: var(--brand-green, #10a37f) !important;
transform: scale(1.05); background-color: rgba(16, 163, 127, 0.1) !important;
transform: scale(1.1);
} }
.btn-voice.listening { .btn-voice.listening {
color: #ef4444 !important; color: #ef4444 !important;
background-color: rgba(239, 68, 68, 0.12) !important;
animation: pulse-voice 1.5s ease-in-out infinite; animation: pulse-voice 1.5s ease-in-out infinite;
} }