// Conectar con Socket.IO const socket = io(); // Elementos del DOM const messagesContainer = document.getElementById('messagesContainer'); const messagesWrapper = document.getElementById('messagesWrapper'); const chatForm = document.getElementById('chatForm'); const messageInput = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); const newChatBtn = document.getElementById('newChatBtn'); const newChatBtnMobile = document.getElementById('newChatBtnMobile'); const sidebar = document.getElementById('sidebar'); const mobileMenuBtn = document.getElementById('mobileMenuBtn'); const sidebarToggle = document.getElementById('sidebarToggle'); const welcomeMessage = document.getElementById('welcomeMessage'); // Estado let isTyping = false; let conversationId = null; // Inicialización function init() { // Event listeners chatForm.addEventListener('submit', handleSubmit); messageInput.addEventListener('input', handleInputChange); messageInput.addEventListener('keydown', handleKeyDown); newChatBtn.addEventListener('click', handleNewChat); if (newChatBtnMobile) { newChatBtnMobile.addEventListener('click', handleNewChat); } if (mobileMenuBtn) { mobileMenuBtn.addEventListener('click', toggleSidebar); } if (sidebarToggle) { sidebarToggle.addEventListener('click', toggleSidebar); } // Cerrar sidebar al hacer click fuera (mobile) document.addEventListener('click', handleClickOutside); // Suggestion cards setupSuggestionCards(); // Escuchar mensajes del servidor socket.on('message', handleIncomingMessage); socket.on('ai_response', handleAIResponse); socket.on('error', handleError); // Escuchar errores de conexión socket.on('connect_error', (error) => { console.error('Error de conexión:', error); showErrorMessage('Error de conexión con el servidor'); }); // Conectado exitosamente socket.on('connect', () => { console.log('Conectado al servidor'); }); // Auto-focus en el input messageInput.focus(); } // Toggle sidebar (mobile) function toggleSidebar() { sidebar.classList.toggle('open'); } // Cerrar sidebar al hacer click fuera function handleClickOutside(e) { if (window.innerWidth <= 768 && sidebar.classList.contains('open')) { if (!sidebar.contains(e.target) && !mobileMenuBtn.contains(e.target)) { sidebar.classList.remove('open'); } } } // Setup suggestion cards function setupSuggestionCards() { const suggestionCards = document.querySelectorAll('.suggestion-card'); suggestionCards.forEach(card => { card.addEventListener('click', () => { const suggestion = card.querySelector('h3').textContent; messageInput.value = suggestion; messageInput.focus(); handleInputChange(); }); }); } // Manejar envío de mensaje function handleSubmit(e) { e.preventDefault(); const message = messageInput.value.trim(); if (!message || isTyping) return; // Ocultar mensaje de bienvenida if (welcomeMessage) { welcomeMessage.style.display = 'none'; } // Mostrar mensaje del usuario inmediatamente addMessage('user', message); // Limpiar input messageInput.value = ''; adjustTextareaHeight(); sendBtn.disabled = true; // Mostrar indicador de escritura showTypingIndicator(); // Enviar mensaje al servidor socket.emit('user_message', { message, conversationId }); } // Manejar cambios en el input function handleInputChange() { adjustTextareaHeight(); const hasText = messageInput.value.trim() !== ''; sendBtn.disabled = !hasText || isTyping; } // Ajustar altura del textarea function adjustTextareaHeight() { messageInput.style.height = 'auto'; const newHeight = Math.min(messageInput.scrollHeight, 200); messageInput.style.height = newHeight + 'px'; } // Manejar teclas en el input function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(e); } } // Manejar nuevo chat function handleNewChat() { // Limpiar mensajes messagesContainer.innerHTML = ''; // Mostrar mensaje de bienvenida messagesContainer.innerHTML = `
`; // Re-setup suggestion cards setupSuggestionCards(); // Reset estado conversationId = null; isTyping = false; messageInput.value = ''; adjustTextareaHeight(); messageInput.focus(); // Cerrar sidebar en mobile if (window.innerWidth <= 768) { sidebar.classList.remove('open'); } } // Manejar mensaje entrante del servidor function handleIncomingMessage(data) { if (data.role === 'user') { addMessage('user', data.content); showTypingIndicator(); } } // Manejar respuesta de AI function handleAIResponse(data) { removeTypingIndicator(); addMessage('ai', data.content); if (data.conversationId) { conversationId = data.conversationId; } isTyping = false; handleInputChange(); } // Manejar errores function handleError(data) { removeTypingIndicator(); showErrorMessage(data.message || 'Ha ocurrido un error'); isTyping = false; handleInputChange(); } // Agregar mensaje al chat function addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const avatar = role === 'user' ? `` : ``; messageDiv.innerHTML = ` `; messagesContainer.appendChild(messageDiv); scrollToBottom(); } // Mostrar indicador de escritura function showTypingIndicator() { isTyping = true; const typingDiv = document.createElement('div'); typingDiv.className = 'typing-indicator'; typingDiv.id = 'typingIndicator'; typingDiv.innerHTML = ` `; messagesContainer.appendChild(typingDiv); scrollToBottom(); } // Remover indicador de escritura function removeTypingIndicator() { const typingIndicator = document.getElementById('typingIndicator'); if (typingIndicator) { typingIndicator.remove(); } } // Mostrar mensaje de error function showErrorMessage(error) { const errorDiv = document.createElement('div'); errorDiv.className = 'message system-error'; errorDiv.innerHTML = ` `; messagesContainer.appendChild(errorDiv); scrollToBottom(); } // Scroll al final del contenedor function scrollToBottom() { requestAnimationFrame(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight; }); } // Escapar HTML para prevenir XSS function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Formatear mensaje con markdown básico function formatMessage(text) { // Escapar HTML primero text = escapeHtml(text); // Convertir bloques de código ``` text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, '$2');
// Convertir **texto** a negrita
text = text.replace(/\*\*(.*?)\*\*/g, '$1');
// Convertir *texto* a cursiva
text = text.replace(/\*(.*?)\*/g, '$1');
// Convertir `código` a código inline
text = text.replace(/`(.*?)`/g, '$1');
// Convertir URLs a enlaces
text = text.replace(/(https?:\/\/[^\s]+)/g, '$1');
// Convertir saltos de línea a párrafos
text = text.split('\n\n').map(p => `${p.replace(/\n/g, '
')}