KEXEL's picture
🐳 27/02 - 21:16 - tem como colocar Modo escuro
a543698 verified
// 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, '<strong>$1</strong>')
.replace(/\n/g, '<br>');
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 = `
<div class="flex gap-1 items-center h-5">
<span class="typing-dot w-2 h-2 bg-purple-400 rounded-full"></span>
<span class="typing-dot w-2 h-2 bg-purple-400 rounded-full"></span>
<span class="typing-dot w-2 h-2 bg-purple-400 rounded-full"></span>
</div>
`;
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();
});