(function () {
const boot = window.MaalFlows || {};
const apiBase = (boot.apiBase || "").replace(/\/$/, "") || new URL(document.currentScript.src).origin;
const siteKey = boot.siteKey || "9b-plus";
const lang = (boot.lang || document.documentElement.lang || navigator.language || "cs").slice(0, 2).toLowerCase();
const storageKey = `maalflows:${siteKey}`;
const session = loadSession();
fetch(`${apiBase}/api/widget/config?siteKey=${encodeURIComponent(siteKey)}`)
.then((res) => res.json())
.then((config) => mount(config))
.catch(() => {});
function mount(config) {
const site = config.site;
const settings = site.settings;
const copy = localized(settings, lang);
const host = document.createElement("div");
host.id = "maalflows-widget";
document.body.append(host);
const root = host.attachShadow({ mode: "open" });
root.innerHTML = `
${escapeHtml(body)}
${attachments.map((a) => `${escapeHtml(a.name)}`).join("")}`; messages.append(item); messages.scrollTop = messages.scrollHeight; } function localized(settings, language) { const pick = (obj) => obj?.[language] || obj?.en || obj?.cs || ""; return { title: pick(settings.title), intro: pick(settings.intro), offlineIntro: pick(settings.offlineIntro), placeholder: pick(settings.placeholder), sendLabel: pick(settings.sendLabel) }; } function styles(settings) { const c = settings.colors; const desktop = settings.desktop; const mobile = settings.mobile; return ` :host { all: initial; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: ${c.text}; } * { box-sizing: border-box; } button, input, textarea { font: inherit; } .mf-launcher { position: fixed; z-index: 2147483000; ${desktop.side}: ${desktop.sideOffsetPx}px; bottom: ${desktop.bottomPx}px; width: 58px; height: 58px; border: 0; border-radius: 50%; background: ${c.brand}; color: ${c.brandText}; box-shadow: 0 12px 30px rgb(0 0 0 / 22%); cursor: pointer; } .mf-launcher span, .mf-launcher span:before, .mf-launcher span:after { display: block; background: ${c.brandText}; height: 3px; border-radius: 4px; content: ""; } .mf-launcher span { width: 26px; margin: 0 auto; } .mf-launcher span:before { transform: translateY(-8px); } .mf-launcher span:after { transform: translateY(5px); width: 18px; } .mf-panel { position: fixed; z-index: 2147483001; ${desktop.side}: ${desktop.sideOffsetPx}px; bottom: ${desktop.bottomPx + 72}px; width: min(${desktop.widthPx}px, calc(100vw - 24px)); max-height: min(680px, calc(100vh - 110px)); display: none; background: ${c.surface}; border: 1px solid #dfe6e2; border-radius: 8px; overflow: hidden; box-shadow: 0 18px 50px rgb(0 0 0 / 18%); } .mf-panel.open { display: grid; grid-template-rows: auto minmax(130px, 1fr) auto auto; } header { position: relative; display: grid; gap: 3px; background: ${c.brand}; color: ${c.brandText}; padding: 15px 46px 15px 16px; } header strong { font-size: 16px; } header small { opacity: .92; font-size: 13px; line-height: 1.35; } .mf-close { position: absolute; top: 9px; right: 9px; width: 30px; height: 30px; border: 0; border-radius: 6px; background: rgb(255 255 255 / 16%); color: ${c.brandText}; cursor: pointer; } .mf-messages { padding: 14px; overflow: auto; display: flex; flex-direction: column; gap: 10px; } .mf-message { max-width: 86%; padding: 9px 11px; border-radius: 8px; background: #f1f5f2; color: ${c.text}; } .mf-message.admin { align-self: flex-start; background: #eef7f4; } .mf-message.visitor { align-self: flex-end; background: ${c.brand}; color: ${c.brandText}; } .mf-message p { margin: 0; white-space: pre-wrap; } .mf-message a { color: inherit; display: inline-block; margin-top: 6px; } .mf-typing { padding: 0 14px 8px; color: ${c.muted}; font-size: 13px; } .mf-form { border-top: 1px solid #dfe6e2; padding: 12px; display: grid; gap: 9px; } input, textarea { width: 100%; border: 1px solid #dfe6e2; border-radius: 6px; padding: 9px 10px; color: ${c.text}; background: white; } textarea { resize: vertical; min-height: 72px; } .mf-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 9px; align-items: center; } .mf-actions button { border: 0; border-radius: 6px; background: ${c.brand}; color: ${c.brandText}; padding: 10px 13px; cursor: pointer; } input[type=file] { font-size: 12px; padding: 7px; } .mf-note { margin: 0; color: ${c.muted}; font-size: 11px; line-height: 1.3; } @media (max-width: 640px) { .mf-launcher { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx}px; } .mf-panel { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx + 68}px; width: min(${mobile.widthPx}px, calc(100vw - 24px)); max-height: calc(100vh - 96px); } } `; } function rememberPage() { const entry = { url: location.href, title: document.title, at: new Date().toISOString() }; session.history = (session.history || []).filter((item) => item.url !== entry.url).concat(entry).slice(-20); saveSession(); return session.history; } function loadSession() { try { const existing = JSON.parse(localStorage.getItem(storageKey) || "{}"); return { visitorToken: existing.visitorToken || randomId(), conversationId: existing.conversationId || null, history: existing.history || [] }; } catch { return { visitorToken: randomId(), conversationId: null, history: [] }; } } function saveSession() { localStorage.setItem(storageKey, JSON.stringify({ visitorToken: session.visitorToken, conversationId: session.conversationId, history: session.history || [] })); } function postJson(url, payload) { return fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }); } function fileToPayload(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => resolve({ name: file.name, type: file.type, size: file.size, dataBase64: reader.result }); reader.readAsDataURL(file); }); } function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } function escapeHtml(value) { return String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } function randomId() { return `vis_${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`; } })();