Files
Autoparts-DB/pos/static/js/chat.min.js
consultoria-as afb3b2405c feat(voice): implementa voz y TTS en chats POS y dashboard
- Agrega TTS (speechSynthesis) a chat.js del POS para leer respuestas IA
- Copia lógica de voz completa (STT + TTS) a dashboard/chat-public.js
- Extiende estilos TTS en chat.css y chat-public.css
- Agrega chat widget a 13 templates POS que no lo tenían
- Corrige duplicado de chat.css en diagrams.html
- Minifica assets actualizados
- 73/73 tests pasan
2026-04-28 00:53:57 +00:00

1 line
8.9 KiB
JavaScript

!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="&#x1F4AC;",e.setAttribute("aria-label","Abrir asistente IA");const t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML=`\n <div class="chat-header">\n <h3>Asistente IA — Buscar partes</h3>\n <button class="chat-header-close" id="chatClose" aria-label="Cerrar">&times;</button>\n </div>\n <div class="chat-messages" id="chatMessages">\n <div class="chat-msg ai">Hola, soy el asistente de Nexus. Dime que refaccion necesitas y te ayudo a encontrarla.</div>\n <div class="chat-typing" id="chatTyping">\n <span></span><span></span><span></span>\n </div>\n </div>\n <div class="chat-input-area">\n <textarea class="chat-input" id="chatInput" placeholder="Ej: Balatas para Tsuru 2015..." rows="1"></textarea>\n <input type="file" id="chatImageInput" accept="image/*" capture="environment" style="display:none;">\n <button class="chat-cam-btn" id="chatCam" aria-label="Enviar foto de parte" title="Identificar parte por foto">&#128247;</button>\n ${o?'<button class="chat-mic-btn" id="chatMic" aria-label="Entrada por voz" title="Entrada por voz">&#127908;</button>':""}\n <button class="chat-send-btn" id="chatSend" aria-label="Enviar">&#9654;</button>\n </div>\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;t<e.results.length;t++)e.results[t].isFinal?a+=e.results[t][0].transcript:n+=e.results[t][0].transcript;t.value=a||n},a.onend=function(){n=!1,s.classList.remove("listening"),t.placeholder=i,a=null,t.value.trim()&&y()},a.onerror=function(e){n=!1,s.classList.remove("listening"),t.placeholder=i,a=null,"no-speech"!==e.error&&"audio-capture"!==e.error&&"not-allowed"!==e.error||function(e){const t=document.createElement("div");t.className="chat-voice-toast",t.textContent=e,document.body.appendChild(t),setTimeout((function(){t.classList.add("visible")}),10),setTimeout((function(){t.classList.remove("visible"),setTimeout((function(){t.remove()}),300)}),2e3)}("No se detecto voz")},a.start()}()}function u(){s=!s;const e=document.getElementById("chatTtsToggle");e&&(e.classList.toggle("off",!s),e.setAttribute("title",s?"Desactivar lectura de respuestas":"Activar lectura de respuestas")),s||m()}function m(){r&&window.speechSynthesis.speaking&&window.speechSynthesis.cancel(),i=null}function h(e){const n=e.target.files&&e.target.files[0];if(!n)return;if(e.target.value="",!n.type.startsWith("image/"))return void f("Solo se permiten imagenes.","ai");if(n.size>5242880)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='<img src="'+e.target.result+'" alt="Foto de parte" style="max-width:180px;max-height:140px;border-radius:8px;display:block;margin-bottom:4px;"><span>Identificar esta parte</span>',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="<strong>"+B(e.brand||"")+" "+B(e.model||"")+"</strong>";e.year&&(s+=" "+e.year),e.mye_options&&e.mye_options.length>0&&(s+="<br>Motorizaciones encontradas:",e.mye_options.forEach((function(e){s+="<br>&bull; "+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?'<span style="background:var(--color-success);color:#fff;padding:1px 6px;border-radius:4px;font-size:0.65rem;margin-left:6px;">MI INVENTARIO</span>':'<span style="background:var(--color-primary);color:#fff;padding:1px 6px;border-radius:4px;font-size:0.65rem;margin-left:6px;">CATÁLOGO</span>';t.innerHTML='<div class="part-number">'+B(r)+u+(d?" &mdash; "+d:"")+'</div><div class="part-name">'+B(o)+(l?' <span style="color:var(--color-text-muted);">('+B(l)+")</span>":"")+'</div><div class="part-stock '+i+'">'+B(c)+"</div>",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()}();