// /home/Autopartes/dashboard/chat-public.js // Public catalog chatbot — voice + TTS enabled (function () { 'use strict'; var isOpen = false; var isSending = false; var isListening = false; var recognition = null; var history = []; var ttsEnabled = true; var ttsUtterance = null; var hasSpeechAPI = ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window); var hasTTS = ('speechSynthesis' in window); function init() { var fab = document.createElement('button'); fab.className = 'chat-fab'; fab.id = 'chatFab'; fab.title = 'Asistente IA'; fab.innerHTML = '💬'; fab.setAttribute('aria-label', 'Abrir asistente IA'); var panel = document.createElement('div'); panel.className = 'chat-panel'; panel.id = 'chatPanel'; panel.innerHTML = '
' + '

Asistente — Buscar partes

' + '
' + (hasTTS ? '' : '') + '' + '
' + '
' + '
' + '
Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.
' + '
' + '
' + '
' + '' + (hasSpeechAPI ? '' : '') + '' + '
'; document.body.appendChild(fab); document.body.appendChild(panel); fab.addEventListener('click', toggleChat); document.getElementById('chatClose').addEventListener('click', toggleChat); document.getElementById('chatSend').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); document.getElementById('chatInput').addEventListener('input', function () { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 80) + 'px'; }); if (hasSpeechAPI) { document.getElementById('chatMic').addEventListener('click', toggleVoice); } if (hasTTS) { document.getElementById('chatTtsToggle').addEventListener('click', toggleTTS); } // Stop TTS when closing chat document.getElementById('chatClose').addEventListener('click', function () { if (hasTTS) stopSpeaking(); }); } // ─── TTS ─── function toggleTTS() { ttsEnabled = !ttsEnabled; var btn = document.getElementById('chatTtsToggle'); if (btn) { btn.classList.toggle('off', !ttsEnabled); btn.setAttribute('title', ttsEnabled ? 'Desactivar lectura de respuestas' : 'Activar lectura de respuestas'); } if (!ttsEnabled) stopSpeaking(); } function speak(text) { if (!hasTTS || !ttsEnabled || !text) return; stopSpeaking(); ttsUtterance = new SpeechSynthesisUtterance(text); ttsUtterance.lang = 'es-MX'; ttsUtterance.rate = 1.1; ttsUtterance.pitch = 1; window.speechSynthesis.speak(ttsUtterance); } function stopSpeaking() { if (hasTTS && window.speechSynthesis.speaking) { window.speechSynthesis.cancel(); } ttsUtterance = null; } // ─── Voice Input ─── function toggleVoice() { if (isListening) { stopVoice(); return; } startVoice(); } function startVoice() { var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) return; recognition = new SpeechRecognition(); recognition.lang = 'es-MX'; recognition.continuous = false; recognition.interimResults = true; var input = document.getElementById('chatInput'); var micBtn = document.getElementById('chatMic'); var savedPlaceholder = input.placeholder; recognition.onstart = function () { isListening = true; micBtn.classList.add('listening'); input.placeholder = 'Escuchando...'; input.value = ''; stopSpeaking(); }; recognition.onresult = function (e) { var interim = ''; var finalTranscript = ''; for (var i = e.resultIndex; i < e.results.length; i++) { if (e.results[i].isFinal) { finalTranscript += e.results[i][0].transcript; } else { interim += e.results[i][0].transcript; } } if (finalTranscript) { input.value = finalTranscript; } else { input.value = interim; } }; recognition.onend = function () { isListening = false; micBtn.classList.remove('listening'); input.placeholder = savedPlaceholder; recognition = null; if (input.value.trim()) { sendMessage(); } }; recognition.onerror = function (e) { isListening = false; micBtn.classList.remove('listening'); input.placeholder = savedPlaceholder; recognition = null; if (e.error === 'no-speech' || e.error === 'audio-capture' || e.error === 'not-allowed') { showVoiceToast('No se detecto voz'); } }; recognition.start(); } function stopVoice() { if (recognition) { recognition.abort(); recognition = null; } isListening = false; var micBtn = document.getElementById('chatMic'); if (micBtn) micBtn.classList.remove('listening'); } function showVoiceToast(msg) { var toast = document.createElement('div'); toast.className = 'chat-voice-toast'; toast.textContent = msg; document.body.appendChild(toast); setTimeout(function () { toast.classList.add('visible'); }, 10); setTimeout(function () { toast.classList.remove('visible'); setTimeout(function () { toast.remove(); }, 300); }, 2000); } // ─── Chat UI ─── function toggleChat() { isOpen = !isOpen; var panel = document.getElementById('chatPanel'); var fab = document.getElementById('chatFab'); if (isOpen) { panel.classList.add('open'); fab.style.display = 'none'; document.getElementById('chatInput').focus(); } else { panel.classList.remove('open'); fab.style.display = 'flex'; } } function sendMessage() { if (isSending) return; var input = document.getElementById('chatInput'); var text = input.value.trim(); if (!text) return; input.value = ''; input.style.height = 'auto'; addBubble(text, 'user'); history.push({ role: 'user', content: text }); if (history.length > 20) history.splice(0, 2); isSending = true; document.getElementById('chatSend').disabled = true; showTyping(true); fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text, history: history.slice(-10) }) }) .then(function (resp) { return resp.json(); }) .then(function (data) { if (data.error) { addBubble('Error: ' + data.error, 'ai'); return; } var aiMsg = data.response || 'Sin respuesta.'; addBubble(aiMsg, 'ai'); history.push({ role: 'assistant', content: aiMsg }); if (ttsEnabled) speak(aiMsg); if (data.search_results && data.search_results.length > 0) { addPartResults(data.search_results); } }) .catch(function (err) { addBubble('Error de conexion: ' + err.message, 'ai'); }) .finally(function () { isSending = false; document.getElementById('chatSend').disabled = false; showTyping(false); }); } function addBubble(text, role) { var container = document.getElementById('chatMessages'); var typing = document.getElementById('chatTyping'); var div = document.createElement('div'); div.className = 'chat-msg ' + role; div.textContent = text; container.insertBefore(div, typing); scrollToBottom(); } function addPartResults(parts) { var container = document.getElementById('chatMessages'); var typing = document.getElementById('chatTyping'); var wrapper = document.createElement('div'); wrapper.className = 'chat-parts'; parts.slice(0, 8).forEach(function (p) { var card = document.createElement('div'); card.className = 'chat-part-card'; var name = p.name_es || p.name_part || ''; var partNum = p.oem_part_number || p.part_number || ''; var brand = p.brand || ''; card.innerHTML = '
' + esc(partNum) + '
' + '
' + esc(name) + (brand ? ' (' + esc(brand) + ')' : '') + '
'; card.style.cursor = 'pointer'; card.addEventListener('click', function () { var searchInput = document.getElementById('searchInput'); if (searchInput && partNum) { searchInput.value = partNum; if (typeof window.doSearch === 'function') window.doSearch(); toggleChat(); } }); wrapper.appendChild(card); }); container.insertBefore(wrapper, typing); scrollToBottom(); } function showTyping(show) { var el = document.getElementById('chatTyping'); if (el) el.classList.toggle('visible', show); if (show) scrollToBottom(); } function scrollToBottom() { var el = document.getElementById('chatMessages'); if (el) el.scrollTop = el.scrollHeight; } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();