diff --git a/modules/turnero/views/dashboard.php b/modules/turnero/views/dashboard.php index 1760faf..0cebfa3 100644 --- a/modules/turnero/views/dashboard.php +++ b/modules/turnero/views/dashboard.php @@ -89,28 +89,41 @@ Layout::open('Dashboard Turnero', 'fas fa-chart-bar'); /* ── Panel IA ── */ #aiPanel { - position:fixed; bottom:0; right:24px; width:360px; z-index:1080; + position:fixed; bottom:0; right:24px; width:380px; z-index:1080; box-shadow:0 -4px 32px rgba(0,0,0,.18); border-radius:16px 16px 0 0; background:#fff; border:1px solid #e2e8f0; border-bottom:none; transform:translateY(100%); transition:transform .3s ease; - display:flex; flex-direction:column; max-height:520px; + display:flex; flex-direction:column; max-height:560px; } #aiPanel.open { transform:translateY(0); } + #aiPanel.minimized .ai-body { display:none; } + #aiPanel.minimized { max-height:none; } .ai-header { background:linear-gradient(135deg, var(--brand-dark,#0d47a1), var(--brand,#1565c0)); color:#fff; padding:12px 16px; border-radius:16px 16px 0 0; - display:flex; align-items:center; gap:8px; flex-shrink:0; + display:flex; align-items:center; gap:8px; flex-shrink:0; cursor:pointer; user-select:none; } .ai-header .ai-title { flex:1; font-weight:700; font-size:.92rem; } .ai-header .btn-close { filter:invert(1) brightness(2); } + .btn-tts { background:transparent; border:none; color:rgba(255,255,255,.55); cursor:pointer; padding:3px 6px; border-radius:6px; font-size:.85rem; transition:color .2s; } + .btn-tts.active { color:#fff; } + .ai-body { display:flex; flex-direction:column; flex:1; overflow:hidden; } + .ai-quick { display:flex; gap:6px; flex-wrap:wrap; padding:8px 12px; border-bottom:1px solid #f1f5f9; flex-shrink:0; } + .ai-chip { background:#f1f5f9; border:1px solid #e2e8f0; border-radius:99px; padding:4px 10px; font-size:.73rem; cursor:pointer; color:#475569; white-space:nowrap; transition:background .15s; } + .ai-chip:hover { background:#e2e8f0; } .ai-messages { flex:1; overflow-y:auto; padding:12px 14px; display:flex; flex-direction:column; gap:8px; } - .ai-msg { max-width:88%; padding:8px 12px; border-radius:12px; font-size:.83rem; line-height:1.45; } + .ai-msg { max-width:90%; padding:8px 12px; border-radius:12px; font-size:.83rem; line-height:1.5; } .ai-msg.user { background:var(--brand,#1565c0); color:#fff; align-self:flex-end; border-radius:12px 12px 2px 12px; } .ai-msg.bot { background:#f1f5f9; color:#1e293b; align-self:flex-start; border-radius:12px 12px 12px 2px; } .ai-msg.bot.typing { color:#94a3b8; font-style:italic; } - .ai-footer { padding:10px 12px; border-top:1px solid #e2e8f0; display:flex; gap:8px; flex-shrink:0; } + .ai-msg.bot strong { color:#0d47a1; } + .ai-msg.bot ul { margin:4px 0 4px 14px; padding:0; } + .ai-footer { padding:10px 12px; border-top:1px solid #e2e8f0; display:flex; gap:6px; flex-shrink:0; } .ai-footer input { flex:1; border:1px solid #e2e8f0; border-radius:8px; padding:7px 12px; font-size:.85rem; outline:none; } .ai-footer input:focus { border-color:var(--brand,#1565c0); } + .btn-mic { background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:7px 11px; cursor:pointer; color:#64748b; font-size:.85rem; transition:all .2s; } + .btn-mic.listening { background:#fee2e2; color:#dc2626; border-color:#fca5a5; animation:mic-pulse 1s infinite; } + @keyframes mic-pulse { 0%,100%{opacity:1} 50%{opacity:.5} } .ai-footer .btn-send { background:var(--brand,#1565c0); color:#fff; border:none; border-radius:8px; padding:7px 14px; cursor:pointer; font-size:.85rem; } #btnAI { position:fixed; bottom:24px; right:24px; z-index:1079; @@ -260,18 +273,30 @@ try {
-
+
Asistente del Turnero - + +
-
-
¡Hola! Puedo responder preguntas sobre el estado del turnero de hoy. ¿Qué necesitas saber?
-
- @@ -568,11 +593,74 @@ function filtrarTabla() { // ── Asistente IA ────────────────────────────────────────────── let _aiOpen = false; +// ── IA helpers ─────────────────────────────────────────────── +let _aiMinimized = false; +let _ttsOn = true; +let _recog = null; + +function mdToHtml(md) { + let s = md + .replace(/&/g,'&').replace(//g,'>') + .replace(/\*\*(.+?)\*\*/g,'$1') + .replace(/^\* (.+)$/gm,'
  • $1
  • '); + s = s.replace(/((?:
  • [^]*?<\/li>\n?)+)/g, + m => `
      ${m.replace(/\n/g,'')}
    `); + return s.replace(/\n/g,'
    '); +} + +function _stripMd(text) { + return text.replace(/\*\*(.+?)\*\*/g,'$1').replace(/^\* /gm,'').replace(/\n/g,' '); +} + +function hablarIA(text) { + if (!_ttsOn || !window.speechSynthesis) return; + window.speechSynthesis.cancel(); + const utt = new SpeechSynthesisUtterance(_stripMd(text)); + utt.lang = 'es-CO'; utt.rate = 1.05; + window.speechSynthesis.speak(utt); +} + +function toggleTTS() { + _ttsOn = !_ttsOn; + document.getElementById('btnTTS').classList.toggle('active', _ttsOn); + if (!_ttsOn) window.speechSynthesis?.cancel(); +} + +function iniciarVoz() { + const SR = window.SpeechRecognition || window.webkitSpeechRecognition; + if (!SR) { alert('Tu navegador no soporta reconocimiento de voz (usa Chrome)'); return; } + const btn = document.getElementById('btnMic'); + if (_recog) { _recog.stop(); return; } + _recog = new SR(); + _recog.lang = 'es-CO'; _recog.interimResults = false; + btn.classList.add('listening'); + _recog.onresult = e => { + document.getElementById('aiInput').value = e.results[0][0].transcript; + enviarIA(); + }; + _recog.onend = () => { btn.classList.remove('listening'); _recog = null; }; + _recog.onerror= () => { btn.classList.remove('listening'); _recog = null; }; + _recog.start(); +} + +function enviarRapido(pregunta) { + document.getElementById('aiInput').value = pregunta; + document.getElementById('aiQuick').style.display = 'none'; + enviarIA(); +} + +function minimizarIA(e) { + if (e.target.closest('button')) return; + _aiMinimized = !_aiMinimized; + document.getElementById('aiPanel').classList.toggle('minimized', _aiMinimized); +} + function toggleAI() { _aiOpen = !_aiOpen; + if (!_aiOpen) { _aiMinimized = false; document.getElementById('aiPanel').classList.remove('minimized'); } document.getElementById('aiPanel').classList.toggle('open', _aiOpen); document.getElementById('btnAI').classList.toggle('hidden', _aiOpen); - if (_aiOpen) document.getElementById('aiInput').focus(); + if (_aiOpen && !_aiMinimized) document.getElementById('aiInput').focus(); } async function enviarIA() { @@ -599,7 +687,8 @@ async function enviarIA() { }); const d = await res.json(); typing.className = 'ai-msg bot'; - typing.textContent = d.ok ? d.respuesta : (d.error || 'Error al conectar con la IA'); + if (d.ok) { typing.innerHTML = mdToHtml(d.respuesta); hablarIA(d.respuesta); } + else { typing.textContent = d.error || 'Error al conectar con la IA'; } } catch(_) { typing.className = 'ai-msg bot'; typing.textContent = 'Error de conexión';