/* _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 = [
'
',
'',
'',
' ',
' ',
'
'
].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();
})();