/* _ai_widget.js — global floating DABI AI assistant * Inject with: * * Self-contained, no dependencies. Idempotent (refuses to mount twice). * Reads JWT from localStorage.pgz_access (falls back to sessionStorage / * localStorage.access_token). POSTs to /sport/api/v2/ai/ask with page * context (path + hash) so the AI can ground answers in where the user is. */ (function () { 'use strict'; if (window.__ai_widget_mounted) return; window.__ai_widget_mounted = true; // ─────────── config ─────────── var ENDPOINT = '/sport/api/v2/ai/ask'; var STORAGE_HISTORY_KEY = '_ai_widget_history'; var MAX_HISTORY = 30; function getToken() { try { return localStorage.getItem('pgz_access') || sessionStorage.getItem('pgz_access') || localStorage.getItem('access_token') || ''; } catch (e) { return ''; } } function pageContext() { return { path: location.pathname || '', hash: (location.hash || '').replace(/^#/, ''), title: document.title || '' }; } function loadHistory() { try { var raw = sessionStorage.getItem(STORAGE_HISTORY_KEY); var arr = raw ? JSON.parse(raw) : []; return Array.isArray(arr) ? arr.slice(-MAX_HISTORY) : []; } catch (e) { return []; } } function saveHistory(arr) { try { sessionStorage.setItem(STORAGE_HISTORY_KEY, JSON.stringify(arr.slice(-MAX_HISTORY))); } catch (e) {} } // ─────────── DOM ─────────── var btn = document.createElement('button'); btn.id = '_ai_widget_btn'; btn.type = 'button'; btn.title = 'DABI AI Copilot'; btn.setAttribute('aria-label', 'Otvori DABI AI Copilot'); btn.textContent = '🤖'; btn.style.cssText = [ 'position:fixed', 'bottom:20px', 'right:20px', 'z-index:99998', 'width:54px', 'height:54px', 'border-radius:50%', 'background:#2563eb', 'color:#fff', 'border:0', 'font-size:24px', 'cursor:pointer', 'box-shadow:0 6px 18px rgba(0,0,0,0.25)', 'transition:transform 0.15s ease', 'line-height:1' ].join(';'); btn.onmouseenter = function () { btn.style.transform = 'scale(1.06)'; }; btn.onmouseleave = function () { btn.style.transform = 'scale(1.0)'; }; var panel = document.createElement('div'); panel.id = '_ai_widget_panel'; panel.style.cssText = [ 'position:fixed', 'bottom:88px', 'right:20px', 'z-index:99999', 'width:380px', 'max-width:calc(100vw - 32px)', 'height:520px', 'max-height:calc(100vh - 120px)', 'display:none', 'flex-direction:column', 'background:#0f172a', 'color:#e2e8f0', 'border:1px solid #334155', 'border-radius:12px', 'box-shadow:0 16px 48px rgba(0,0,0,0.5)', 'font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif', 'font-size:13px', 'overflow:hidden' ].join(';'); panel.innerHTML = [ '
', ' 🤖', ' DABI AI Copilot', ' ', ' ', '
', '
', '
', ' ', ' ', '
' ].join(''); function mount() { if (!document.body) { document.addEventListener('DOMContentLoaded', mount); return; } document.body.appendChild(btn); document.body.appendChild(panel); wire(); rerender(); } // ─────────── behaviour ─────────── var messages = loadHistory(); function setStatus(s) { var el = document.getElementById('_ai_widget_status'); if (el) el.textContent = s || ''; } function escHTML(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function rerender() { var box = document.getElementById('_ai_widget_msgs'); if (!box) return; if (!messages.length) { box.innerHTML = '
Postavi pitanje DABI-ju.
Kontekst trenutne stranice se šalje automatski.
'; return; } box.innerHTML = messages.map(function (m) { var bubbleStyle = m.role === 'user' ? 'align-self:flex-end;background:#2563eb;color:#fff' : (m.role === 'error' ? 'align-self:flex-start;background:#7f1d1d;color:#fecaca' : 'align-self:flex-start;background:#1e293b;color:#e2e8f0;border:1px solid #334155'); return '
' + escHTML(m.text) + '
'; }).join(''); box.scrollTop = box.scrollHeight; } function open() { panel.style.display = 'flex'; btn.style.display = 'none'; setTimeout(function () { var q = document.getElementById('_ai_widget_q'); if (q) q.focus(); }, 50); } function close() { panel.style.display = 'none'; btn.style.display = 'block'; } function pushMsg(role, text) { messages.push({ role: role, text: text, t: Date.now() }); if (messages.length > MAX_HISTORY) messages = messages.slice(-MAX_HISTORY); saveHistory(messages); rerender(); } async function ask() { var inp = document.getElementById('_ai_widget_q'); var sendBtn = document.getElementById('_ai_widget_send'); if (!inp || !sendBtn) return; var q = (inp.value || '').trim(); if (!q) return; var tok = getToken(); if (!tok) { pushMsg('error', '⚠ Prijava potrebna. Otvori /login pa se vrati.'); setStatus('traži prijavu'); return; } pushMsg('user', q); inp.value = ''; sendBtn.disabled = true; sendBtn.textContent = '…'; setStatus('razmišljam…'); try { var ctx = pageContext(); var body = { question: q, query: q, q: q, context: ctx, page_path: ctx.path, page_hash: ctx.hash, page_title: ctx.title }; var r = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + tok }, body: JSON.stringify(body) }); if (r.status === 401) { pushMsg('error', '⚠ Sesija je istekla. Otvori /login.'); setStatus('401'); return; } if (!r.ok) { var t = ''; try { t = await r.text(); } catch (e) {} pushMsg('error', '❌ HTTP ' + r.status + (t ? ' — ' + t.slice(0, 200) : '')); setStatus('greška'); return; } var data = await r.json(); var answer = data.answer || data.response || data.text || (typeof data === 'string' ? data : JSON.stringify(data, null, 2).slice(0, 1500)); pushMsg('assistant', answer); setStatus('odgovor'); } catch (e) { pushMsg('error', '❌ ' + (e && e.message ? e.message : String(e))); setStatus('greška'); } finally { sendBtn.disabled = false; sendBtn.textContent = 'Pošalji'; } } function wire() { btn.addEventListener('click', open); var closeBtn = document.getElementById('_ai_widget_close'); if (closeBtn) closeBtn.addEventListener('click', close); var sendBtn = document.getElementById('_ai_widget_send'); if (sendBtn) sendBtn.addEventListener('click', ask); var q = document.getElementById('_ai_widget_q'); if (q) { q.addEventListener('keydown', function (ev) { if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); ask(); } if (ev.key === 'Escape') { close(); } }); } document.addEventListener('keydown', function (ev) { // Cmd/Ctrl + K opens the widget — convenience shortcut if ((ev.metaKey || ev.ctrlKey) && ev.key.toLowerCase() === 'k') { ev.preventDefault(); if (panel.style.display === 'none') open(); else close(); } }); } mount(); })();