🎤 Voz + ⌨️ Atalhos: Ctrl+N/F/K, Escape, microfone com Web Speech API
This commit is contained in:
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user