diff --git a/dashboard/chat-public.css b/dashboard/chat-public.css
index 35381ac..523f75a 100644
--- a/dashboard/chat-public.css
+++ b/dashboard/chat-public.css
@@ -1,5 +1,5 @@
/* ==========================================================================
- NEXUS — Public Catalog Chat Widget
+ NEXUS — Public Catalog Chat Widget (Voice + TTS enabled)
Reuses design tokens from tokens.css
========================================================================== */
@@ -228,6 +228,99 @@
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
+/* ─── Header Actions (TTS toggle + close) ─── */
+.chat-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.chat-tts-toggle {
+ background: none;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ cursor: pointer;
+ padding: 4px;
+ line-height: 1;
+ opacity: 0.9;
+ transition: opacity 0.15s ease;
+}
+
+.chat-tts-toggle:hover { opacity: 1; }
+.chat-tts-toggle.off { opacity: 0.4; }
+
+/* ─── Mic Button (Voice Input) ─── */
+.chat-mic-btn {
+ width: 38px;
+ height: 38px;
+ border-radius: 8px;
+ border: 1px solid var(--color-border, #333);
+ background: var(--color-bg-base, #111);
+ color: var(--color-text-secondary, #aaa);
+ font-size: 1.1rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.chat-mic-btn:hover {
+ border-color: var(--color-accent, #F5A623);
+ color: var(--color-accent, #F5A623);
+}
+
+.chat-mic-btn.listening {
+ background: #f85149;
+ border-color: #f85149;
+ color: #fff;
+ animation: micPulse 1.4s infinite;
+}
+
+@keyframes micPulse {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.4); }
+ 50% { box-shadow: 0 0 0 10px rgba(248, 81, 73, 0); }
+}
+
+/* ─── TTS Button ─── */
+.chat-tts-btn {
+ background: none;
+ border: none;
+ color: #8b949e;
+ font-size: 0.85rem;
+ cursor: pointer;
+ padding: 2px 4px;
+ margin-left: 6px;
+ border-radius: 4px;
+ transition: color 0.2s, background 0.2s;
+}
+.chat-tts-btn:hover { color: #58a6ff; background: rgba(88,166,255,0.1); }
+.chat-tts-btn.tts-active { color: #58a6ff; }
+
+/* ─── Voice Toast ─── */
+.chat-voice-toast {
+ position: fixed;
+ bottom: 160px;
+ left: 50%;
+ transform: translateX(-50%) translateY(10px);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 8px 18px;
+ border-radius: 8px;
+ font-size: 0.85rem;
+ z-index: 9999;
+ opacity: 0;
+ transition: opacity 0.3s, transform 0.3s;
+ pointer-events: none;
+}
+
+.chat-voice-toast.visible {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
@media (max-width: 480px) {
.chat-panel {
width: calc(100vw - 16px);
diff --git a/dashboard/chat-public.js b/dashboard/chat-public.js
index 6d9cb98..c3052e3 100644
--- a/dashboard/chat-public.js
+++ b/dashboard/chat-public.js
@@ -1,15 +1,20 @@
// /home/Autopartes/dashboard/chat-public.js
-// Public catalog chatbot — no auth required, calls /api/chat
+// 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() {
- // FAB button
var fab = document.createElement('button');
fab.className = 'chat-fab';
fab.id = 'chatFab';
@@ -17,14 +22,16 @@
fab.innerHTML = '💬';
fab.setAttribute('aria-label', 'Abrir asistente IA');
- // Chat panel
var panel = document.createElement('div');
panel.className = 'chat-panel';
panel.id = 'chatPanel';
panel.innerHTML =
'
' +
'' +
'
Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.
' +
@@ -32,6 +39,7 @@
'
' +
'' +
'' +
+ (hasSpeechAPI ? '' : '') +
'' +
'
';
@@ -52,8 +60,139 @@
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');
@@ -104,6 +243,8 @@
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);
}
@@ -149,7 +290,6 @@
card.style.cursor = 'pointer';
card.addEventListener('click', function () {
- // Search in catalog
var searchInput = document.getElementById('searchInput');
if (searchInput && partNum) {
searchInput.value = partNum;
diff --git a/dashboard/chat-public.min.css b/dashboard/chat-public.min.css
index 35381ac..523f75a 100644
--- a/dashboard/chat-public.min.css
+++ b/dashboard/chat-public.min.css
@@ -1,5 +1,5 @@
/* ==========================================================================
- NEXUS — Public Catalog Chat Widget
+ NEXUS — Public Catalog Chat Widget (Voice + TTS enabled)
Reuses design tokens from tokens.css
========================================================================== */
@@ -228,6 +228,99 @@
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
+/* ─── Header Actions (TTS toggle + close) ─── */
+.chat-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.chat-tts-toggle {
+ background: none;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ cursor: pointer;
+ padding: 4px;
+ line-height: 1;
+ opacity: 0.9;
+ transition: opacity 0.15s ease;
+}
+
+.chat-tts-toggle:hover { opacity: 1; }
+.chat-tts-toggle.off { opacity: 0.4; }
+
+/* ─── Mic Button (Voice Input) ─── */
+.chat-mic-btn {
+ width: 38px;
+ height: 38px;
+ border-radius: 8px;
+ border: 1px solid var(--color-border, #333);
+ background: var(--color-bg-base, #111);
+ color: var(--color-text-secondary, #aaa);
+ font-size: 1.1rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.chat-mic-btn:hover {
+ border-color: var(--color-accent, #F5A623);
+ color: var(--color-accent, #F5A623);
+}
+
+.chat-mic-btn.listening {
+ background: #f85149;
+ border-color: #f85149;
+ color: #fff;
+ animation: micPulse 1.4s infinite;
+}
+
+@keyframes micPulse {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.4); }
+ 50% { box-shadow: 0 0 0 10px rgba(248, 81, 73, 0); }
+}
+
+/* ─── TTS Button ─── */
+.chat-tts-btn {
+ background: none;
+ border: none;
+ color: #8b949e;
+ font-size: 0.85rem;
+ cursor: pointer;
+ padding: 2px 4px;
+ margin-left: 6px;
+ border-radius: 4px;
+ transition: color 0.2s, background 0.2s;
+}
+.chat-tts-btn:hover { color: #58a6ff; background: rgba(88,166,255,0.1); }
+.chat-tts-btn.tts-active { color: #58a6ff; }
+
+/* ─── Voice Toast ─── */
+.chat-voice-toast {
+ position: fixed;
+ bottom: 160px;
+ left: 50%;
+ transform: translateX(-50%) translateY(10px);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 8px 18px;
+ border-radius: 8px;
+ font-size: 0.85rem;
+ z-index: 9999;
+ opacity: 0;
+ transition: opacity 0.3s, transform 0.3s;
+ pointer-events: none;
+}
+
+.chat-voice-toast.visible {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
@media (max-width: 480px) {
.chat-panel {
width: calc(100vw - 16px);
diff --git a/dashboard/chat-public.min.js b/dashboard/chat-public.min.js
index 7fa9053..e65147e 100644
--- a/dashboard/chat-public.min.js
+++ b/dashboard/chat-public.min.js
@@ -1 +1 @@
-!function(){"use strict";var e=!1,t=!1,n=[];function a(){var e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="💬",e.setAttribute("aria-label","Abrir asistente IA");var t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML='Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.
',document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",s),document.getElementById("chatClose").addEventListener("click",s),document.getElementById("chatSend").addEventListener("click",c),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),c())})),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"}))}function s(){e=!e;var t=document.getElementById("chatPanel"),n=document.getElementById("chatFab");e?(t.classList.add("open"),n.style.display="none",document.getElementById("chatInput").focus()):(t.classList.remove("open"),n.style.display="flex")}function c(){if(!t){var e=document.getElementById("chatInput"),a=e.value.trim();a&&(e.value="",e.style.height="auto",i(a,"user"),n.push({role:"user",content:a}),n.length>20&&n.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,d(!0),fetch("/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:a,history:n.slice(-10)})}).then((function(e){return e.json()})).then((function(e){if(e.error)i("Error: "+e.error,"ai");else{var t,a,c,d,l=e.response||"Sin respuesta.";i(l,"ai"),n.push({role:"assistant",content:l}),e.search_results&&e.search_results.length>0&&(t=e.search_results,a=document.getElementById("chatMessages"),c=document.getElementById("chatTyping"),(d=document.createElement("div")).className="chat-parts",t.slice(0,8).forEach((function(e){var t=document.createElement("div");t.className="chat-part-card";var n=e.name_es||e.name_part||"",a=e.oem_part_number||e.part_number||"",c=e.brand||"";t.innerHTML=''+o(a)+'
'+o(n)+(c?' ('+o(c)+")":"")+"
",t.style.cursor="pointer",t.addEventListener("click",(function(){var e=document.getElementById("searchInput");e&&a&&(e.value=a,"function"==typeof window.doSearch&&window.doSearch(),s())})),d.appendChild(t)})),a.insertBefore(d,c),r())}})).catch((function(e){i("Error de conexion: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,d(!1)})))}}function i(e,t){var n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),r()}function d(e){var t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&r()}function r(){var e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function o(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",a):a()}();
\ No newline at end of file
+!function(){"use strict";var e=!1,t=!1,n=!1,a=null,s=[],c=!0,i=null,o="webkitSpeechRecognition"in window||"SpeechRecognition"in window,r="speechSynthesis"in window;function l(){var e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="💬",e.setAttribute("aria-label","Abrir asistente IA");var t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML='Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.
'+(o?'':"")+'
',document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",m),document.getElementById("chatClose").addEventListener("click",m),document.getElementById("chatSend").addEventListener("click",p),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),p())})),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"})),o&&document.getElementById("chatMic").addEventListener("click",h),r&&document.getElementById("chatTtsToggle").addEventListener("click",d),document.getElementById("chatClose").addEventListener("click",(function(){r&&u()}))}function d(){c=!c;var e=document.getElementById("chatTtsToggle");e&&(e.classList.toggle("off",!c),e.setAttribute("title",c?"Desactivar lectura de respuestas":"Activar lectura de respuestas")),c||u()}function u(){r&&window.speechSynthesis.speaking&&window.speechSynthesis.cancel(),i=null}function h(){n?function(){a&&(a.abort(),a=null);n=!1;var e=document.getElementById("chatMic");e&&e.classList.remove("listening")}():function(){var e=window.SpeechRecognition||window.webkitSpeechRecognition;if(!e)return;(a=new e).lang="es-MX",a.continuous=!1,a.interimResults=!0;var t=document.getElementById("chatInput"),s=document.getElementById("chatMic"),c=t.placeholder;a.onstart=function(){n=!0,s.classList.add("listening"),t.placeholder="Escuchando...",t.value="",u()},a.onresult=function(e){for(var n="",a="",s=e.resultIndex;s20&&s.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,g(!0),fetch("/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:n,history:s.slice(-10)})}).then((function(e){return e.json()})).then((function(e){if(e.error)v("Error: "+e.error,"ai");else{var t,n,a,o,l=e.response||"Sin respuesta.";v(l,"ai"),s.push({role:"assistant",content:l}),c&&function(e){r&&c&&e&&(u(),(i=new SpeechSynthesisUtterance(e)).lang="es-MX",i.rate=1.1,i.pitch=1,window.speechSynthesis.speak(i))}(l),e.search_results&&e.search_results.length>0&&(t=e.search_results,n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),(o=document.createElement("div")).className="chat-parts",t.slice(0,8).forEach((function(e){var t=document.createElement("div");t.className="chat-part-card";var n=e.name_es||e.name_part||"",a=e.oem_part_number||e.part_number||"",s=e.brand||"";t.innerHTML=''+f(a)+'
'+f(n)+(s?' ('+f(s)+")":"")+"
",t.style.cursor="pointer",t.addEventListener("click",(function(){var e=document.getElementById("searchInput");e&&a&&(e.value=a,"function"==typeof window.doSearch&&window.doSearch(),m())})),o.appendChild(t)})),n.insertBefore(o,a),y())}})).catch((function(e){v("Error de conexion: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,g(!1)})))}}function v(e,t){var n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),y()}function g(e){var t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&y()}function y(){var e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function f(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",l):l()}();
\ No newline at end of file
diff --git a/pos/static/css/chat.css b/pos/static/css/chat.css
index ec6a47e..a02936d 100644
--- a/pos/static/css/chat.css
+++ b/pos/static/css/chat.css
@@ -104,6 +104,27 @@
.chat-header-close:hover { opacity: 1; }
+.chat-header-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+}
+
+.chat-tts-toggle {
+ background: none;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ cursor: pointer;
+ padding: var(--space-1);
+ line-height: 1;
+ opacity: 0.9;
+ transition: opacity var(--duration-fast) var(--ease-in-out);
+}
+
+.chat-tts-toggle:hover { opacity: 1; }
+.chat-tts-toggle.off { opacity: 0.35; }
+
/* ─── Messages Area ─── */
.chat-messages {
diff --git a/pos/static/css/chat.min.css b/pos/static/css/chat.min.css
index ec6a47e..a02936d 100644
--- a/pos/static/css/chat.min.css
+++ b/pos/static/css/chat.min.css
@@ -104,6 +104,27 @@
.chat-header-close:hover { opacity: 1; }
+.chat-header-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+}
+
+.chat-tts-toggle {
+ background: none;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ cursor: pointer;
+ padding: var(--space-1);
+ line-height: 1;
+ opacity: 0.9;
+ transition: opacity var(--duration-fast) var(--ease-in-out);
+}
+
+.chat-tts-toggle:hover { opacity: 1; }
+.chat-tts-toggle.off { opacity: 0.35; }
+
/* ─── Messages Area ─── */
.chat-messages {
diff --git a/pos/static/js/chat.js b/pos/static/js/chat.js
index d7bb979..ddcaac7 100644
--- a/pos/static/js/chat.js
+++ b/pos/static/js/chat.js
@@ -9,8 +9,11 @@
let isSending = false;
let isListening = false;
let recognition = null;
+ let ttsEnabled = true;
+ let ttsUtterance = null;
const history = []; // conversation history for AI context
const hasSpeechAPI = ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window);
+ const hasTTS = ('speechSynthesis' in window);
// ─── Build DOM ───
function init() {
@@ -71,6 +74,16 @@
document.getElementById('chatMic').addEventListener('click', toggleVoice);
}
+ // TTS toggle
+ if (hasTTS) {
+ document.getElementById('chatTtsToggle').addEventListener('click', toggleTTS);
+ }
+
+ // Stop TTS when closing
+ document.getElementById('chatClose').addEventListener('click', function () {
+ if (hasTTS) stopSpeaking();
+ });
+
// Auto-resize textarea
document.getElementById('chatInput').addEventListener('input', function () {
this.style.height = 'auto';
@@ -170,6 +183,34 @@
}, 2000);
}
+ // ─── TTS (Text-to-Speech) ───
+ function toggleTTS() {
+ ttsEnabled = !ttsEnabled;
+ const 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;
+ }
+
// ─── Image Upload (Part identification placeholder) ───
function handleImageUpload(e) {
const file = e.target.files && e.target.files[0];
@@ -314,6 +355,7 @@
const aiMsg = data.response || 'Sin respuesta.';
addBubble(aiMsg, 'ai');
history.push({ role: 'assistant', content: aiMsg });
+ speak(aiMsg);
// Vehicle info
if (data.vehicle && data.vehicle.brand_id) {
diff --git a/pos/static/js/chat.min.js b/pos/static/js/chat.min.js
index 04f8242..f11e8cf 100644
--- a/pos/static/js/chat.min.js
+++ b/pos/static/js/chat.min.js
@@ -1 +1 @@
-!function(){"use strict";let e=!1,t=!1,n=!1,a=null;const s=[],i="webkitSpeechRecognition"in window||"SpeechRecognition"in window;function c(){const e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="💬",e.setAttribute("aria-label","Abrir asistente IA");const t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML=`\n \n \n
Hola, soy el asistente de Nexus. Dime que refaccion necesitas y te ayudo a encontrarla.
\n
\n \n
\n
\n \n \n \n \n ${i?'':""}\n \n
\n `,document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",l),document.getElementById("chatClose").addEventListener("click",l),document.getElementById("chatSend").addEventListener("click",u),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),u())})),document.getElementById("chatCam").addEventListener("click",(function(){document.getElementById("chatImageInput").click()})),document.getElementById("chatImageInput").addEventListener("change",r),i&&document.getElementById("chatMic").addEventListener("click",o),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"}))}function o(){n?function(){a&&(a.abort(),a=null);n=!1;const e=document.getElementById("chatMic");e&&e.classList.remove("listening")}():function(){const e=window.SpeechRecognition||window.webkitSpeechRecognition;if(!e)return;a=new e,a.lang="es-MX",a.continuous=!1,a.interimResults=!0;const t=document.getElementById("chatInput"),s=document.getElementById("chatMic"),i=t.placeholder;a.onstart=function(){n=!0,s.classList.add("listening"),t.placeholder="Escuchando...",t.value=""},a.onresult=function(e){let n="",a="";for(let t=e.resultIndex;t5242880)return void m("La imagen es muy grande (max 5MB).","ai");const a=new FileReader;a.onload=function(e){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),i=document.createElement("div");i.className="chat-msg user chat-msg-image",i.innerHTML='
Identificar esta parte',n.insertBefore(i,a),y();var c=e.target.result,o="Identifica esta parte automotriz y sugiere terminos de busqueda.";s.push({role:"user",content:o}),s.length>20&&s.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,g(!0);var r=d();fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+r},body:JSON.stringify({message:o,image:c,history:s.slice(-10)})}).then((function(e){return e.json()})).then((function(e){const t=e.response||"No pude identificar la parte. Intenta describirla con texto.";m(t,"ai"),s.push({role:"assistant",content:t}),e.vehicle&&e.vehicle.brand_id&&p(e.vehicle),e.search_results&&e.search_results.length>0&&h(e.search_results)})).catch((function(e){m("Error al procesar imagen: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,g(!1)}))},a.readAsDataURL(n)}function l(){e=!e;const t=document.getElementById("chatPanel"),n=document.getElementById("chatFab");e?(t.classList.add("open"),n.style.display="none",document.getElementById("chatInput").focus()):(t.classList.remove("open"),n.style.display="flex")}function d(){return window.__pos&&window.__pos.token?window.__pos.token:localStorage.getItem("pos_token")||""}async function u(){if(t)return;const e=document.getElementById("chatInput"),n=e.value.trim();if(n){e.value="",e.style.height="auto",m(n,"user"),s.push({role:"user",content:n}),s.length>20&&s.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,g(!0);try{const e=d(),t=await fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+e},body:JSON.stringify({message:n,history:s.slice(-10)})}),a=await t.json();if(!t.ok)return void m("Error: "+(a.error||t.statusText),"ai");const i=a.response||"Sin respuesta.";m(i,"ai"),s.push({role:"assistant",content:i}),a.vehicle&&a.vehicle.brand_id&&p(a.vehicle),a.search_results&&a.search_results.length>0&&h(a.search_results)}catch(e){m("Error de conexion: "+e.message,"ai")}finally{t=!1,document.getElementById("chatSend").disabled=!1,g(!1)}}}function m(e,t){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),y()}function p(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-vehicle-banner";let s=""+f(e.brand||"")+" "+f(e.model||"")+"";e.year&&(s+=" "+e.year),e.mye_options&&e.mye_options.length>0&&(s+="
Motorizaciones encontradas:",e.mye_options.forEach((function(e){s+="
• "+f(e.engine),e.trim&&(s+=" ("+f(e.trim)+")")}))),a.innerHTML=s,t.insertBefore(a,n),y()}function h(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-parts",e.slice(0,8).forEach((function(e){const t=document.createElement("div");t.className="chat-part-card";const n="local"===e.source,s=e.local_stock||0,i=s>0?"in-stock":"",c=s>0?s+" en stock":"Sin stock local",o=e.name_es||e.name_part||"",r=e.oem_part_number||e.part_number||"",d=e.brand||"",u=e.price_1?"$"+parseFloat(e.price_1).toFixed(2):"",m=n?'MI INVENTARIO':'CATÁLOGO';t.innerHTML=''+f(r)+m+(u?" — "+u:"")+'
'+f(o)+(d?' ('+f(d)+")":"")+'
'+f(c)+"
",t.addEventListener("click",(function(){e.id_part&&"function"==typeof window.openPartDetail&&(window.openPartDetail(e.id_part),l())})),a.appendChild(t)})),t.insertBefore(a,n),y()}function g(e){const t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&y()}function y(){const e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function f(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",c):c()}();
\ No newline at end of file
+!function(){"use strict";let e=!1,t=!1,n=!1,a=null,s=!0,i=null;const c=[],o="webkitSpeechRecognition"in window||"SpeechRecognition"in window,r="speechSynthesis"in window;function l(){const e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="💬",e.setAttribute("aria-label","Abrir asistente IA");const t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML=`\n \n \n
Hola, soy el asistente de Nexus. Dime que refaccion necesitas y te ayudo a encontrarla.
\n
\n \n
\n
\n \n \n \n \n ${o?'':""}\n \n
\n `,document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",p),document.getElementById("chatClose").addEventListener("click",p),document.getElementById("chatSend").addEventListener("click",y),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),y())})),document.getElementById("chatCam").addEventListener("click",(function(){document.getElementById("chatImageInput").click()})),document.getElementById("chatImageInput").addEventListener("change",h),o&&document.getElementById("chatMic").addEventListener("click",d),r&&document.getElementById("chatTtsToggle").addEventListener("click",u),document.getElementById("chatClose").addEventListener("click",(function(){r&&m()})),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"}))}function d(){n?function(){a&&(a.abort(),a=null);n=!1;const e=document.getElementById("chatMic");e&&e.classList.remove("listening")}():function(){const e=window.SpeechRecognition||window.webkitSpeechRecognition;if(!e)return;a=new e,a.lang="es-MX",a.continuous=!1,a.interimResults=!0;const t=document.getElementById("chatInput"),s=document.getElementById("chatMic"),i=t.placeholder;a.onstart=function(){n=!0,s.classList.add("listening"),t.placeholder="Escuchando...",t.value=""},a.onresult=function(e){let n="",a="";for(let t=e.resultIndex;t5242880)return void f("La imagen es muy grande (max 5MB).","ai");const a=new FileReader;a.onload=function(e){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg user chat-msg-image",s.innerHTML='
Identificar esta parte',n.insertBefore(s,a),I();var i=e.target.result,o="Identifica esta parte automotriz y sugiere terminos de busqueda.";c.push({role:"user",content:o}),c.length>20&&c.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,b(!0);var r=g();fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+r},body:JSON.stringify({message:o,image:i,history:c.slice(-10)})}).then((function(e){return e.json()})).then((function(e){const t=e.response||"No pude identificar la parte. Intenta describirla con texto.";f(t,"ai"),c.push({role:"assistant",content:t}),e.vehicle&&e.vehicle.brand_id&&v(e.vehicle),e.search_results&&e.search_results.length>0&&E(e.search_results)})).catch((function(e){f("Error al procesar imagen: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,b(!1)}))},a.readAsDataURL(n)}function p(){e=!e;const t=document.getElementById("chatPanel"),n=document.getElementById("chatFab");e?(t.classList.add("open"),n.style.display="none",document.getElementById("chatInput").focus()):(t.classList.remove("open"),n.style.display="flex")}function g(){return window.__pos&&window.__pos.token?window.__pos.token:localStorage.getItem("pos_token")||""}async function y(){if(t)return;const e=document.getElementById("chatInput"),n=e.value.trim();if(n){e.value="",e.style.height="auto",f(n,"user"),c.push({role:"user",content:n}),c.length>20&&c.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,b(!0);try{const e=g(),t=await fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+e},body:JSON.stringify({message:n,history:c.slice(-10)})}),a=await t.json();if(!t.ok)return void f("Error: "+(a.error||t.statusText),"ai");const o=a.response||"Sin respuesta.";f(o,"ai"),c.push({role:"assistant",content:o}),function(e){r&&s&&e&&(m(),i=new SpeechSynthesisUtterance(e),i.lang="es-MX",i.rate=1.1,i.pitch=1,window.speechSynthesis.speak(i))}(o),a.vehicle&&a.vehicle.brand_id&&v(a.vehicle),a.search_results&&a.search_results.length>0&&E(a.search_results)}catch(e){f("Error de conexion: "+e.message,"ai")}finally{t=!1,document.getElementById("chatSend").disabled=!1,b(!1)}}}function f(e,t){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),I()}function v(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-vehicle-banner";let s=""+B(e.brand||"")+" "+B(e.model||"")+"";e.year&&(s+=" "+e.year),e.mye_options&&e.mye_options.length>0&&(s+="
Motorizaciones encontradas:",e.mye_options.forEach((function(e){s+="
• "+B(e.engine),e.trim&&(s+=" ("+B(e.trim)+")")}))),a.innerHTML=s,t.insertBefore(a,n),I()}function E(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-parts",e.slice(0,8).forEach((function(e){const t=document.createElement("div");t.className="chat-part-card";const n="local"===e.source,s=e.local_stock||0,i=s>0?"in-stock":"",c=s>0?s+" en stock":"Sin stock local",o=e.name_es||e.name_part||"",r=e.oem_part_number||e.part_number||"",l=e.brand||"",d=e.price_1?"$"+parseFloat(e.price_1).toFixed(2):"",u=n?'MI INVENTARIO':'CATÁLOGO';t.innerHTML=''+B(r)+u+(d?" — "+d:"")+'
'+B(o)+(l?' ('+B(l)+")":"")+'
'+B(c)+"
",t.addEventListener("click",(function(){e.id_part&&"function"==typeof window.openPartDetail&&(window.openPartDetail(e.id_part),p())})),a.appendChild(t)})),t.insertBefore(a,n),I()}function b(e){const t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&I()}function I(){const e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function B(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",l):l()}();
\ No newline at end of file
diff --git a/pos/templates/accounting.html b/pos/templates/accounting.html
index 7058d42..5f792a0 100644
--- a/pos/templates/accounting.html
+++ b/pos/templates/accounting.html
@@ -5,6 +5,7 @@
Contabilidad — Nexus Autoparts POS
+
@@ -495,5 +496,6 @@
+