🎤 Voz + ⌨️ Atalhos: Ctrl+N/F/K, Escape, microfone com Web Speech API

This commit is contained in:
2026-05-25 23:25:36 +00:00
parent c935e980b2
commit 4edd3fce7d
3 changed files with 129 additions and 0 deletions
+85
View File
@@ -64,6 +64,7 @@ document.addEventListener('DOMContentLoaded', () => {
const userInput = document.getElementById('userInput'); const userInput = document.getElementById('userInput');
const btnSend = document.getElementById('btnSend'); const btnSend = document.getElementById('btnSend');
const btnVoice = document.getElementById('btnVoice');
const chatForm = document.getElementById('chatForm'); const chatForm = document.getElementById('chatForm');
const messagesContainer = document.getElementById('messagesContainer'); const messagesContainer = document.getElementById('messagesContainer');
const welcomeContainer = document.getElementById('welcomeContainer'); const welcomeContainer = document.getElementById('welcomeContainer');
@@ -90,6 +91,10 @@ document.addEventListener('DOMContentLoaded', () => {
let searchQuery = ''; let searchQuery = '';
let activeTag = 'all'; let activeTag = 'all';
// Variáveis de Reconhecimento de Voz
let recognition = null;
let isListening = false;
// ========================================================================== // ==========================================================================
// CONTROLES DE INTERFACE & SIDEBAR // CONTROLES DE INTERFACE & SIDEBAR
// ========================================================================== // ==========================================================================
@@ -156,6 +161,83 @@ document.addEventListener('DOMContentLoaded', () => {
renderHistory(); renderHistory();
}); });
// ==========================================================================
// VOZ — WEB SPEECH API (ditado)
// ==========================================================================
const initVoiceRecognition = () => {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
btnVoice.style.display = 'none';
return;
}
recognition = new SpeechRecognition();
recognition.lang = 'pt-BR';
recognition.continuous = false;
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
userInput.value = transcript;
userInput.dispatchEvent(new Event('input'));
setVoiceState(false);
};
recognition.onerror = (event) => {
console.warn('Voice error:', event.error);
setVoiceState(false);
};
recognition.onend = () => {
setVoiceState(false);
};
};
const setVoiceState = (listening) => {
isListening = listening;
if (listening) {
btnVoice.classList.add('listening');
btnVoice.setAttribute('title', 'Clique para parar');
} else {
btnVoice.classList.remove('listening');
btnVoice.setAttribute('title', 'Ditar mensagem');
}
};
btnVoice.addEventListener('click', () => {
if (!recognition) return;
if (isListening) {
recognition.stop();
} else {
recognition.start();
setVoiceState(true);
}
});
// ==========================================================================
// ATALHOS DE TECLADO
// ==========================================================================
document.addEventListener('keydown', (e) => {
// Ctrl+N → novo chat
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
createNewChat();
}
// Ctrl+F ou Ctrl+K → focar busca
if ((e.ctrlKey || e.metaKey) && (e.key === 'f' || e.key === 'k')) {
e.preventDefault();
toggleSidebar(true);
setTimeout(() => searchInput.focus(), 50);
}
// Escape → fechar sidebar
if (e.key === 'Escape') {
toggleSidebar(false);
}
});
// ========================================================================== // ==========================================================================
// HISTÓRICO DE CHATS (LOCALSTORAGE) — VERSÃO COMPLETA COM PINNED, TAGS, BUSCA // HISTÓRICO DE CHATS (LOCALSTORAGE) — VERSÃO COMPLETA COM PINNED, TAGS, BUSCA
// ========================================================================== // ==========================================================================
@@ -682,4 +764,7 @@ document.addEventListener('DOMContentLoaded', () => {
} else { } else {
createNewChat(); createNewChat();
} }
// Inicializar reconhecimento de voz
initVoiceRecognition();
}); });
+7
View File
@@ -155,6 +155,13 @@
<form id="chatForm"> <form id="chatForm">
<div class="input-box"> <div class="input-box">
<textarea id="userInput" placeholder="Mensagem Camila AI..." rows="1" autocomplete="off"></textarea> <textarea id="userInput" placeholder="Mensagem Camila AI..." rows="1" autocomplete="off"></textarea>
<button type="button" id="btnVoice" class="btn-voice" aria-label="Usar microfone" title="Ditar mensagem">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line stroke-linecap="round" stroke-linejoin="round" x1="12" x2="12" y1="19" y2="22"/>
</svg>
</button>
<button type="submit" id="btnSend" disabled aria-label="Enviar mensagem"> <button type="submit" id="btnSend" disabled aria-label="Enviar mensagem">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd" d="M11.47 2.47a.75.75 0 0 1 1.06 0l7.5 7.5a.75.75 0 1 1-1.06 1.06l-6.22-6.22V21a.75.75 0 0 1-1.5 0V4.81L5.03 11.03a.75.75 0 0 1-1.06-1.06l7.5-7.5Z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M11.47 2.47a.75.75 0 0 1 1.06 0l7.5 7.5a.75.75 0 1 1-1.06 1.06l-6.22-6.22V21a.75.75 0 0 1-1.5 0V4.81L5.03 11.03a.75.75 0 0 1-1.06-1.06l7.5-7.5Z" clip-rule="evenodd" />
+37
View File
@@ -1192,6 +1192,43 @@ code {
height: 16px; height: 16px;
} }
/* Botão de voz (microfone) */
.btn-voice {
background: none !important;
border: none !important;
color: var(--text-muted) !important;
border-radius: 50% !important;
width: 32px !important;
height: 32px !important;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
flex-shrink: 0;
transition: color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast);
padding: 0 !important;
}
.btn-voice svg {
width: 18px;
height: 18px;
}
.btn-voice:hover {
color: var(--text-primary) !important;
transform: scale(1.05);
}
.btn-voice.listening {
color: #ef4444 !important;
animation: pulse-voice 1.5s ease-in-out infinite;
}
@keyframes pulse-voice {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.15); opacity: 0.8; }
}
.disclaimer { .disclaimer {
font-size: 11px; font-size: 11px;
color: var(--text-muted); color: var(--text-muted);