| 3 |
3 |
* Groupe KA. Vanilla JS, zéro dépendance, injecté sur les 13 sites. |
| 4 |
4 |
* <script>window.KA_AGENT={site:"lou-ka"}</script> |
| 5 |
5 |
* <script src="/ka-agent.js" defer></script> |
| 6 |
|
− * Bulle flottante en bas à droite → panneau de chat (bulle) → plein écran (⛶). |
|
6 |
+ * v2 (2026-08-19) : ouverture directement PLEIN ÉCRAN (desktop et mobile, |
|
7 |
+ * bouton ▭ pour réduire en fenêtre sur desktop) ; sur téléphone le panneau se |
|
8 |
+ * cale sur window.visualViewport (le clavier ne fait plus grossir/défiler la |
|
9 |
+ * page — fond verrouillé par body position:fixed) ; rendu MARKDOWN complet en |
|
10 |
+ * streaming (titres, listes, tableaux, code, liens nommés, citations). |
| 7 |
11 |
* Flux SSE en direct depuis le backend central (texte token par token + |
| 8 |
12 |
* indicateur d'outil). Historique conservé en localStorage (par site). |
| 9 |
13 |
*/ |
| 21 |
25 |
border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b); |
| 22 |
26 |
color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif; |
| 23 |
27 |
cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center; |
| 24 |
|
− justify-content:center;transition:transform .15s} |
|
28 |
+ justify-content:center;transition:transform .15s;touch-action:manipulation} |
| 25 |
29 |
.kaa-btn:hover{transform:translate(-2px,-2px)} |
| 26 |
|
− .kaa-panel{position:fixed;right:16px;bottom:calc(86px + env(safe-area-inset-bottom,0px));z-index:2147483001;width:min(392px,calc(100vw - 24px)); |
| 27 |
|
− height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); |
| 28 |
|
− border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85); |
| 29 |
|
− overflow:hidden;font-family:Inter,system-ui,sans-serif} |
|
30 |
+ .kaa-panel{position:fixed;z-index:2147483001;display:none;flex-direction:column;background:var(--paper,#f5f3ee); |
|
31 |
+ overflow:hidden;font-family:Inter,system-ui,sans-serif;touch-action:manipulation} |
| 30 |
32 |
.kaa-panel.kaa-open{display:flex} |
| 31 |
|
− .kaa-panel.kaa-full{right:0;bottom:0;width:100vw;height:100vh;height:100dvh;border-radius:0;border:0;box-shadow:none} |
| 32 |
|
− html.kaa-lock,html.kaa-lock body{overflow:hidden!important;overscroll-behavior:none} |
|
33 |
+ .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} |
|
34 |
+ .kaa-panel:not(.kaa-full){right:16px;bottom:calc(86px + env(safe-area-inset-bottom,0px));width:min(392px,calc(100vw - 24px)); |
|
35 |
+ height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px)); |
|
36 |
+ border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85)} |
|
37 |
+ html.kaa-lock,html.kaa-lock body{overflow:hidden!important;overscroll-behavior:none;touch-action:none} |
| 33 |
38 |
.kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); |
| 34 |
|
− color:var(--paper,#f5f3ee);flex:none} |
|
39 |
+ color:var(--paper,#f5f3ee);flex:none;padding-top:max(11px,env(safe-area-inset-top,0px))} |
| 35 |
40 |
.kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em} |
| 36 |
41 |
.kaa-head .kaa-ka{display:inline-block;background:var(--accent,#d9f26b);color:var(--on-accent,#141814); |
| 37 |
42 |
border-radius:6px;padding:1px 7px 2px;transform:rotate(-2deg);font-weight:700} |
| 38 |
43 |
.kaa-head small{font:700 9.5px "JetBrains Mono",monospace;letter-spacing:.09em;text-transform:uppercase;opacity:.65} |
| 39 |
44 |
.kaa-head .kaa-sp{flex:1} |
| 40 |
45 |
.kaa-hbtn{width:34px;height:34px;border:1.5px solid rgba(245,243,238,.4);border-radius:8px;background:none; |
| 41 |
|
− color:var(--paper,#f5f3ee);font-size:15px;cursor:pointer;line-height:1} |
|
46 |
+ color:var(--paper,#f5f3ee);font-size:15px;cursor:pointer;line-height:1;flex:none} |
| 42 |
47 |
.kaa-hbtn:hover{border-color:var(--accent,#d9f26b);color:var(--accent,#d9f26b)} |
| 43 |
48 |
.kaa-log{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px; |
| 44 |
49 |
overscroll-behavior:contain;-webkit-overflow-scrolling:touch} |
| 45 |
50 |
.kaa-msg{max-width:86%;padding:9px 13px;border:1.5px solid var(--ink,#141814);border-radius:12px; |
| 46 |
|
− font-size:13.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word} |
|
51 |
+ font-size:13.5px;line-height:1.55;word-break:break-word} |
|
52 |
+ .kaa-full .kaa-msg{max-width:min(86%,760px)} |
|
53 |
+ .kaa-full .kaa-log{align-items:stretch} |
| 47 |
54 |
.kaa-msg.kaa-u{align-self:flex-end;background:var(--accent,#d9f26b);color:var(--on-accent,#141814); |
| 48 |
|
− border-bottom-right-radius:4px} |
|
55 |
+ border-bottom-right-radius:4px;white-space:pre-wrap} |
| 49 |
56 |
.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)} |
| 50 |
|
− .kaa-msg.kaa-a a{color:inherit;text-decoration:underline;text-underline-offset:3px} |
| 51 |
|
− .kaa-msg.kaa-a b{font-weight:700} |
|
57 |
+ .kaa-msg.kaa-a a{color:inherit;text-decoration:underline;text-underline-offset:3px;font-weight:600} |
|
58 |
+ .kaa-msg.kaa-a p{margin:.35em 0} |
|
59 |
+ .kaa-msg.kaa-a p:first-child{margin-top:0} |
|
60 |
+ .kaa-msg.kaa-a p:last-child{margin-bottom:0} |
|
61 |
+ .kaa-msg.kaa-a h1,.kaa-msg.kaa-a h2,.kaa-msg.kaa-a h3,.kaa-msg.kaa-a h4{ |
|
62 |
+ font:700 14px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.01em;margin:.7em 0 .3em} |
|
63 |
+ .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} |
|
64 |
+ .kaa-msg.kaa-a h1{font-size:15.5px}.kaa-msg.kaa-a h2{font-size:14.5px} |
|
65 |
+ .kaa-msg.kaa-a ul,.kaa-msg.kaa-a ol{margin:.35em 0;padding-left:1.35em} |
|
66 |
+ .kaa-msg.kaa-a li{margin:.18em 0} |
|
67 |
+ .kaa-msg.kaa-a code{font:600 12px "JetBrains Mono",monospace;background:var(--surface-2,#f0eee7); |
|
68 |
+ border:1px solid rgba(20,24,20,.15);border-radius:5px;padding:.5px 4px} |
|
69 |
+ .kaa-msg.kaa-a pre{margin:.45em 0;padding:9px 11px;background:var(--ink,#141814);color:var(--paper,#f5f3ee); |
|
70 |
+ border-radius:9px;overflow-x:auto} |
|
71 |
+ .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} |
|
72 |
+ .kaa-msg.kaa-a blockquote{margin:.45em 0;padding:.15em .8em;border-left:3px solid var(--accent,#d9f26b); |
|
73 |
+ background:var(--surface-2,#faf9f5);color:var(--ink-2,#4d5551)} |
|
74 |
+ .kaa-msg.kaa-a hr{border:0;border-top:1.5px dashed rgba(20,24,20,.3);margin:.6em 0} |
|
75 |
+ .kaa-msg.kaa-a table{border-collapse:collapse;margin:.45em 0;font-size:12.5px;display:block;overflow-x:auto;max-width:100%} |
|
76 |
+ .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} |
|
77 |
+ .kaa-msg.kaa-a th{background:var(--accent,#d9f26b);color:var(--on-accent,#141814); |
|
78 |
+ font:700 11px "Space Grotesk",system-ui,sans-serif;letter-spacing:.02em} |
|
79 |
+ .kaa-msg.kaa-a td{background:#fff} |
| 52 |
80 |
.kaa-tool{align-self:flex-start;font:700 10px "JetBrains Mono",monospace;letter-spacing:.07em; |
| 53 |
81 |
text-transform:uppercase;color:var(--ink-2,#4d5551);padding:3px 10px;border:1.5px dashed var(--ink-2,#4d5551); |
| 54 |
82 |
border-radius:999px;background:var(--surface-2,#faf9f5)} |
| 55 |
83 |
.kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none; |
| 56 |
84 |
padding-bottom:calc(11px + env(safe-area-inset-bottom,0))} |
|
85 |
+ .kaa-full .kaa-in{justify-content:center} |
|
86 |
+ .kaa-full .kaa-in textarea{max-width:700px} |
| 57 |
87 |
.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; |
| 58 |
88 |
border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none} |
| 59 |
89 |
.kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; |
| 60 |
|
− background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer} |
|
90 |
+ background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer;touch-action:manipulation} |
| 61 |
91 |
.kaa-in button:disabled{opacity:.45} |
| 62 |
|
− .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px} |
|
92 |
+ .kaa-hello{font-size:12.5px;color:var(--ink-2,#4d5551);padding:4px 2px;max-width:760px} |
| 63 |
93 |
.kaa-dots::after{content:"…";animation:kaa-b 1.2s infinite} |
| 64 |
94 |
@keyframes kaa-b{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}} |
| 65 |
|
− @media (max-width:560px){.kaa-panel{right:8px;left:8px;width:auto}} |
|
95 |
+ @media (max-width:640px){.kaa-x1{display:none}} |
| 66 |
96 |
`; |
| 67 |
97 |
|
| 68 |
|
− /* ---------- markdown minimal (gras, liens, puces) — texte échappé d'abord ---------- */ |
|
98 |
+ /* ---------- markdown complet (rendu en streaming, texte échappé d'abord) ---------- */ |
| 69 |
99 |
function esc(s) { |
| 70 |
100 |
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
| 71 |
101 |
} |
| 72 |
|
− function md(s) { |
| 73 |
|
− return esc(s) |
| 74 |
|
− .replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>") |
| 75 |
|
− .replace(/(https?:\/\/[^\s<)"']+)/g, function (u) { |
| 76 |
|
− return '<a href="' + u + '" target="_blank" rel="noopener noreferrer">' + u.replace(/^https?:\/\/(www\.)?/, "").slice(0, 40) + "</a>"; |
| 77 |
|
− }); |
|
102 |
+ function inline(s) { |
|
103 |
+ s = esc(s); |
|
104 |
+ s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>"); |
|
105 |
+ s = s.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, |
|
106 |
+ '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'); |
|
107 |
+ s = s.replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>"); |
|
108 |
+ s = s.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\*)/g, "$1<i>$2</i>"); |
|
109 |
+ s = s.replace(/(^|[^"'>=\w])(https?:\/\/[^\s<)"']+)/g, function (m, pre, u) { |
|
110 |
+ return pre + '<a href="' + u + '" target="_blank" rel="noopener noreferrer">' + |
|
111 |
+ u.replace(/^https?:\/\/(www\.)?/, "").slice(0, 42) + "</a>"; |
|
112 |
+ }); |
|
113 |
+ return s; |
|
114 |
+ } |
|
115 |
+ function md(src) { |
|
116 |
+ var lines = String(src).split("\n"), out = [], i = 0, m; |
|
117 |
+ function para(buf) { if (buf.length) out.push("<p>" + buf.map(inline).join("<br>") + "</p>"); } |
|
118 |
+ while (i < lines.length) { |
|
119 |
+ var l = lines[i]; |
|
120 |
+ if (/^\s*```/.test(l)) { /* bloc de code */ |
|
121 |
+ var code = []; i++; |
|
122 |
+ while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]); |
|
123 |
+ i++; |
|
124 |
+ out.push("<pre><code>" + esc(code.join("\n")) + "</code></pre>"); |
|
125 |
+ } else if ((m = l.match(/^\s*(#{1,4})\s+(.*)$/))) { /* titres */ |
|
126 |
+ out.push("<h" + m[1].length + ">" + inline(m[2]) + "</h" + m[1].length + ">"); i++; |
|
127 |
+ } else if (/^\s*(---+|\*\*\*+)\s*$/.test(l)) { /* filet */ |
|
128 |
+ out.push("<hr>"); i++; |
|
129 |
+ } else if (/^\s*>|^\s*>/.test(l)) { /* citation */ |
|
130 |
+ var q = []; |
|
131 |
+ while (i < lines.length && /^\s*>/.test(lines[i])) q.push(lines[i++].replace(/^\s*>\s?/, "")); |
|
132 |
+ out.push("<blockquote>" + q.map(inline).join("<br>") + "</blockquote>"); |
|
133 |
+ } else if (/^\s*\|.*\|\s*$/.test(l)) { /* tableau */ |
|
134 |
+ var rows = []; |
|
135 |
+ while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])) { |
|
136 |
+ var cells = lines[i].trim().replace(/^\||\|$/g, "").split("|").map(function (c) { return c.trim(); }); |
|
137 |
+ if (!/^[:\-\s|]+$/.test(lines[i].replace(/\|/g, ""))) rows.push(cells); |
|
138 |
+ i++; |
|
139 |
+ } |
|
140 |
+ if (rows.length) { |
|
141 |
+ var t = "<table>"; |
|
142 |
+ rows.forEach(function (r, ri) { |
|
143 |
+ var tag = ri === 0 ? "th" : "td"; |
|
144 |
+ t += "<tr>" + r.map(function (c) { return "<" + tag + ">" + inline(c) + "</" + tag + ">"; }).join("") + "</tr>"; |
|
145 |
+ }); |
|
146 |
+ out.push(t + "</table>"); |
|
147 |
+ } |
|
148 |
+ } else if (/^\s*[-*+]\s+/.test(l)) { /* liste à puces */ |
|
149 |
+ var ul = []; |
|
150 |
+ while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) |
|
151 |
+ ul.push("<li>" + inline(lines[i++].replace(/^\s*[-*+]\s+/, "")) + "</li>"); |
|
152 |
+ out.push("<ul>" + ul.join("") + "</ul>"); |
|
153 |
+ } else if (/^\s*\d+[.)]\s+/.test(l)) { /* liste numérotée */ |
|
154 |
+ var ol = []; |
|
155 |
+ while (i < lines.length && /^\s*\d+[.)]\s+/.test(lines[i])) |
|
156 |
+ ol.push("<li>" + inline(lines[i++].replace(/^\s*\d+[.)]\s+/, "")) + "</li>"); |
|
157 |
+ out.push("<ol>" + ol.join("") + "</ol>"); |
|
158 |
+ } else if (!l.trim()) { i++; } /* ligne vide */ |
|
159 |
+ else { /* paragraphe */ |
|
160 |
+ var buf = []; |
|
161 |
+ while (i < lines.length && lines[i].trim() && |
|
162 |
+ !/^\s*(```|#{1,4}\s|[-*+]\s|\d+[.)]\s|>|\||---+\s*$)/.test(lines[i])) |
|
163 |
+ buf.push(lines[i++]); |
|
164 |
+ if (!buf.length) buf.push(lines[i++] || ""); |
|
165 |
+ para(buf); |
|
166 |
+ } |
|
167 |
+ } |
|
168 |
+ return out.join(""); |
| 78 |
169 |
} |
| 79 |
170 |
|
| 80 |
171 |
/* ---------- état ---------- */ |
| 98 |
189 |
panel.setAttribute("aria-label", "KA Agent"); |
| 99 |
190 |
panel.innerHTML = |
| 100 |
191 |
'<div class="kaa-head"><b>KA<span class="kaa-ka">Agent</span></b><small>Groupe KA · IA</small><span class="kaa-sp"></span>' + |
| 101 |
|
− '<button class="kaa-hbtn kaa-x1" title="Plein écran" aria-label="Plein écran">⛶</button>' + |
|
192 |
+ '<button class="kaa-hbtn kaa-x1" title="Réduire / agrandir" aria-label="Réduire ou agrandir">▭</button>' + |
| 102 |
193 |
'<button class="kaa-hbtn kaa-x2" title="Fermer" aria-label="Fermer">✕</button></div>' + |
| 103 |
194 |
'<div class="kaa-log"></div>' + |
| 104 |
|
− '<div class="kaa-in"><textarea rows="1" placeholder="Posez votre question…" aria-label="Votre question"></textarea>' + |
|
195 |
+ '<div class="kaa-in"><textarea rows="1" enterkeyhint="send" autocapitalize="sentences" autocomplete="off" placeholder="Posez votre question…" aria-label="Votre question"></textarea>' + |
| 105 |
196 |
"<button aria-label=\"Envoyer\">➤</button></div>"; |
| 106 |
197 |
document.body.appendChild(btn); |
| 107 |
198 |
document.body.appendChild(panel); |
| 132 |
223 |
if (log.length) return; |
| 133 |
224 |
var d = document.createElement("div"); |
| 134 |
225 |
d.className = "kaa-hello"; |
| 135 |
|
− 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."; |
|
226 |
+ 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."; |
| 136 |
227 |
logEl.appendChild(d); |
| 137 |
228 |
} |
| 138 |
229 |
function render() { |
| 139 |
230 |
logEl.innerHTML = ""; |
| 140 |
231 |
hello(); |
| 141 |
|
− log.forEach(function (m) { bubble(m.role === "user" ? "kaa-u" : "kaa-a", md(m.content)); }); |
|
232 |
+ log.forEach(function (m) { |
|
233 |
+ m.role === "user" ? bubble("kaa-u", esc(m.content)) : bubble("kaa-a", md(m.content)); |
|
234 |
+ }); |
| 142 |
235 |
scroll(); |
| 143 |
236 |
} |
| 144 |
237 |
|
| 145 |
|
− /* ---------- ouverture / plein écran ---------- */ |
| 146 |
|
− /* Sur téléphone : panneau plein écran d'office + scroll d'arrière-plan |
| 147 |
|
− verrouillé tant qu'il est ouvert (html.kaa-lock). Pas de focus clavier |
| 148 |
|
− forcé au tap (le clavier ne s'ouvre que si l'utilisateur touche le champ). */ |
| 149 |
|
− var mobileMq = window.matchMedia("(max-width: 640px)"); |
|
238 |
+ /* ---------- plein écran + clavier mobile ---------- |
|
239 |
+ Ouverture = PLEIN ÉCRAN direct (desktop et mobile). ▭ réduit en fenêtre |
|
240 |
+ (desktop seulement — caché sur mobile). Tant que le plein écran est ouvert, |
|
241 |
+ l'arrière-plan est GELÉ (body position:fixed, scroll restauré à la |
|
242 |
+ fermeture) et le panneau se cale sur window.visualViewport : quand le |
|
243 |
+ clavier s'ouvre, le panneau rétrécit à la place de laisser iOS |
|
244 |
+ zoomer/pousser la page. Pas de focus clavier forcé au tap sur mobile. */ |
| 150 |
245 |
var fineInput = window.matchMedia("(hover: hover) and (pointer: fine)"); |
| 151 |
|
− function syncLock() { |
| 152 |
|
− var open = panel.classList.contains("kaa-open"); |
| 153 |
|
− if (open && mobileMq.matches) panel.classList.add("kaa-full"); |
| 154 |
|
− document.documentElement.classList.toggle( |
| 155 |
|
− "kaa-lock", open && panel.classList.contains("kaa-full")); |
|
246 |
+ var vv = window.visualViewport; |
|
247 |
+ var savedY = 0; |
|
248 |
+ |
|
249 |
+ function isOpen() { return panel.classList.contains("kaa-open"); } |
|
250 |
+ function isFull() { return panel.classList.contains("kaa-full"); } |
|
251 |
+ |
|
252 |
+ function fitVV() { |
|
253 |
+ if (!vv || !isOpen() || !isFull()) { panel.style.height = ""; panel.style.top = ""; return; } |
|
254 |
+ panel.style.top = Math.round(vv.offsetTop) + "px"; |
|
255 |
+ panel.style.height = Math.round(vv.height) + "px"; |
|
256 |
+ scroll(); |
| 156 |
257 |
} |
| 157 |
|
− btn.addEventListener("click", function () { |
| 158 |
|
− panel.classList.toggle("kaa-open"); |
| 159 |
|
− if (panel.classList.contains("kaa-open")) { |
| 160 |
|
− render(); |
| 161 |
|
− if (fineInput.matches) ta.focus(); |
|
258 |
+ if (vv) { |
|
259 |
+ vv.addEventListener("resize", fitVV); |
|
260 |
+ vv.addEventListener("scroll", fitVV); |
|
261 |
+ } |
|
262 |
+ |
|
263 |
+ function lockBg(on) { |
|
264 |
+ var b = document.body, h = document.documentElement; |
|
265 |
+ if (on && !h.classList.contains("kaa-lock")) { |
|
266 |
+ savedY = window.scrollY || 0; |
|
267 |
+ h.classList.add("kaa-lock"); |
|
268 |
+ b.style.position = "fixed"; |
|
269 |
+ b.style.top = -savedY + "px"; |
|
270 |
+ b.style.left = "0"; b.style.right = "0"; b.style.width = "100%"; |
|
271 |
+ } else if (!on && h.classList.contains("kaa-lock")) { |
|
272 |
+ h.classList.remove("kaa-lock"); |
|
273 |
+ b.style.position = ""; b.style.top = ""; b.style.left = ""; b.style.right = ""; b.style.width = ""; |
|
274 |
+ window.scrollTo(0, savedY); |
| 162 |
275 |
} |
| 163 |
|
− syncLock(); |
|
276 |
+ } |
|
277 |
+ function sync() { |
|
278 |
+ lockBg(isOpen() && isFull()); |
|
279 |
+ fitVV(); |
|
280 |
+ } |
|
281 |
+ |
|
282 |
+ btn.addEventListener("click", function () { |
|
283 |
+ panel.classList.add("kaa-open", "kaa-full"); // plein écran d'office |
|
284 |
+ render(); |
|
285 |
+ if (fineInput.matches) ta.focus(); |
|
286 |
+ sync(); |
| 164 |
287 |
}); |
| 165 |
288 |
panel.querySelector(".kaa-x2").addEventListener("click", function () { |
| 166 |
289 |
panel.classList.remove("kaa-open", "kaa-full"); |
| 167 |
|
− syncLock(); |
|
290 |
+ sync(); |
| 168 |
291 |
}); |
| 169 |
292 |
panel.querySelector(".kaa-x1").addEventListener("click", function () { |
| 170 |
293 |
panel.classList.toggle("kaa-full"); |
| 171 |
|
− syncLock(); |
|
294 |
+ sync(); |
| 172 |
295 |
scroll(); |
| 173 |
296 |
}); |
|
297 |
+ document.addEventListener("keydown", function (e) { |
|
298 |
+ if (e.key === "Escape" && isOpen()) { |
|
299 |
+ panel.classList.remove("kaa-open", "kaa-full"); |
|
300 |
+ sync(); |
|
301 |
+ } |
|
302 |
+ }); |
| 174 |
303 |
|
| 175 |
304 |
/* ---------- envoi + flux SSE ---------- */ |
| 176 |
305 |
var busy = false; |
| 182 |
311 |
ta.value = ""; |
| 183 |
312 |
log.push({ role: "user", content: q }); |
| 184 |
313 |
save(); |
| 185 |
|
− bubble("kaa-u", md(q)); |
|
314 |
+ bubble("kaa-u", esc(q)); |
| 186 |
315 |
var out = bubble("kaa-a", '<span class="kaa-dots"></span>'); |
| 187 |
316 |
var acc = ""; |
| 188 |
317 |
var chips = []; |
| 211 |
340 |
try { data = JSON.parse(dm); } catch (e) {} |
| 212 |
341 |
if (ev === "delta" && data.text) { |
| 213 |
342 |
acc += data.text; |
| 214 |
|
− out.innerHTML = md(acc); |
|
343 |
+ out.innerHTML = md(acc); // markdown re-rendu à chaque delta |
| 215 |
344 |
scroll(); |
| 216 |
345 |
} else if (ev === "tool") { |
| 217 |
346 |
chips.push(toolChip(data.name || "recherche")); |