/* 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. * * * v2 (2026-08-19) : ouverture directement PLEIN ÉCRAN (desktop et mobile, * bouton ▭ pour réduire en fenêtre sur desktop) ; sur téléphone le panneau se * cale sur window.visualViewport (le clavier ne fait plus grossir/défiler la * page — fond verrouillé par body position:fixed) ; rendu MARKDOWN complet en * streaming (titres, listes, tableaux, code, liens nommés, citations). * Flux SSE en direct depuis le backend central (texte token par token + * indicateur d'outil). Historique conservé en localStorage (par site). * v3 (2026-08-22) : bulle DÉPLAÇABLE au doigt/à la souris (pointer events, * seuil 6 px pour distinguer clic et glisser, bornée aux marges de l'écran, * position mémorisée par site en localStorage, re-bornée au resize) ; en mode * fenêtré le panneau s'ancre du côté où se trouve la bulle. * v4 (2026-08-25) : CARTES d'annonces cliquables (bloc ```ka-card — un JSON * par ligne : site/titre/sous_titre/prix/badge/image/lien) rendues aux * couleurs de la plateforme visée (palette écosystème embarquée), et CHOIX * de clarification (bloc ```ka-choix — {question, options[]}) rendus en * boutons : un clic envoie l'option comme message ; seuls les boutons de la * dernière bulle assistante restent actifs. * v4.2 (2026-08-25) : extraction des cartes/choix par ÉQUILIBRAGE D'ACCOLADES * (jsonObjects) — tous les objets JSON d'un bloc sont trouvés quelle que soit * la mise en forme (une ligne, aéré, tableau) et un bloc tronqué en plein * streaming rend toutes ses cartes complètes au lieu d'une seule. */ (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:calc(18px + env(safe-area-inset-bottom,0px));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;touch-action:none} .kaa-btn:hover{transform:translate(-2px,-2px)} .kaa-btn.kaa-drag{transition:none;cursor:grabbing} .kaa-btn.kaa-drag:hover{transform:none} .kaa-panel{position:fixed;z-index:2147483001;display:none;flex-direction:column;background:var(--paper,#f5f3ee); overflow:hidden;font-family:Inter,system-ui,sans-serif;touch-action:manipulation} .kaa-panel.kaa-open{display:flex} .kaa-panel.kaa-full{left:0;top:0;right:0;bottom:0;width:100vw;height:100vh;height:100dvh;border:0;border-radius:0;box-shadow:none} .kaa-panel:not(.kaa-full){right:16px;bottom:calc(86px + env(safe-area-inset-bottom,0px));width:min(392px,calc(100vw - 24px)); height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px)); border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85)} .kaa-panel.kaa-left:not(.kaa-full){right:auto;left:16px} html.kaa-lock,html.kaa-lock body{overflow:hidden!important;overscroll-behavior:none;touch-action:none} .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); color:var(--paper,#f5f3ee);flex:none;padding-top:max(11px,env(safe-area-inset-top,0px))} .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;flex:none} .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.55;word-break:break-word} .kaa-full .kaa-msg{max-width:min(86%,760px)} .kaa-full .kaa-log{align-items:stretch} .kaa-msg.kaa-u{align-self:flex-end;background:var(--accent,#d9f26b);color:var(--on-accent,#141814); border-bottom-right-radius:4px;white-space:pre-wrap} .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;font-weight:600} .kaa-msg.kaa-a p{margin:.35em 0} .kaa-msg.kaa-a p:first-child{margin-top:0} .kaa-msg.kaa-a p:last-child{margin-bottom:0} .kaa-msg.kaa-a h1,.kaa-msg.kaa-a h2,.kaa-msg.kaa-a h3,.kaa-msg.kaa-a h4{ font:700 14px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.01em;margin:.7em 0 .3em} .kaa-msg.kaa-a h1:first-child,.kaa-msg.kaa-a h2:first-child,.kaa-msg.kaa-a h3:first-child,.kaa-msg.kaa-a h4:first-child{margin-top:.1em} .kaa-msg.kaa-a h1{font-size:15.5px}.kaa-msg.kaa-a h2{font-size:14.5px} .kaa-msg.kaa-a ul,.kaa-msg.kaa-a ol{margin:.35em 0;padding-left:1.35em} .kaa-msg.kaa-a li{margin:.18em 0} .kaa-msg.kaa-a code{font:600 12px "JetBrains Mono",monospace;background:var(--surface-2,#f0eee7); border:1px solid rgba(20,24,20,.15);border-radius:5px;padding:.5px 4px} .kaa-msg.kaa-a pre{margin:.45em 0;padding:9px 11px;background:var(--ink,#141814);color:var(--paper,#f5f3ee); border-radius:9px;overflow-x:auto} .kaa-msg.kaa-a pre code{background:none;border:0;color:inherit;padding:0;font-weight:400;font-size:11.5px;line-height:1.5} .kaa-msg.kaa-a blockquote{margin:.45em 0;padding:.15em .8em;border-left:3px solid var(--accent,#d9f26b); background:var(--surface-2,#faf9f5);color:var(--ink-2,#4d5551)} .kaa-msg.kaa-a hr{border:0;border-top:1.5px dashed rgba(20,24,20,.3);margin:.6em 0} .kaa-msg.kaa-a table{border-collapse:collapse;margin:.45em 0;font-size:12.5px;display:block;overflow-x:auto;max-width:100%} .kaa-msg.kaa-a th,.kaa-msg.kaa-a td{border:1.5px solid var(--ink,#141814);padding:4px 9px;text-align:left;white-space:nowrap} .kaa-msg.kaa-a th{background:var(--accent,#d9f26b);color:var(--on-accent,#141814); font:700 11px "Space Grotesk",system-ui,sans-serif;letter-spacing:.02em} .kaa-msg.kaa-a td{background:#fff} .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-full .kaa-in{justify-content:center} .kaa-full .kaa-in textarea{max-width:700px} .kaa-in textarea{flex:1;resize:none;min-height:44px;max-height:110px;padding:10px 12px;font:16px/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;touch-action:manipulation} .kaa-in button:disabled{opacity:.45} .kaa-cards{display:flex;flex-direction:column;gap:8px;margin:.5em 0 .2em} .kaa-card{display:flex;align-items:stretch;border:1.5px solid var(--ink,#141814);border-radius:11px; overflow:hidden;background:#fff;text-decoration:none!important;color:var(--ink,#141814); box-shadow:3px 3px 0 rgba(20,24,20,.14);border-left:5px solid var(--kaa-c,var(--accent,#d9f26b));transition:transform .12s} .kaa-msg.kaa-a a.kaa-card{text-decoration:none;font-weight:400} .kaa-card:hover{transform:translate(-1px,-1px)} .kaa-card-img{flex:none;width:76px;min-height:76px;background-size:cover;background-position:center; border-right:1.5px solid rgba(20,24,20,.15)} .kaa-card-body{flex:1;display:flex;flex-direction:column;gap:2px;padding:8px 11px;min-width:0} .kaa-card-top{display:flex;gap:6px;align-items:center;flex-wrap:wrap} .kaa-card-site{font:700 9px "JetBrains Mono",monospace;letter-spacing:.08em;text-transform:uppercase; background:var(--kaa-c,var(--accent,#d9f26b));color:var(--kaa-on,var(--on-accent,#141814));border-radius:5px;padding:1px 6px 2px} .kaa-card-badge{font:700 9px "JetBrains Mono",monospace;letter-spacing:.06em;text-transform:uppercase; border:1px dashed var(--ink-2,#4d5551);border-radius:999px;padding:1px 7px;color:var(--ink-2,#4d5551)} .kaa-card-title{font:700 13.5px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.01em;line-height:1.25} .kaa-card-sub{font-size:11.5px;color:var(--ink-2,#4d5551);line-height:1.35} .kaa-card-price{font:700 13px "Space Grotesk",system-ui,sans-serif;margin-top:1px} .kaa-card-go{flex:none;align-self:center;padding:0 10px;font-weight:700;opacity:.55} .kaa-choices{display:flex;flex-wrap:wrap;gap:7px;margin:.45em 0 .15em;align-items:center} .kaa-choices-q{flex-basis:100%;font-size:12px;color:var(--ink-2,#4d5551);font-weight:600} .kaa-choice{border:1.5px solid var(--ink,#141814);border-radius:999px;background:var(--paper,#f5f3ee); color:var(--ink,#141814);font:600 12.5px Inter,system-ui,sans-serif;padding:7px 13px;cursor:pointer; box-shadow:2px 2px 0 rgba(20,24,20,.35);transition:transform .1s;touch-action:manipulation} .kaa-choice:hover{background:var(--accent,#d9f26b);color:var(--on-accent,#141814);transform:translate(-1px,-1px)} .kaa-choices.kaa-done .kaa-choice{opacity:.45;pointer-events:none;box-shadow:none} .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px} .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:640px){.kaa-x1{display:none}} `; /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */ function esc(s) { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } /* ---------- cartes + choix (v4) ---------- Palette de l'écosystème (ka-ui/ecosystem.json) embarquée : chaque carte prend les couleurs de la plateforme de SA fiche, pas du site hôte. */ var ECO = { "groupe-ka": ["Groupe KA", "#d9f26b", "#141814"], "trouve-ka": ["Trouve·Ka", "#1c7ed6", "#ffffff"], "lou-ka": ["Lou·Ka", "#ff6a00", "#ffffff"], "immo-ka": ["Immo·Ka", "#e23744", "#ffffff"], "vrai-prix": ["Vrai-Prix", "#ff5148", "#ffffff"], "auto-ka": ["Auto·Ka", "#ff5a2a", "#ffffff"], "fabri-ka": ["Fabri·Ka", "#c4532e", "#ffffff"], "food-ka": ["Food·Ka", "#1f9d55", "#ffffff"], "resto-ka": ["Resto·Ka", "#f08c00", "#141814"], "sorti-ka": ["Sorti·Ka", "#d6336c", "#ffffff"], "crea-ka": ["Créa·Ka", "#7048e8", "#ffffff"], "api-ka": ["API·Ka", "#3b5bdb", "#ffffff"], "job-ka": ["Job·Ka", "#0c8599", "#ffffff"], "valoplex": ["ValoPlex", "#ff9f45", "#141814"] }; function httpUrl(u) { return typeof u === "string" && /^https:\/\//.test(u) ? u : ""; } /* Extrait chaque objet JSON de premier niveau d'un bloc ka-card, quelle que soit sa mise en forme (un par ligne, aéré, tableau, bloc TRONQUÉ par le streaming) : balayage à équilibrage d'accolades, hors chaînes. */ function jsonObjects(txt) { var objs = [], depth = 0, start = -1, inStr = false, escp = false; for (var k = 0; k < txt.length; k++) { var c = txt[k]; if (inStr) { if (escp) escp = false; else if (c === "\\") escp = true; else if (c === '"') inStr = false; continue; } if (c === '"') { inStr = true; continue; } if (c === "{") { if (!depth) start = k; depth++; } else if (c === "}" && depth) { depth--; if (!depth && start >= 0) { try { objs.push(JSON.parse(txt.slice(start, k + 1))); } catch (e) {} start = -1; } } } return objs; // un objet incomplet en fin de bloc est simplement ignoré } function cards(lines) { var html = ""; jsonObjects(lines.join("\n")).forEach(function (o) { if (!o || !o.titre || !httpUrl(o.lien)) return; var pal = ECO[o.site] || ["Groupe KA", "var(--accent,#d9f26b)", "var(--on-accent,#141814)"]; var img = httpUrl(o.image); html += '' + (img ? '' : "") + '' + '' + esc(pal[0]) + "" + (o.badge ? '' + esc(String(o.badge)) + "" : "") + "" + '' + esc(String(o.titre)) + "" + (o.sous_titre ? '' + esc(String(o.sous_titre)) + "" : "") + (o.prix ? '' + esc(String(o.prix)) + "" : "") + '→'; }); return html ? '
$1");
s = s.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g,
'$1');
s = s.replace(/\*\*([^*\n]+)\*\*/g, "$1");
s = s.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\*)/g, "$1$2");
s = s.replace(/(^|[^"'>=\w])(https?:\/\/[^\s<)"']+)/g, function (m, pre, u) {
return pre + '' +
u.replace(/^https?:\/\/(www\.)?/, "").slice(0, 42) + "";
});
return s;
}
function md(src) {
var lines = String(src).split("\n"), out = [], i = 0, m;
function para(buf) { if (buf.length) out.push("" + buf.map(inline).join("
") + "
" + esc(code.join("\n")) + "");
} else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */
out.push("" + q.map(inline).join(""); } else if (/^\s*\|.*\|\s*$/.test(l)) { /* tableau */ var rows = []; while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])) { var cells = lines[i].trim().replace(/^\||\|$/g, "").split("|").map(function (c) { return c.trim(); }); if (!/^[:\-\s|]+$/.test(lines[i].replace(/\|/g, ""))) rows.push(cells); i++; } if (rows.length) { var t = "
") + "