/* Auteur : Simon-Pierre Boucher — contact@spboucher.ai * ka-ui/agent/ka-agent.js — KA AGENT : widget de chat commun de l'écosystème * Groupe KA. Vanilla JS, zéro dépendance, injecté sur les 13 sites. * * * Bulle flottante en bas à droite → panneau de chat (bulle) → plein écran (⛶). * Flux SSE en direct depuis le backend central (texte token par token + * indicateur d'outil). Historique conservé en localStorage (par site). */ (function () { "use strict"; var CFG = window.KA_AGENT || {}; var SITE = CFG.site || "groupe-ka"; var ENDPOINT = CFG.endpoint || "https://www.api-ka.com/api/agent/chat"; var KEY = "ka-agent-" + SITE; var MAXLOG = 20; /* ---------- styles ---------- */ var css = ` .kaa-btn{position:fixed;right:18px;bottom:18px;z-index:2147483000;width:58px;height:58px; border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b); color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif; cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center; justify-content:center;transition:transform .15s;padding-bottom:env(safe-area-inset-bottom,0)} .kaa-btn:hover{transform:translate(-2px,-2px)} .kaa-panel{position:fixed;right:16px;bottom:86px;z-index:2147483001;width:min(392px,calc(100vw - 24px)); height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85); overflow:hidden;font-family:Inter,system-ui,sans-serif} .kaa-panel.kaa-open{display:flex} .kaa-panel.kaa-full{right:0;bottom:0;width:100vw;height:100dvh;border-radius:0;border:0;box-shadow:none} .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); color:var(--paper,#f5f3ee);flex:none} .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em} .kaa-head .kaa-ka{display:inline-block;background:var(--accent,#d9f26b);color:var(--on-accent,#141814); border-radius:6px;padding:1px 7px 2px;transform:rotate(-2deg);font-weight:700} .kaa-head small{font:700 9.5px "JetBrains Mono",monospace;letter-spacing:.09em;text-transform:uppercase;opacity:.65} .kaa-head .kaa-sp{flex:1} .kaa-hbtn{width:34px;height:34px;border:1.5px solid rgba(245,243,238,.4);border-radius:8px;background:none; color:var(--paper,#f5f3ee);font-size:15px;cursor:pointer;line-height:1} .kaa-hbtn:hover{border-color:var(--accent,#d9f26b);color:var(--accent,#d9f26b)} .kaa-log{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px; overscroll-behavior:contain;-webkit-overflow-scrolling:touch} .kaa-msg{max-width:86%;padding:9px 13px;border:1.5px solid var(--ink,#141814);border-radius:12px; font-size:13.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word} .kaa-msg.kaa-u{align-self:flex-end;background:var(--accent,#d9f26b);color:var(--on-accent,#141814); border-bottom-right-radius:4px} .kaa-msg.kaa-a{align-self:flex-start;background:#fff;border-bottom-left-radius:4px;box-shadow:3px 3px 0 rgba(20,24,20,.12)} .kaa-msg.kaa-a a{color:inherit;text-decoration:underline;text-underline-offset:3px} .kaa-msg.kaa-a b{font-weight:700} .kaa-tool{align-self:flex-start;font:700 10px "JetBrains Mono",monospace;letter-spacing:.07em; text-transform:uppercase;color:var(--ink-2,#4d5551);padding:3px 10px;border:1.5px dashed var(--ink-2,#4d5551); border-radius:999px;background:var(--surface-2,#faf9f5)} .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none; padding-bottom:calc(11px + env(safe-area-inset-bottom,0))} .kaa-in textarea{flex:1;resize:none;min-height:44px;max-height:110px;padding:11px 12px;font:13.5px/1.4 Inter,system-ui,sans-serif; border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none} .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer} .kaa-in button:disabled{opacity:.45} .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px} .kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite} @keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}} @media (max-width:560px){.kaa-panel{right:8px;left:8px;width:auto}} `; /* ---------- markdown minimal (gras, liens, puces) — texte échappé d'abord ---------- */ function esc(s) { return s.replace(/&/g, "&").replace(//g, ">"); } function md(s) { return esc(s) .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/(https?:\/\/[^\s<)"']+)/g, function (u) { return '' + u.replace(/^https?:\/\/(www\.)?/, "").slice(0, 40) + ""; }); } /* ---------- état ---------- */ var log = []; try { log = JSON.parse(localStorage.getItem(KEY) || "[]"); } catch (e) {} function save() { try { localStorage.setItem(KEY, JSON.stringify(log.slice(-MAXLOG))); } catch (e) {} } /* ---------- DOM ---------- */ var style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); var btn = document.createElement("button"); btn.className = "kaa-btn"; btn.setAttribute("aria-label", "Ouvrir KA Agent — assistant de l'écosystème Groupe KA"); btn.innerHTML = "Ka"; var panel = document.createElement("div"); panel.className = "kaa-panel"; panel.setAttribute("role", "dialog"); panel.setAttribute("aria-label", "KA Agent"); panel.innerHTML = '
KAAgentGroupe KA · IA' + '' + '
' + '
' + '
' + "
"; document.body.appendChild(btn); document.body.appendChild(panel); var logEl = panel.querySelector(".kaa-log"); var ta = panel.querySelector("textarea"); var send = panel.querySelector(".kaa-in button"); function scroll() { logEl.scrollTop = logEl.scrollHeight; } function bubble(cls, html) { var d = document.createElement("div"); d.className = "kaa-msg " + cls; d.innerHTML = html; logEl.appendChild(d); scroll(); return d; } function toolChip(name) { var d = document.createElement("div"); d.className = "kaa-tool kaa-dots"; d.textContent = "🔎 " + name.replace(/_/g, " "); logEl.appendChild(d); scroll(); return d; } function hello() { if (log.length) return; var d = document.createElement("div"); d.className = "kaa-hello"; d.textContent = "👋 Je suis KA Agent, l'assistant de l'écosystème Groupe KA. Posez-moi une question sur ce site, ses données (prix, annonces, stats…) ou sur le groupe."; logEl.appendChild(d); } function render() { logEl.innerHTML = ""; hello(); log.forEach(function (m) { bubble(m.role === "user" ? "kaa-u" : "kaa-a", md(m.content)); }); scroll(); } /* ---------- ouverture / plein écran ---------- */ btn.addEventListener("click", function () { panel.classList.toggle("kaa-open"); if (panel.classList.contains("kaa-open")) { render(); ta.focus(); } }); panel.querySelector(".kaa-x2").addEventListener("click", function () { panel.classList.remove("kaa-open", "kaa-full"); }); panel.querySelector(".kaa-x1").addEventListener("click", function () { panel.classList.toggle("kaa-full"); scroll(); }); /* ---------- envoi + flux SSE ---------- */ var busy = false; function ask() { var q = ta.value.trim(); if (!q || busy) return; busy = true; send.disabled = true; ta.value = ""; log.push({ role: "user", content: q }); save(); bubble("kaa-u", md(q)); var out = bubble("kaa-a", ''); var acc = ""; var chips = []; fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ site: SITE, messages: log.slice(-MAXLOG) }), }) .then(function (res) { if (!res.ok || !res.body) throw new Error("HTTP " + res.status); var reader = res.body.getReader(); var dec = new TextDecoder(); var buf = ""; function pump() { return reader.read().then(function (r) { if (r.done) return finish(); buf += dec.decode(r.value, { stream: true }); var blocks = buf.split("\n\n"); buf = blocks.pop(); blocks.forEach(function (b) { var ev = (b.match(/^event: (.+)$/m) || [])[1]; var dm = (b.match(/^data: (.+)$/m) || [])[1]; if (!ev || !dm) return; var data = {}; try { data = JSON.parse(dm); } catch (e) {} if (ev === "delta" && data.text) { acc += data.text; out.innerHTML = md(acc); scroll(); } else if (ev === "tool") { chips.push(toolChip(data.name || "recherche")); logEl.appendChild(out); // la bulle reste après le chip scroll(); } else if (ev === "error") { acc = acc || "Désolé, une erreur est survenue (" + (data.message || "inconnue") + "). Réessayez."; out.innerHTML = md(acc); } }); return pump(); }); } return pump(); }) .catch(function (e) { acc = acc || "Impossible de joindre KA Agent (" + e.message + "). Réessayez dans un instant."; out.innerHTML = md(acc); }) .then(function () { finish(); }); function finish() { if (busy === false) return; busy = false; send.disabled = false; chips.forEach(function (c) { c.classList.remove("kaa-dots"); }); if (!acc) { acc = "Je n'ai pas pu formuler de réponse — réessayez."; out.innerHTML = md(acc); } log.push({ role: "assistant", content: acc }); save(); scroll(); } } send.addEventListener("click", ask); ta.addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(); } }); })();