// State Management let isTyping = false; let messageHistory = []; let recognition = null; // Initialize Speech Recognition if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognition = new SpeechRecognition(); recognition.lang = 'pt-BR'; recognition.continuous = false; recognition.interimResults = false; } // DOM Elements const chatContainer = document.getElementById('chatContainer'); const messageForm = document.getElementById('messageForm'); const messageInput = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); const voiceModal = document.getElementById('voiceModal'); const voiceText = document.getElementById('voiceText'); // Knowledge Base const knowledgeBase = { greetings: { patterns: ['oi', 'olá', 'ola', 'hey', 'e aí', 'e ai', 'bom dia', 'boa tarde', 'boa noite', 'salve'], responses: [ 'Oi! Como posso ajudar você hoje? 😊', 'Olá! Que bom ver você por aqui! ✨', 'Hey! Pronto para ajudar! 🚀', 'Bem-vindo! O que posso fazer por você?' ] }, time: { patterns: ['hora', 'horas', 'que horas', 'horário', 'horario'], responses: () => { const now = new Date(); const timeString = now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); return `🕐 Agora são ${timeString}`; } }, date: { patterns: ['data', 'data de hoje', 'que dia é hoje', 'dia de hoje'], responses: () => { const now = new Date(); const dateString = now.toLocaleDateString('pt-BR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); return `📅 Hoje é ${dateString}`; } }, weather: { patterns: ['clima', 'tempo', 'previsão', 'previsao', 'temperatura', 'está frio', 'está calor', 'ta frio', 'ta calor'], responses: [ '🌤️ Não tenho acesso a dados meteorológicos em tempo real, mas posso sugerir que você verifique um app de clima confiável!', '☀️ Infelizmente não consigo verificar o clima agora. Recomendo olhar o Weather.com ou o app do seu celular.' ] }, jokes: { patterns: ['piada', 'conta uma piada', 'me faça rir', 'me faz rir', 'algo engraçado'], responses: [ '😄 Por que o computador foi ao médico? Porque estava com vírus! 🦠', '😂 O que o pato disse para a pata? Vem quá! 🦆', '🤣 Qual é o animal mais antigo? A zebra, porque está em preto e branco! 🦓', '😅 Por que o livro de matemática se suicidou? Porque tinha muitos problemas! 📚', '🤪 O que é um pontinho amarelo no alto de um prédio? Uma ervilha com vertigem! 🟡' ] }, capital: { patterns: ['capital', 'capital da', 'qual a capital'], responses: { 'frança': '🇫🇷 A capital da França é Paris, também conhecida como Cidade Luz!', 'brasil': '🇧🇷 A capital do Brasil é Brasília, inaugurada em 1960!', 'japão': '🇯🇵 A capital do Japão é Tóquio, a maior metrópole do mundo!', 'estados unidos': '🇺🇸 A capital dos Estados Unidos é Washington, D.C.!', 'argentina': '🇦🇷 A capital da Argentina é Buenos Aires!', 'portugal': '🇵🇹 A capital de Portugal é Lisboa!', 'espanha': '🇪🇸 A capital da Espanha é Madri!', 'itália': '🇮🇹 A capital da Itália é Roma!', 'alemanha': '🇩🇪 A capital da Alemanha é Berlim!', 'canadá': '🇨🇦 A capital do Canadá é Ottawa!' } }, thanks: { patterns: ['obrigado', 'obrigada', 'valeu', 'agradeço', 'thanks', 'ty'], responses: [ 'Por nada! 😊 Estou aqui para ajudar!', 'De nada! Fico feliz em poder ajudar! ✨', 'Não há de quê! Precisar de mais alguma coisa? 🙌' ] }, calculator: { patterns: ['calcular', 'quanto é', 'resultado de', '+', '-', '*', '/', 'x'], responses: (message) => { try { // Remove non-math characters and evaluate const cleanExpression = message.replace(/[^0-9+\-*/().\s]/g, '').replace(/x/g, '*'); if (!cleanExpression) return null; const result = Function('"use strict"; return (' + cleanExpression + ')')(); return `🧮 O resultado de ${cleanExpression.replace('*', '×')} é ${result}`; } catch { return null; } } }, name: { patterns: ['seu nome', 'como se chama', 'quem é você', 'quem e voce', 'qual seu nome'], responses: [ 'Eu sou a Nova, sua assistente virtual! 🤖✨', 'Me chamo Nova! Fui criada para ajudar você com o que precisar!', 'Olá! Eu sou Nova, prazer em conhecê-lo! 💜' ] }, help: { patterns: ['ajuda', 'help', 'o que você faz', 'o que voce faz', 'capacidades'], responses: [ '🛠️ **O que posso fazer:**\n\n• 🕐 Informar data e hora\n• 🌍 Responder sobre capitais\n• 😄 Contar piadas\n• 🧮 Fazer cálculos simples\n• 💬 Conversar sobre diversos assuntos\n\n**Tente perguntar:**\n• "Que horas são?"\n• "Qual a capital do Japão?"\n• "Quanto é 25 x 4?"' ] }, music: { patterns: ['música', 'musica', 'tocar', 'spotify', 'youtube music'], responses: [ '🎵 Não consigo tocar música diretamente, mas posso sugerir que você use Spotify, YouTube Music ou Apple Music!', '🎧 Para ouvir música, recomendo abrir seu app de streaming favorito!' ] }, news: { patterns: ['notícias', 'noticias', 'últimas notícias', 'novidades'], responses: [ '📰 Não tenho acesso a notícias em tempo real. Sugiro verificar sites como G1, Folha, ou Estadão para informações atualizadas!' ] }, motivation: { patterns: ['motivação', 'motivacao', 'ânimo', 'animo', 'triste', 'deprimido', 'incentivo'], responses: [ '💪 "O único limite para o nosso crescimento é a nossa própria determinação."\n\nVocê é mais forte do que imagina! ✨', '🌟 "Não importa quantas vezes você caia, o que importa é quantas vezes você se levanta."\n\nVai dar tudo certo! 💜', '⭐ "Acredite em você mesmo e tudo será possível!"\n\nVocê tem potencial para conquistar grandes coisas! 🚀' ] } }; // Auto-resize textarea function autoResize(textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px'; } // Handle Enter key function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); messageForm.dispatchEvent(new Event('submit')); } } // Send suggestion function sendSuggestion(text) { messageInput.value = text; messageForm.dispatchEvent(new Event('submit')); } // Create message element function createMessageElement(text, isUser = false) { const wrapper = document.createElement('div'); wrapper.className = `flex ${isUser ? 'justify-end' : 'justify-start'} message-bubble`; const content = document.createElement('div'); content.className = `max-w-[80%] md:max-w-[70%] px-4 py-3 rounded-2xl ${ isUser ? 'gradient-bg text-white rounded-br-md' : 'bg-white border border-gray-100 text-gray-800 rounded-bl-md shadow-sm' }`; // Parse markdown-like formatting const formattedText = text .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\n/g, '
'); content.innerHTML = formattedText; wrapper.appendChild(content); return wrapper; } // Create typing indicator function createTypingIndicator() { const wrapper = document.createElement('div'); wrapper.id = 'typingIndicator'; wrapper.className = 'flex justify-start message-bubble'; const content = document.createElement('div'); content.className = 'bg-white border border-gray-100 px-4 py-3 rounded-2xl rounded-bl-md shadow-sm'; content.innerHTML = `
`; wrapper.appendChild(content); return wrapper; } // Process message and generate response function processMessage(message) { const lowerMessage = message.toLowerCase(); // Check knowledge base for (const category of Object.values(knowledgeBase)) { const matched = category.patterns.some(pattern => lowerMessage.includes(pattern)); if (matched) { // Special handlers if (category === knowledgeBase.calculator) { const result = category.responses(message); if (result) return result; } if (category === knowledgeBase.capital) { for (const [country, response] of Object.entries(category.responses)) { if (lowerMessage.includes(country)) { return response; } } return 'Posso ajudar com capitais! Tente perguntar sobre: França, Brasil, Japão, Estados Unidos, Argentina, Portugal, Espanha, Itália, Alemanha ou Canadá! 🌍'; } if (typeof category.responses === 'function') { return category.responses(); } // Random response from array if (Array.isArray(category.responses)) { return category.responses[Math.floor(Math.random() * category.responses.length)]; } } } // Default responses const defaults = [ '🤔 Interessante! Me conte mais sobre isso.', '💭 Não tenho certeza se entendi completamente. Pode reformular?', '🌟 Ótimo ponto! O que mais você gostaria de saber?', '💡 Estou aprendendo todos os dias! Pode me dar mais detalhes?', '🎯 Entendo! E como posso ajudar com isso?' ]; return defaults[Math.floor(Math.random() * defaults.length)]; } // Send message async function sendMessage(message) { if (!message.trim() || isTyping) return; // Add user message chatContainer.appendChild(createMessageElement(message, true)); chatContainer.scrollTop = chatContainer.scrollHeight; // Save to history messageHistory.push({ role: 'user', content: message }); // Clear input messageInput.value = ''; messageInput.style.height = 'auto'; // Show typing indicator isTyping = true; sendBtn.disabled = true; const typingIndicator = createTypingIndicator(); chatContainer.appendChild(typingIndicator); chatContainer.scrollTop = chatContainer.scrollHeight; // Simulate processing time await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000)); // Remove typing indicator typingIndicator.remove(); // Generate and show response const response = processMessage(message); chatContainer.appendChild(createMessageElement(response, false)); chatContainer.scrollTop = chatContainer.scrollHeight; // Save to history messageHistory.push({ role: 'assistant', content: response }); // Reset state isTyping = false; sendBtn.disabled = false; } // Voice input function startVoiceInput() { if (!recognition) { alert('Seu navegador não suporta reconhecimento de voz. Tente usar Chrome ou Edge.'); return; } voiceModal.classList.remove('opacity-0', 'pointer-events-none'); voiceModal.querySelector('div').classList.remove('scale-90'); recognition.onresult = (event) => { const transcript = event.results[0][0].transcript; voiceText.textContent = `"${transcript}"`; setTimeout(() => { stopVoiceInput(); messageInput.value = transcript; messageForm.dispatchEvent(new Event('submit')); }, 500); }; recognition.onerror = () => { voiceText.textContent = 'Não entendi. Tente novamente.'; }; recognition.start(); } function stopVoiceInput() { if (recognition) recognition.stop(); voiceModal.classList.add('opacity-0', 'pointer-events-none'); voiceModal.querySelector('div').classList.add('scale-90'); voiceText.textContent = 'Fale algo'; } // Clear chat function clearChat() { if (confirm('Deseja limpar toda a conversa?')) { // Keep welcome message and suggestions const welcomeSection = chatContainer.querySelector('.text-center'); const suggestions = chatContainer.querySelector('.flex.flex-wrap, .flex.overflow-x-auto'); chatContainer.innerHTML = ''; if (welcomeSection) chatContainer.appendChild(welcomeSection); if (suggestions) chatContainer.appendChild(suggestions); messageHistory = []; } } // Theme management let isDarkMode = false; function initTheme() { // Check saved theme or system preference const savedTheme = localStorage.getItem('nova-theme'); if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) { enableDarkMode(); } } function enableDarkMode() { isDarkMode = true; document.documentElement.classList.add('dark'); localStorage.setItem('nova-theme', 'dark'); updateThemeIcons(); } function disableDarkMode() { isDarkMode = false; document.documentElement.classList.remove('dark'); localStorage.setItem('nova-theme', 'light'); updateThemeIcons(); } function toggleTheme() { if (isDarkMode) { disableDarkMode(); } else { enableDarkMode(); } } function updateThemeIcons() { // Update desktop icon const desktopIcon = document.getElementById('themeIconDesktop'); if (desktopIcon) { desktopIcon.setAttribute('data-lucide', isDarkMode ? 'sun' : 'moon'); lucide.createIcons(); } // Update mobile icon and text const mobileIcon = document.getElementById('themeIconMobile'); const mobileText = document.getElementById('themeTextMobile'); if (mobileIcon) { mobileIcon.setAttribute('data-lucide', isDarkMode ? 'sun' : 'moon'); mobileIcon.className = isDarkMode ? 'w-5 h-5 text-yellow-500' : 'w-5 h-5 text-purple-500'; lucide.createIcons(); } if (mobileText) { mobileText.textContent = isDarkMode ? 'Modo claro' : 'Modo escuro'; } } // Initialize theme on load initTheme(); // Export chat (placeholder) function exportChat() { const chatText = messageHistory.map(m => `${m.role === 'user' ? 'Você' : 'Nova'}: ${m.content}`).join('\n\n'); if (!chatText) { alert('Não há mensagens para exportar!'); return; } // Try to use share API on mobile if (navigator.share) { navigator.share({ title: 'Conversa com Nova Assistant', text: chatText }).catch(() => {}); } else { // Fallback: copy to clipboard navigator.clipboard.writeText(chatText).then(() => { alert('Conversa copiada para a área de transferência!'); }); } } // Mobile menu functions function showMobileMenu() { const menu = document.getElementById('mobileMenu'); const sheet = menu.querySelector('.absolute.bottom-0'); menu.classList.remove('pointer-events-none', 'opacity-0'); menu.classList.add('pointer-events-auto', 'opacity-100'); setTimeout(() => { sheet.classList.remove('translate-y-full'); }, 10); } function hideMobileMenu() { const menu = document.getElementById('mobileMenu'); const sheet = menu.querySelector('.absolute.bottom-0'); sheet.classList.add('translate-y-full'); setTimeout(() => { menu.classList.add('pointer-events-none', 'opacity-0'); menu.classList.remove('pointer-events-auto', 'opacity-100'); }, 300); } // Toggle quick actions on mobile function toggleQuickActions() { const qa = document.getElementById('quickActions'); qa.classList.toggle('hidden'); } // Check input and enable/disable send button function checkInput() { const sendBtn = document.getElementById('sendBtn'); sendBtn.disabled = !messageInput.value.trim(); } // Event listeners messageForm.addEventListener('submit', (e) => { e.preventDefault(); if (messageInput.value.trim()) { sendMessage(messageInput.value); checkInput(); } }); // Focus input on load (desktop only) if (window.innerWidth > 640) { messageInput.focus(); } // Prevent zoom on iOS when focusing input document.addEventListener('gesturestart', function(e) { e.preventDefault(); });