SPB Git

spb/crea-ka

Public

Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)

widget ka-agent v4 : cartes d annonces cliquables (ka-card, palette ecosysteme) + choix de clarification en boutons (ka-choix) — sync ka-ui 0c910a8

Simon-Pierre Boucher committed (Aug 25, 2026) parent 9ff9109 Browse files

2 changed files +228 −12

modified frontend/dist/ka-agent.js +114 −6
@@ -14,6 +14,12 @@
14 * seuil 6 px pour distinguer clic et glisser, bornée aux marges de l'écran, 14 * seuil 6 px pour distinguer clic et glisser, bornée aux marges de l'écran,
15 * position mémorisée par site en localStorage, re-bornée au resize) ; en mode 15 * position mémorisée par site en localStorage, re-bornée au resize) ; en mode
16 * fenêtré le panneau s'ancre du côté où se trouve la bulle. 16 * fenêtré le panneau s'ancre du côté où se trouve la bulle.
17+ * v4 (2026-08-25) : CARTES d'annonces cliquables (bloc ```ka-card — un JSON
18+ * par ligne : site/titre/sous_titre/prix/badge/image/lien) rendues aux
19+ * couleurs de la plateforme visée (palette écosystème embarquée), et CHOIX
20+ * de clarification (bloc ```ka-choix — {question, options[]}) rendus en
21+ * boutons : un clic envoie l'option comme message ; seuls les boutons de la
22+ * dernière bulle assistante restent actifs.
17 */ 23 */
18 (function () { 24 (function () {
19 "use strict"; 25 "use strict";
@@ -96,6 +102,31 @@
96 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; 102 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px;
97 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer;touch-action:manipulation} 103 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer;touch-action:manipulation}
98 .kaa-in button:disabled{opacity:.45} 104 .kaa-in button:disabled{opacity:.45}
105+ .kaa-cards{display:flex;flex-direction:column;gap:8px;margin:.5em 0 .2em}
106+ .kaa-card{display:flex;align-items:stretch;border:1.5px solid var(--ink,#141814);border-radius:11px;
107+ overflow:hidden;background:#fff;text-decoration:none!important;color:var(--ink,#141814);
108+ box-shadow:3px 3px 0 rgba(20,24,20,.14);border-left:5px solid var(--kaa-c,var(--accent,#d9f26b));transition:transform .12s}
109+ .kaa-msg.kaa-a a.kaa-card{text-decoration:none;font-weight:400}
110+ .kaa-card:hover{transform:translate(-1px,-1px)}
111+ .kaa-card-img{flex:none;width:76px;min-height:76px;background-size:cover;background-position:center;
112+ border-right:1.5px solid rgba(20,24,20,.15)}
113+ .kaa-card-body{flex:1;display:flex;flex-direction:column;gap:2px;padding:8px 11px;min-width:0}
114+ .kaa-card-top{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
115+ .kaa-card-site{font:700 9px "JetBrains Mono",monospace;letter-spacing:.08em;text-transform:uppercase;
116+ background:var(--kaa-c,var(--accent,#d9f26b));color:var(--kaa-on,var(--on-accent,#141814));border-radius:5px;padding:1px 6px 2px}
117+ .kaa-card-badge{font:700 9px "JetBrains Mono",monospace;letter-spacing:.06em;text-transform:uppercase;
118+ border:1px dashed var(--ink-2,#4d5551);border-radius:999px;padding:1px 7px;color:var(--ink-2,#4d5551)}
119+ .kaa-card-title{font:700 13.5px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.01em;line-height:1.25}
120+ .kaa-card-sub{font-size:11.5px;color:var(--ink-2,#4d5551);line-height:1.35}
121+ .kaa-card-price{font:700 13px "Space Grotesk",system-ui,sans-serif;margin-top:1px}
122+ .kaa-card-go{flex:none;align-self:center;padding:0 10px;font-weight:700;opacity:.55}
123+ .kaa-choices{display:flex;flex-wrap:wrap;gap:7px;margin:.45em 0 .15em;align-items:center}
124+ .kaa-choices-q{flex-basis:100%;font-size:12px;color:var(--ink-2,#4d5551);font-weight:600}
125+ .kaa-choice{border:1.5px solid var(--ink,#141814);border-radius:999px;background:var(--paper,#f5f3ee);
126+ color:var(--ink,#141814);font:600 12.5px Inter,system-ui,sans-serif;padding:7px 13px;cursor:pointer;
127+ box-shadow:2px 2px 0 rgba(20,24,20,.35);transition:transform .1s;touch-action:manipulation}
128+ .kaa-choice:hover{background:var(--accent,#d9f26b);color:var(--on-accent,#141814);transform:translate(-1px,-1px)}
129+ .kaa-choices.kaa-done .kaa-choice{opacity:.45;pointer-events:none;box-shadow:none}
99 .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px} 130 .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px}
100 .kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite} 131 .kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite}
101 @keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}} 132 @keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}}
@@ -104,7 +135,63 @@
104 135
105 /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */ 136 /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */
106 function esc(s) { 137 function esc(s) {
107− return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 138+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
139+ }
140+
141+ /* ---------- cartes + choix (v4) ----------
142+ Palette de l'écosystème (ka-ui/ecosystem.json) embarquée : chaque carte
143+ prend les couleurs de la plateforme de SA fiche, pas du site hôte. */
144+ var ECO = {
145+ "groupe-ka": ["Groupe KA", "#d9f26b", "#141814"],
146+ "trouve-ka": ["Trouve·Ka", "#1c7ed6", "#ffffff"],
147+ "lou-ka": ["Lou·Ka", "#ff6a00", "#ffffff"],
148+ "immo-ka": ["Immo·Ka", "#e23744", "#ffffff"],
149+ "vrai-prix": ["Vrai-Prix", "#ff5148", "#ffffff"],
150+ "auto-ka": ["Auto·Ka", "#ff5a2a", "#ffffff"],
151+ "fabri-ka": ["Fabri·Ka", "#c4532e", "#ffffff"],
152+ "food-ka": ["Food·Ka", "#1f9d55", "#ffffff"],
153+ "resto-ka": ["Resto·Ka", "#f08c00", "#141814"],
154+ "sorti-ka": ["Sorti·Ka", "#d6336c", "#ffffff"],
155+ "crea-ka": ["Créa·Ka", "#7048e8", "#ffffff"],
156+ "api-ka": ["API·Ka", "#3b5bdb", "#ffffff"],
157+ "job-ka": ["Job·Ka", "#0c8599", "#ffffff"],
158+ "valoplex": ["ValoPlex", "#ff9f45", "#141814"]
159+ };
160+ function httpUrl(u) { return typeof u === "string" && /^https:\/\//.test(u) ? u : ""; }
161+ function cards(lines) {
162+ var html = "";
163+ lines.forEach(function (ln) {
164+ ln = ln.trim();
165+ if (!ln) return;
166+ var o = null;
167+ try { o = JSON.parse(ln); } catch (e) { return; } // ligne incomplète (streaming) → ignorée
168+ if (!o || !o.titre || !httpUrl(o.lien)) return;
169+ var pal = ECO[o.site] || ["Groupe KA", "var(--accent,#d9f26b)", "var(--on-accent,#141814)"];
170+ var img = httpUrl(o.image);
171+ html += '<a class="kaa-card" href="' + esc(o.lien) + '" target="_blank" rel="noopener noreferrer"' +
172+ ' style="--kaa-c:' + pal[1] + ";--kaa-on:" + pal[2] + '">' +
173+ (img ? '<span class="kaa-card-img" style="background-image:url(&quot;' + esc(img) + '&quot;)"></span>' : "") +
174+ '<span class="kaa-card-body"><span class="kaa-card-top">' +
175+ '<span class="kaa-card-site">' + esc(pal[0]) + "</span>" +
176+ (o.badge ? '<span class="kaa-card-badge">' + esc(String(o.badge)) + "</span>" : "") +
177+ "</span>" +
178+ '<span class="kaa-card-title">' + esc(String(o.titre)) + "</span>" +
179+ (o.sous_titre ? '<span class="kaa-card-sub">' + esc(String(o.sous_titre)) + "</span>" : "") +
180+ (o.prix ? '<span class="kaa-card-price">' + esc(String(o.prix)) + "</span>" : "") +
181+ '</span><span class="kaa-card-go">→</span></a>';
182+ });
183+ return html ? '<div class="kaa-cards">' + html + "</div>" : "";
184+ }
185+ function choices(lines) {
186+ var o = null;
187+ try { o = JSON.parse(lines.join("\n")); } catch (e) { return ""; } // bloc incomplet (streaming)
188+ if (!o || !Array.isArray(o.options) || !o.options.length) return "";
189+ var h = '<span class="kaa-choices">' +
190+ (o.question ? '<span class="kaa-choices-q">' + esc(String(o.question)) + "</span>" : "");
191+ o.options.slice(0, 6).forEach(function (op) {
192+ h += '<button type="button" class="kaa-choice">' + esc(String(op)) + "</button>";
193+ });
194+ return h + "</span>";
108 } 195 }
109 function inline(s) { 196 function inline(s) {
110 s = esc(s); 197 s = esc(s);
@@ -124,11 +211,14 @@
124 function para(buf) { if (buf.length) out.push("<p>" + buf.map(inline).join("<br>") + "</p>"); } 211 function para(buf) { if (buf.length) out.push("<p>" + buf.map(inline).join("<br>") + "</p>"); }
125 while (i < lines.length) { 212 while (i < lines.length) {
126 var l = lines[i]; 213 var l = lines[i];
127− if (/^\s*```/.test(l)) { /* bloc de code */ 214+ if (/^\s*```/.test(l)) { /* bloc de code / carte / choix */
215+ var lang = (l.match(/^\s*```\s*([\w-]+)?/) || [])[1] || "";
128 var code = []; i++; 216 var code = []; i++;
129 while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]); 217 while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]);
130 i++; 218 i++;
131− out.push("<pre><code>" + esc(code.join("\n")) + "</code></pre>"); 219+ if (lang === "ka-card") out.push(cards(code));
220+ else if (lang === "ka-choix") out.push(choices(code));
221+ else out.push("<pre><code>" + esc(code.join("\n")) + "</code></pre>");
132 } else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */ 222 } else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */
133 out.push("<h" + m[1].length + ">" + inline(m[2]) + "</h" + m[1].length + ">"); i++; 223 out.push("<h" + m[1].length + ">" + inline(m[2]) + "</h" + m[1].length + ">"); i++;
134 } else if (/^\s*(---+|\*\*\*+)\s*$/.test(l)) { /* filet */ 224 } else if (/^\s*(---+|\*\*\*+)\s*$/.test(l)) { /* filet */
@@ -230,15 +320,25 @@
230 if (log.length) return; 320 if (log.length) return;
231 var d = document.createElement("div"); 321 var d = document.createElement("div");
232 d.className = "kaa-hello"; 322 d.className = "kaa-hello";
233− 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, fiches, stats…) ou sur le groupe."; 323+ d.textContent = "👋 Je suis KA Agent, l'assistant de l'écosystème Groupe KA. Je peux chercher dans TOUTES les plateformes du groupe : logements, propriétés, autos, emplois, épicerie, restos, sorties, créateurs, produits d'ici — plus estimations de valeur, annuaires (déménageurs, inspecteurs), historiques et statistiques. Posez votre question !";
234 logEl.appendChild(d); 324 logEl.appendChild(d);
235 } 325 }
326+ /* les boutons de choix ne restent actifs que dans la DERNIÈRE bulle assistante */
327+ function retireChoices(keepLast) {
328+ var groups = logEl.querySelectorAll(".kaa-choices:not(.kaa-done)");
329+ var msgs = logEl.querySelectorAll(".kaa-msg.kaa-a");
330+ var last = msgs.length ? msgs[msgs.length - 1] : null;
331+ groups.forEach(function (g) {
332+ if (!(keepLast && last && last.contains(g))) g.classList.add("kaa-done");
333+ });
334+ }
236 function render() { 335 function render() {
237 logEl.innerHTML = ""; 336 logEl.innerHTML = "";
238 hello(); 337 hello();
239 log.forEach(function (m) { 338 log.forEach(function (m) {
240 m.role === "user" ? bubble("kaa-u", esc(m.content)) : bubble("kaa-a", md(m.content)); 339 m.role === "user" ? bubble("kaa-u", esc(m.content)) : bubble("kaa-a", md(m.content));
241 }); 340 });
341+ retireChoices(log.length && log[log.length - 1].role === "assistant");
242 scroll(); 342 scroll();
243 } 343 }
244 344
@@ -385,6 +485,7 @@
385 busy = true; 485 busy = true;
386 send.disabled = true; 486 send.disabled = true;
387 ta.value = ""; 487 ta.value = "";
488+ retireChoices(false); // un nouveau message clôt les choix en suspens
388 log.push({ role: "user", content: q }); 489 log.push({ role: "user", content: q });
389 save(); 490 save();
390 bubble("kaa-u", esc(q)); 491 bubble("kaa-u", esc(q));
@@ -453,6 +554,13 @@
453 ta.addEventListener("keydown", function (e) { 554 ta.addEventListener("keydown", function (e) {
454 if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(); } 555 if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(); }
455 }); 556 });
557+ logEl.addEventListener("click", function (e) { /* clic sur un bouton de choix (v4) */
558+ var b = e.target && e.target.closest ? e.target.closest(".kaa-choice") : null;
559+ if (!b || busy) return;
560+ var g = b.closest(".kaa-choices");
561+ if (g && g.classList.contains("kaa-done")) return;
562+ ta.value = b.textContent;
563+ ask();
564+ });
456 })(); 565 })();
457− 566+/*KA_ANALYTICS*/(function(){try{if(window.__kaAn)return;window.__kaAn=1;var k="KA_VID",v=localStorage.getItem(k);if(!v){v=Date.now().toString(36)+Math.random().toString(36).slice(2,8);localStorage.setItem(k,v);}var b=function(){var i=new Image();i.src="https://www.administration-ka.com/collect?site=crea-ka&vid="+v+"&p="+encodeURIComponent(location.pathname)+"&t="+Date.now();};b();setInterval(b,60000);}catch(e){}})();
458−;/*KA_ANALYTICS*/(function(){try{if(window.__kaAn)return;window.__kaAn=1;var k="KA_VID",v=localStorage.getItem(k);if(!v){v=Date.now().toString(36)+Math.random().toString(36).slice(2,8);localStorage.setItem(k,v);}var b=function(){var i=new Image();i.src="https://www.administration-ka.com/collect?site=crea-ka&vid="+v+"&p="+encodeURIComponent(location.pathname)+"&t="+Date.now();};b();setInterval(b,60000);}catch(e){}})();
modified frontend/src/ka-agent.js +114 −6
@@ -14,6 +14,12 @@
14 * seuil 6 px pour distinguer clic et glisser, bornée aux marges de l'écran, 14 * seuil 6 px pour distinguer clic et glisser, bornée aux marges de l'écran,
15 * position mémorisée par site en localStorage, re-bornée au resize) ; en mode 15 * position mémorisée par site en localStorage, re-bornée au resize) ; en mode
16 * fenêtré le panneau s'ancre du côté où se trouve la bulle. 16 * fenêtré le panneau s'ancre du côté où se trouve la bulle.
17+ * v4 (2026-08-25) : CARTES d'annonces cliquables (bloc ```ka-card — un JSON
18+ * par ligne : site/titre/sous_titre/prix/badge/image/lien) rendues aux
19+ * couleurs de la plateforme visée (palette écosystème embarquée), et CHOIX
20+ * de clarification (bloc ```ka-choix — {question, options[]}) rendus en
21+ * boutons : un clic envoie l'option comme message ; seuls les boutons de la
22+ * dernière bulle assistante restent actifs.
17 */ 23 */
18 (function () { 24 (function () {
19 "use strict"; 25 "use strict";
@@ -96,6 +102,31 @@
96 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; 102 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px;
97 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer;touch-action:manipulation} 103 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer;touch-action:manipulation}
98 .kaa-in button:disabled{opacity:.45} 104 .kaa-in button:disabled{opacity:.45}
105+ .kaa-cards{display:flex;flex-direction:column;gap:8px;margin:.5em 0 .2em}
106+ .kaa-card{display:flex;align-items:stretch;border:1.5px solid var(--ink,#141814);border-radius:11px;
107+ overflow:hidden;background:#fff;text-decoration:none!important;color:var(--ink,#141814);
108+ box-shadow:3px 3px 0 rgba(20,24,20,.14);border-left:5px solid var(--kaa-c,var(--accent,#d9f26b));transition:transform .12s}
109+ .kaa-msg.kaa-a a.kaa-card{text-decoration:none;font-weight:400}
110+ .kaa-card:hover{transform:translate(-1px,-1px)}
111+ .kaa-card-img{flex:none;width:76px;min-height:76px;background-size:cover;background-position:center;
112+ border-right:1.5px solid rgba(20,24,20,.15)}
113+ .kaa-card-body{flex:1;display:flex;flex-direction:column;gap:2px;padding:8px 11px;min-width:0}
114+ .kaa-card-top{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
115+ .kaa-card-site{font:700 9px "JetBrains Mono",monospace;letter-spacing:.08em;text-transform:uppercase;
116+ background:var(--kaa-c,var(--accent,#d9f26b));color:var(--kaa-on,var(--on-accent,#141814));border-radius:5px;padding:1px 6px 2px}
117+ .kaa-card-badge{font:700 9px "JetBrains Mono",monospace;letter-spacing:.06em;text-transform:uppercase;
118+ border:1px dashed var(--ink-2,#4d5551);border-radius:999px;padding:1px 7px;color:var(--ink-2,#4d5551)}
119+ .kaa-card-title{font:700 13.5px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.01em;line-height:1.25}
120+ .kaa-card-sub{font-size:11.5px;color:var(--ink-2,#4d5551);line-height:1.35}
121+ .kaa-card-price{font:700 13px "Space Grotesk",system-ui,sans-serif;margin-top:1px}
122+ .kaa-card-go{flex:none;align-self:center;padding:0 10px;font-weight:700;opacity:.55}
123+ .kaa-choices{display:flex;flex-wrap:wrap;gap:7px;margin:.45em 0 .15em;align-items:center}
124+ .kaa-choices-q{flex-basis:100%;font-size:12px;color:var(--ink-2,#4d5551);font-weight:600}
125+ .kaa-choice{border:1.5px solid var(--ink,#141814);border-radius:999px;background:var(--paper,#f5f3ee);
126+ color:var(--ink,#141814);font:600 12.5px Inter,system-ui,sans-serif;padding:7px 13px;cursor:pointer;
127+ box-shadow:2px 2px 0 rgba(20,24,20,.35);transition:transform .1s;touch-action:manipulation}
128+ .kaa-choice:hover{background:var(--accent,#d9f26b);color:var(--on-accent,#141814);transform:translate(-1px,-1px)}
129+ .kaa-choices.kaa-done .kaa-choice{opacity:.45;pointer-events:none;box-shadow:none}
99 .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px} 130 .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px}
100 .kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite} 131 .kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite}
101 @keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}} 132 @keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}}
@@ -104,7 +135,63 @@
104 135
105 /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */ 136 /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */
106 function esc(s) { 137 function esc(s) {
107− return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 138+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
139+ }
140+
141+ /* ---------- cartes + choix (v4) ----------
142+ Palette de l'écosystème (ka-ui/ecosystem.json) embarquée : chaque carte
143+ prend les couleurs de la plateforme de SA fiche, pas du site hôte. */
144+ var ECO = {
145+ "groupe-ka": ["Groupe KA", "#d9f26b", "#141814"],
146+ "trouve-ka": ["Trouve·Ka", "#1c7ed6", "#ffffff"],
147+ "lou-ka": ["Lou·Ka", "#ff6a00", "#ffffff"],
148+ "immo-ka": ["Immo·Ka", "#e23744", "#ffffff"],
149+ "vrai-prix": ["Vrai-Prix", "#ff5148", "#ffffff"],
150+ "auto-ka": ["Auto·Ka", "#ff5a2a", "#ffffff"],
151+ "fabri-ka": ["Fabri·Ka", "#c4532e", "#ffffff"],
152+ "food-ka": ["Food·Ka", "#1f9d55", "#ffffff"],
153+ "resto-ka": ["Resto·Ka", "#f08c00", "#141814"],
154+ "sorti-ka": ["Sorti·Ka", "#d6336c", "#ffffff"],
155+ "crea-ka": ["Créa·Ka", "#7048e8", "#ffffff"],
156+ "api-ka": ["API·Ka", "#3b5bdb", "#ffffff"],
157+ "job-ka": ["Job·Ka", "#0c8599", "#ffffff"],
158+ "valoplex": ["ValoPlex", "#ff9f45", "#141814"]
159+ };
160+ function httpUrl(u) { return typeof u === "string" && /^https:\/\//.test(u) ? u : ""; }
161+ function cards(lines) {
162+ var html = "";
163+ lines.forEach(function (ln) {
164+ ln = ln.trim();
165+ if (!ln) return;
166+ var o = null;
167+ try { o = JSON.parse(ln); } catch (e) { return; } // ligne incomplète (streaming) → ignorée
168+ if (!o || !o.titre || !httpUrl(o.lien)) return;
169+ var pal = ECO[o.site] || ["Groupe KA", "var(--accent,#d9f26b)", "var(--on-accent,#141814)"];
170+ var img = httpUrl(o.image);
171+ html += '<a class="kaa-card" href="' + esc(o.lien) + '" target="_blank" rel="noopener noreferrer"' +
172+ ' style="--kaa-c:' + pal[1] + ";--kaa-on:" + pal[2] + '">' +
173+ (img ? '<span class="kaa-card-img" style="background-image:url(&quot;' + esc(img) + '&quot;)"></span>' : "") +
174+ '<span class="kaa-card-body"><span class="kaa-card-top">' +
175+ '<span class="kaa-card-site">' + esc(pal[0]) + "</span>" +
176+ (o.badge ? '<span class="kaa-card-badge">' + esc(String(o.badge)) + "</span>" : "") +
177+ "</span>" +
178+ '<span class="kaa-card-title">' + esc(String(o.titre)) + "</span>" +
179+ (o.sous_titre ? '<span class="kaa-card-sub">' + esc(String(o.sous_titre)) + "</span>" : "") +
180+ (o.prix ? '<span class="kaa-card-price">' + esc(String(o.prix)) + "</span>" : "") +
181+ '</span><span class="kaa-card-go">→</span></a>';
182+ });
183+ return html ? '<div class="kaa-cards">' + html + "</div>" : "";
184+ }
185+ function choices(lines) {
186+ var o = null;
187+ try { o = JSON.parse(lines.join("\n")); } catch (e) { return ""; } // bloc incomplet (streaming)
188+ if (!o || !Array.isArray(o.options) || !o.options.length) return "";
189+ var h = '<span class="kaa-choices">' +
190+ (o.question ? '<span class="kaa-choices-q">' + esc(String(o.question)) + "</span>" : "");
191+ o.options.slice(0, 6).forEach(function (op) {
192+ h += '<button type="button" class="kaa-choice">' + esc(String(op)) + "</button>";
193+ });
194+ return h + "</span>";
108 } 195 }
109 function inline(s) { 196 function inline(s) {
110 s = esc(s); 197 s = esc(s);
@@ -124,11 +211,14 @@
124 function para(buf) { if (buf.length) out.push("<p>" + buf.map(inline).join("<br>") + "</p>"); } 211 function para(buf) { if (buf.length) out.push("<p>" + buf.map(inline).join("<br>") + "</p>"); }
125 while (i < lines.length) { 212 while (i < lines.length) {
126 var l = lines[i]; 213 var l = lines[i];
127− if (/^\s*```/.test(l)) { /* bloc de code */ 214+ if (/^\s*```/.test(l)) { /* bloc de code / carte / choix */
215+ var lang = (l.match(/^\s*```\s*([\w-]+)?/) || [])[1] || "";
128 var code = []; i++; 216 var code = []; i++;
129 while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]); 217 while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]);
130 i++; 218 i++;
131− out.push("<pre><code>" + esc(code.join("\n")) + "</code></pre>"); 219+ if (lang === "ka-card") out.push(cards(code));
220+ else if (lang === "ka-choix") out.push(choices(code));
221+ else out.push("<pre><code>" + esc(code.join("\n")) + "</code></pre>");
132 } else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */ 222 } else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */
133 out.push("<h" + m[1].length + ">" + inline(m[2]) + "</h" + m[1].length + ">"); i++; 223 out.push("<h" + m[1].length + ">" + inline(m[2]) + "</h" + m[1].length + ">"); i++;
134 } else if (/^\s*(---+|\*\*\*+)\s*$/.test(l)) { /* filet */ 224 } else if (/^\s*(---+|\*\*\*+)\s*$/.test(l)) { /* filet */
@@ -230,15 +320,25 @@
230 if (log.length) return; 320 if (log.length) return;
231 var d = document.createElement("div"); 321 var d = document.createElement("div");
232 d.className = "kaa-hello"; 322 d.className = "kaa-hello";
233− 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, fiches, stats…) ou sur le groupe."; 323+ d.textContent = "👋 Je suis KA Agent, l'assistant de l'écosystème Groupe KA. Je peux chercher dans TOUTES les plateformes du groupe : logements, propriétés, autos, emplois, épicerie, restos, sorties, créateurs, produits d'ici — plus estimations de valeur, annuaires (déménageurs, inspecteurs), historiques et statistiques. Posez votre question !";
234 logEl.appendChild(d); 324 logEl.appendChild(d);
235 } 325 }
326+ /* les boutons de choix ne restent actifs que dans la DERNIÈRE bulle assistante */
327+ function retireChoices(keepLast) {
328+ var groups = logEl.querySelectorAll(".kaa-choices:not(.kaa-done)");
329+ var msgs = logEl.querySelectorAll(".kaa-msg.kaa-a");
330+ var last = msgs.length ? msgs[msgs.length - 1] : null;
331+ groups.forEach(function (g) {
332+ if (!(keepLast && last && last.contains(g))) g.classList.add("kaa-done");
333+ });
334+ }
236 function render() { 335 function render() {
237 logEl.innerHTML = ""; 336 logEl.innerHTML = "";
238 hello(); 337 hello();
239 log.forEach(function (m) { 338 log.forEach(function (m) {
240 m.role === "user" ? bubble("kaa-u", esc(m.content)) : bubble("kaa-a", md(m.content)); 339 m.role === "user" ? bubble("kaa-u", esc(m.content)) : bubble("kaa-a", md(m.content));
241 }); 340 });
341+ retireChoices(log.length && log[log.length - 1].role === "assistant");
242 scroll(); 342 scroll();
243 } 343 }
244 344
@@ -385,6 +485,7 @@
385 busy = true; 485 busy = true;
386 send.disabled = true; 486 send.disabled = true;
387 ta.value = ""; 487 ta.value = "";
488+ retireChoices(false); // un nouveau message clôt les choix en suspens
388 log.push({ role: "user", content: q }); 489 log.push({ role: "user", content: q });
389 save(); 490 save();
390 bubble("kaa-u", esc(q)); 491 bubble("kaa-u", esc(q));
@@ -453,6 +554,13 @@
453 ta.addEventListener("keydown", function (e) { 554 ta.addEventListener("keydown", function (e) {
454 if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(); } 555 if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(); }
455 }); 556 });
557+ logEl.addEventListener("click", function (e) { /* clic sur un bouton de choix (v4) */
558+ var b = e.target && e.target.closest ? e.target.closest(".kaa-choice") : null;
559+ if (!b || busy) return;
560+ var g = b.closest(".kaa-choices");
561+ if (g && g.classList.contains("kaa-done")) return;
562+ ta.value = b.textContent;
563+ ask();
564+ });
456 })(); 565 })();
457− 566+/*KA_ANALYTICS*/(function(){try{if(window.__kaAn)return;window.__kaAn=1;var k="KA_VID",v=localStorage.getItem(k);if(!v){v=Date.now().toString(36)+Math.random().toString(36).slice(2,8);localStorage.setItem(k,v);}var b=function(){var i=new Image();i.src="https://www.administration-ka.com/collect?site=crea-ka&vid="+v+"&p="+encodeURIComponent(location.pathname)+"&t="+Date.now();};b();setInterval(b,60000);}catch(e){}})();
458−;/*KA_ANALYTICS*/(function(){try{if(window.__kaAn)return;window.__kaAn=1;var k="KA_VID",v=localStorage.getItem(k);if(!v){v=Date.now().toString(36)+Math.random().toString(36).slice(2,8);localStorage.setItem(k,v);}var b=function(){var i=new Image();i.src="https://www.administration-ka.com/collect?site=crea-ka&vid="+v+"&p="+encodeURIComponent(location.pathname)+"&t="+Date.now();};b();setInterval(b,60000);}catch(e){}})();
459 567
Menu

Theme