widget ka-agent v4.2 : extraction cartes/choix par equilibrage d accolades — sync ka-ui 39e968d
1 changed file +31 −15
modified
frontend/ka-agent.js
+31 −15
@@ -20,6 +20,10 @@ | ||
| 20 | 20 | * de clarification (bloc ```ka-choix — {question, options[]}) rendus en |
| 21 | 21 | * boutons : un clic envoie l'option comme message ; seuls les boutons de la |
| 22 | 22 | * dernière bulle assistante restent actifs. |
| 23 | + * v4.2 (2026-08-25) : extraction des cartes/choix par ÉQUILIBRAGE D'ACCOLADES | |
| 24 | + * (jsonObjects) — tous les objets JSON d'un bloc sont trouvés quelle que soit | |
| 25 | + * la mise en forme (une ligne, aéré, tableau) et un bloc tronqué en plein | |
| 26 | + * streaming rend toutes ses cartes complètes au lieu d'une seule. | |
| 23 | 27 | */ |
| 24 | 28 | (function () { |
| 25 | 29 | "use strict"; |
@@ -158,21 +162,34 @@ | ||
| 158 | 162 | "valoplex": ["ValoPlex", "#ff9f45", "#141814"] |
| 159 | 163 | }; |
| 160 | 164 | function httpUrl(u) { return typeof u === "string" && /^https:\/\//.test(u) ? u : ""; } |
| 161 | − function cards(lines) { | |
| 162 | − var objs = []; | |
| 163 | − lines.forEach(function (ln) { // format demandé : un JSON par ligne | |
| 164 | − ln = ln.trim(); | |
| 165 | − if (!ln) return; | |
| 166 | − try { objs.push(JSON.parse(ln)); } catch (e) {} // ligne incomplète (streaming) → ignorée | |
| 167 | − }); | |
| 168 | − if (!objs.length) { // filet : le modèle aère parfois son JSON | |
| 169 | − try { | |
| 170 | − var w = JSON.parse(lines.join("\n")); | |
| 171 | − objs = Array.isArray(w) ? w : [w]; | |
| 172 | − } catch (e) {} | |
| 165 | + /* Extrait chaque objet JSON de premier niveau d'un bloc ka-card, quelle que | |
| 166 | + soit sa mise en forme (un par ligne, aéré, tableau, bloc TRONQUÉ par le | |
| 167 | + streaming) : balayage à équilibrage d'accolades, hors chaînes. */ | |
| 168 | + function jsonObjects(txt) { | |
| 169 | + var objs = [], depth = 0, start = -1, inStr = false, escp = false; | |
| 170 | + for (var k = 0; k < txt.length; k++) { | |
| 171 | + var c = txt[k]; | |
| 172 | + if (inStr) { | |
| 173 | + if (escp) escp = false; | |
| 174 | + else if (c === "\\") escp = true; | |
| 175 | + else if (c === '"') inStr = false; | |
| 176 | + continue; | |
| 177 | + } | |
| 178 | + if (c === '"') { inStr = true; continue; } | |
| 179 | + if (c === "{") { if (!depth) start = k; depth++; } | |
| 180 | + else if (c === "}" && depth) { | |
| 181 | + depth--; | |
| 182 | + if (!depth && start >= 0) { | |
| 183 | + try { objs.push(JSON.parse(txt.slice(start, k + 1))); } catch (e) {} | |
| 184 | + start = -1; | |
| 185 | + } | |
| 186 | + } | |
| 173 | 187 | } |
| 188 | + return objs; // un objet incomplet en fin de bloc est simplement ignoré | |
| 189 | + } | |
| 190 | + function cards(lines) { | |
| 174 | 191 | var html = ""; |
| 175 | − objs.forEach(function (o) { | |
| 192 | + jsonObjects(lines.join("\n")).forEach(function (o) { | |
| 176 | 193 | if (!o || !o.titre || !httpUrl(o.lien)) return; |
| 177 | 194 | var pal = ECO[o.site] || ["Groupe KA", "var(--accent,#d9f26b)", "var(--on-accent,#141814)"]; |
| 178 | 195 | var img = httpUrl(o.image); |
@@ -191,8 +208,7 @@ | ||
| 191 | 208 | return html ? '<div class="kaa-cards">' + html + "</div>" : ""; |
| 192 | 209 | } |
| 193 | 210 | function choices(lines) { |
| 194 | − var o = null; | |
| 195 | − try { o = JSON.parse(lines.join("\n")); } catch (e) { return ""; } // bloc incomplet (streaming) | |
| 211 | + var o = jsonObjects(lines.join("\n"))[0] || null; // bloc incomplet (streaming) → rien | |
| 196 | 212 | if (!o || !Array.isArray(o.options) || !o.options.length) return ""; |
| 197 | 213 | var h = '<span class="kaa-choices">' + |
| 198 | 214 | (o.question ? '<span class="kaa-choices-q">' + esc(String(o.question)) + "</span>" : ""); |
| 199 | 215 | |