diff --git a/api/chat.php b/api/chat.php new file mode 100644 index 0000000..8953d32 --- /dev/null +++ b/api/chat.php @@ -0,0 +1,101 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +try { + $pdo = db(); + $user_id = $_SESSION['user_id']; + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $input = json_decode(file_get_contents('php://input'), true); + $message = $input['message'] ?? ''; + $context = $input['context'] ?? null; // New: get context + + if (empty(trim($message))) { + echo json_encode(['success' => false, 'error' => 'Message cannot be empty.']); + exit; + } + + // New: Handle simulation context + if ($context === 'vpn' || $context === 'antivirus') { + $ai_input = [['role' => 'user', 'content' => $message]]; + $ai_response = LocalAIApi::createResponse(['input' => $ai_input]); + $ai_message = LocalAIApi::extractText($ai_response); + + if (!empty($ai_message)) { + echo json_encode(['success' => true, 'data' => $ai_message]); + } else { + echo json_encode(['success' => false, 'error' => 'Failed to get AI response.']); + } + exit; // Stop execution for simulations + } + + // --- Existing Chat Logic --- + $personality = $input['personality'] ?? ''; + + $stmt = $pdo->prepare("INSERT INTO messages (user_id, message) VALUES (?, ?)"); + $stmt->execute([$user_id, $message]); + + // --- AI CONTEXT PREPARATION --- + + // 1. Prepare the system prompt (personality) + $final_ai_input = []; + if (!empty(trim($personality))) { + $final_ai_input[] = ['role' => 'system', 'content' => $personality]; + } + + // 2. Get last 5 messages for conversation history + $stmt = $pdo->query("SELECT m.message, u.username FROM messages m JOIN users u ON m.user_id = u.id ORDER BY m.created_at DESC LIMIT 5"); + $recent_messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + $conversation_history = array_map(function($msg) { + return ['role' => $msg['username'] === 'AI' ? 'assistant' : 'user', 'content' => $msg['message']]; + }, array_reverse($recent_messages)); + + // 3. Combine system prompt with conversation history + $final_ai_input = array_merge($final_ai_input, $conversation_history); + + // --- CALL THE AI --- + $ai_response = LocalAIApi::createResponse(['input' => $final_ai_input]); + $ai_message = LocalAIApi::extractText($ai_response); + + if (!empty($ai_message)) { + $stmt = $pdo->prepare("SELECT id FROM users WHERE username = 'AI'"); + $stmt->execute(); + $ai_user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($ai_user) { + $stmt = $pdo->prepare("INSERT INTO messages (user_id, message) VALUES (?, ?)"); + $stmt->execute([$ai_user['id'], $ai_message]); + } + } + + echo json_encode(['success' => true, 'message' => 'Message saved.']); + + } elseif ($_SERVER['REQUEST_METHOD'] === 'GET') { + if (isset($_GET['action']) && $_GET['action'] === 'reset') { + // Truncate the messages table to reset the chat + $pdo->query("TRUNCATE TABLE messages"); + echo json_encode(['success' => true, 'message' => 'Chat history has been cleared.']); + } else { + $stmt = $pdo->query("SELECT m.id, m.message, m.created_at, u.username, LEFT(u.username, 1) as user_initial FROM messages m JOIN users u ON m.user_id = u.id ORDER BY m.created_at ASC"); + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['success' => true, 'messages' => $messages, 'currentUser' => $_SESSION['username']]); + } + } else { + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Method Not Allowed']); + } + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..73c7c1a --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,235 @@ +:root { + --primary-color: #007bff; + --secondary-color: #6c757d; + --light-color: #f8f9fa; + --dark-color: #343a40; + --body-bg: #f4f7f6; + --card-bg: #ffffff; + --sent-bg: #007bff; + --received-bg: #e9ecef; + --font-family: 'Poppins', sans-serif; +} + +body { + background-color: var(--body-bg); + font-family: var(--font-family); + padding-top: 56px; +} + +.navbar { + box-shadow: 0 2px 4px rgba(0,0,0,.05); +} + +.btn-primary { + background-color: var(--primary-color); + border-color: var(--primary-color); + transition: all 0.3s ease; +} + +.btn-primary:hover { + opacity: 0.9; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.1); +} + +/* Chat Page Styles */ +.chat-container { + padding-top: 2rem; + padding-bottom: 2rem; +} + +#chat-card { + border-radius: 1rem; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); + height: 80vh; + display: flex; + flex-direction: column; + background-color: var(--card-bg); +} + +#chat-box { + flex-grow: 1; + overflow-y: auto; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + display: flex; + align-items: flex-end; + max-width: 75%; + gap: 0.75rem; +} + +.avatar { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 500; + color: white; + flex-shrink: 0; +} + +.message-body { + padding: 0.75rem 1.25rem; + border-radius: 1.5rem; + position: relative; + box-shadow: 0 2px 5px rgba(0,0,0,0.05); + line-height: 1.6; +} + +.message-text { + margin-bottom: 0; +} + +.message-time { + font-size: 0.7rem; + color: #adb5bd; + display: block; + margin-top: 0.25rem; +} + +.message.sent { + align-self: flex-end; + flex-direction: row-reverse; +} + +.message.sent .message-body { + background: var(--sent-bg); + color: white; + border-radius: 1.5rem 1.5rem 0.5rem 1.5rem; +} + +.message.sent .message-time { + color: var(--light-color); + opacity: 0.8; + text-align: right; +} + +.message.received { + align-self: flex-start; +} + +.message.received .message-body { + background-color: var(--received-bg); + color: var(--dark-color); + border-radius: 1.5rem 1.5rem 1.5rem 0.5rem; +} + +.message.received .avatar { + background-color: var(--secondary-color); +} + +#message-form .form-control { + border-radius: 2rem; + border: 1px solid #ced4da; + padding: 0.75rem 1.25rem; +} + +#message-form .form-control:focus { + box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25); + border-color: var(--primary-color); +} + +#send-button { + border-radius: 2rem; +} + +/* Typing Indicator Styles */ +.typing-indicator-container { + padding: 0.5rem 1.5rem; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.typing-indicator { + display: flex; + align-items: center; + background-color: var(--received-bg); + border-radius: 1.5rem; + padding: 0.75rem 1.25rem; +} + +.typing-indicator span { + height: 8px; + width: 8px; + background-color: #999; + border-radius: 50%; + display: inline-block; + margin: 0 2px; + animation: typing 1.2s infinite ease-in-out; +} + +.typing-indicator span:nth-of-type(2) { + animation-delay: 0.2s; +} + +.typing-indicator span:nth-of-type(3) { + animation-delay: 0.4s; +} + +@keyframes typing { + 0%, 100% { + transform: translateY(0); + opacity: 0.4; + } + 50% { + transform: translateY(-6px); + opacity: 1; + } +} + +.message.error .message-body { + background: #dc3545a1; +} + +/* Security Page Styles */ +.security-tool-card { + background-color: var(--card-bg); + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); + height: 100%; + display: flex; + flex-direction: column; +} + +.security-tool-card h5 { + font-weight: 600; + margin-bottom: 1rem; +} + +.security-tool-card p { + color: var(--secondary-color); + flex-grow: 1; +} + +.security-tool-card .form-control { + border-radius: 0.5rem; +} + +#vpn-status p, +#antivirus-result p { + padding: 1rem; + border-radius: 0.5rem; + background-color: var(--light-color); + margin-bottom: 0; + transition: all 0.3s ease; +} + +#vpn-status p.connecting, +#antivirus-result p.scanning { + background-color: #fff3cd; /* Bootstrap warning yellow */ + color: #664d03; +} + +#vpn-status p.connected { + background-color: #d1e7dd; /* Bootstrap success green */ + color: #0f5132; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..7b2d5a3 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,280 @@ +document.addEventListener('DOMContentLoaded', function() { + const messageForm = document.getElementById('message-form'); + const messageInput = document.getElementById('message-input'); + const chatBox = document.getElementById('chat-box'); + const typingIndicator = document.getElementById('typing-indicator'); + + let currentUser = ''; + let lastAnimatedMessageId = null; + + function showTypingIndicator() { + if (typingIndicator) { + typingIndicator.style.display = 'block'; + chatBox.scrollTop = chatBox.scrollHeight; + } + } + + function hideTypingIndicator() { + if (typingIndicator) { + typingIndicator.style.display = 'none'; + } + } + + function createAvatar(message) { + const avatar = document.createElement('div'); + avatar.classList.add('avatar'); + const initial = message.user_initial || (message.username === 'AI' ? 'A' : '?'); + avatar.textContent = initial; + // Add a color based on the username + const colors = ['#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3', '#03a9f4', '#00bcd4', '#009688', '#4caf50', '#8bc34a', '#cddc39', '#ffeb3b', '#ffc107', '#ff9800', '#ff5722', '#795548', '#607d8b']; + const colorIndex = message.username.charCodeAt(0) % colors.length; + avatar.style.backgroundColor = colors[colorIndex]; + return avatar; + } + + function renderMessage(message) { + const isSent = message.username === currentUser; + if (isSent || message.username === 'AI') { + hideTypingIndicator(); + } + + const messageElement = document.createElement('div'); + messageElement.classList.add('message', isSent ? 'sent' : 'received'); + messageElement.dataset.messageId = message.id; + + const avatar = createAvatar(message); + messageElement.appendChild(avatar); + + const messageBody = document.createElement('div'); + messageBody.classList.add('message-body'); + + if (!isSent) { + const usernameElement = document.createElement('div'); + usernameElement.classList.add('message-username'); + usernameElement.textContent = message.username; + messageBody.appendChild(usernameElement); + } + + const messageP = document.createElement('p'); + messageP.classList.add('message-text'); + messageP.textContent = message.message; + + const messageTime = document.createElement('span'); + messageTime.classList.add('message-time'); + const time = new Date(message.created_at); + messageTime.textContent = time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + + messageBody.appendChild(messageP); + messageBody.appendChild(messageTime); + messageElement.appendChild(messageBody); + chatBox.appendChild(messageElement); + + chatBox.scrollTop = chatBox.scrollHeight; + } + + function renderMessageWithStreaming(message) { + renderMessage({ ...message, message: '' }); // Render the container first + const messageP = chatBox.querySelector(`[data-message-id="${message.id}"] .message-text`); + if (!messageP) return; + + const words = message.message.split(' '); + let i = 0; + const interval = setInterval(() => { + if (i < words.length) { + messageP.textContent += (i > 0 ? ' ' : '') + words[i]; + chatBox.scrollTop = chatBox.scrollHeight; + i++; + } else { + clearInterval(interval); + } + }, 100); + } + + async function fetchMessages() { + try { + const response = await fetch('/api/chat.php'); + if (!response.ok) { + if (response.status === 401) window.location.href = 'login.php'; + throw new Error('Network response was not ok'); + } + const data = await response.json(); + if (data.success) { + currentUser = data.currentUser; + const messages = data.messages || []; + + const existingMessageIds = new Set([...chatBox.querySelectorAll('.message')].map(m => m.dataset.messageId)); + + messages.forEach(message => { + if (!existingMessageIds.has(message.id.toString())) { + const isAI = message.username === 'AI'; + const isNewer = message.id > lastAnimatedMessageId; + + if (isAI && isNewer) { + lastAnimatedMessageId = message.id; + renderMessageWithStreaming(message); + } else { + renderMessage(message); + } + } + }); + + if (messages.length > 0) { + const lastMessage = messages[messages.length - 1]; + if (lastMessage.username !== 'AI') { + hideTypingIndicator(); + } + } + + } + } catch (error) { + console.error('Error fetching messages:', error); + hideTypingIndicator(); + } + } + + async function resetChat() { + try { + const response = await fetch('/api/chat.php?action=reset'); + const result = await response.json(); + if (result.success) { + chatBox.innerHTML = ''; + } else { + console.error('Error resetting chat:', result.error); + } + } catch (error) { + console.error('Error resetting chat:', error); + } + } + + if (chatBox) { + fetchMessages(); + setInterval(fetchMessages, 3000); + + if (messageForm) { + messageForm.addEventListener('submit', async function(e) { + e.preventDefault(); + const messageText = messageInput.value.trim(); + if (messageText === '/reset') { + messageInput.value = ''; + await resetChat(); + return; + } + + if (messageText !== '') { + messageInput.value = ''; + messageInput.focus(); + showTypingIndicator(); + + // Optimistically render user's message + const tempId = `temp_${new Date().getTime()}`; + renderMessage({ + id: tempId, + username: currentUser, + message: messageText, + created_at: new Date().toISOString(), + user_initial: currentUser.charAt(0).toUpperCase() + }); + + const personality = document.getElementById('ai-personality').value.trim(); + + try { + const response = await fetch('/api/chat.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: messageText, personality: personality }) + }); + const result = await response.json(); + if (!result.success) { + console.error('Error saving message:', result.error); + // Optionally remove the optimistic message or show an error + const failedMsg = chatBox.querySelector(`[data-message-id="${tempId}"]`); + if(failedMsg) failedMsg.classList.add('error'); + } + // The poller will fetch the confirmed message from the server + } catch (error) { + console.error('Error sending message:', error); + hideTypingIndicator(); + const failedMsg = chatBox.querySelector(`[data-message-id="${tempId}"]`); + if(failedMsg) failedMsg.classList.add('error'); + } + } + }); + } + } + + // Security Panel Logic + const vpnConnectBtn = document.getElementById('vpn-connect-btn'); + const vpnStatus = document.getElementById('vpn-status'); + const vpnCountrySelect = document.getElementById('vpn-country'); + + const antivirusScanBtn = document.getElementById('antivirus-scan-btn'); + const antivirusResult = document.getElementById('antivirus-result'); + + let isVpnConnected = false; + + if (vpnConnectBtn) { + vpnConnectBtn.addEventListener('click', async () => { + isVpnConnected = !isVpnConnected; + + if (isVpnConnected) { + const selectedCountry = vpnCountrySelect.value; + vpnConnectBtn.textContent = 'Desconectar'; + vpnConnectBtn.classList.remove('btn-primary'); + vpnConnectBtn.classList.add('btn-danger'); + vpnStatus.innerHTML = '
Conectando...
'; + + const prompt = `Simule que você está estabelecendo uma conexão VPN para o país ${selectedCountry}. Descreva o processo e, ao final, confirme a conexão segura e forneça um endereço de IP fictício para esse país.`; + + try { + const response = await fetch('/api/chat.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: prompt, context: 'vpn' }) // Add context for the API + }); + const result = await response.json(); + if (result.success && result.data) { + vpnStatus.innerHTML = `${result.data.replace(/\n/g, '
')}
Falha ao conectar. Tente novamente.
'; + } + } catch (error) { + console.error('VPN connection error:', error); + vpnStatus.innerHTML = 'Ocorreu um erro na simulação.
'; + } + + } else { + vpnConnectBtn.textContent = 'Conectar'; + vpnConnectBtn.classList.remove('btn-danger'); + vpnConnectBtn.classList.add('btn-primary'); + vpnStatus.innerHTML = 'Status: Desconectado
'; + } + }); + } + + if (antivirusScanBtn) { + antivirusScanBtn.addEventListener('click', async () => { + antivirusScanBtn.disabled = true; + antivirusResult.innerHTML = 'Verificando sistema... Isso pode levar um momento.
'; + + const prompt = `Simule uma verificação completa de um sistema de computador em busca de vírus e malware. Descreva as etapas da verificação (ex: verificação de memória, arquivos de inicialização, registro, arquivos do sistema). Ao final, apresente um relatório com os resultados, informando se alguma ameaça foi encontrada ou se o sistema está limpo.`; + + try { + const response = await fetch('/api/chat.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: prompt, context: 'antivirus' }) // Add context for the API + }); + const result = await response.json(); + if (result.success && result.data) { + antivirusResult.innerHTML = `${result.data.replace(/\n/g, '
')}
A verificação falhou. Tente novamente.
'; + } catch (error) { + console.error('Antivirus scan error:', error); + antivirusResult.innerHTML = 'Ocorreu um erro na simulação da verificação.
'; + } finally { + antivirusScanBtn.disabled = false; + } + }); + } +}); \ No newline at end of file diff --git a/chat.php b/chat.php new file mode 100644 index 0000000..d246aa8 --- /dev/null +++ b/chat.php @@ -0,0 +1,49 @@ + + +