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
This commit is contained in:
2026-04-28 00:53:57 +00:00
parent 1f909f4c42
commit afb3b2405c
20 changed files with 443 additions and 10 deletions

View File

@@ -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) {