SPB Git forge

spb/api-ka

Public

API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com

48commits 1branches 0releases
5.9 MBsize
maindefault branch
19 days agolast push
Python 60.9% HTML 21% TypeScript 7.3% JavaScript 5.2% CSS 4.8% Shell 0.8%

ka-agent v3 : bulle déplaçable par l'usager (drag pointer events + position mémorisée par site) — sync ka-ui 4e63c53

Simon-Pierre Boucher committed 1 mo ago (Aug 22, 2026) parent e548e0d

1 changed file +78 −2

modified src/api/web/ka-agent.js +78 −2
@@ -10,6 +10,10 @@
10 10 * streaming (titres, listes, tableaux, code, liens nommés, citations).
11 11 * Flux SSE en direct depuis le backend central (texte token par token +
12 12 * indicateur d'outil). Historique conservé en localStorage (par site).
13 + * v3 (2026-08-22) : bulle DÉPLAÇABLE au doigt/à la souris (pointer events,
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
16 + * fenêtré le panneau s'ancre du côté où se trouve la bulle.
13 17 */
14 18 (function () {
15 19 "use strict";
@@ -25,8 +29,10 @@
25 29 border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b);
26 30 color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif;
27 31 cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center;
28 justify-content:center;transition:transform .15s;touch-action:manipulation}
32 + justify-content:center;transition:transform .15s;touch-action:none}
29 33 .kaa-btn:hover{transform:translate(-2px,-2px)}
34 + .kaa-btn.kaa-drag{transition:none;cursor:grabbing}
35 + .kaa-btn.kaa-drag:hover{transform:none}
30 36 .kaa-panel{position:fixed;z-index:2147483001;display:none;flex-direction:column;background:var(--paper,#f5f3ee);
31 37 overflow:hidden;font-family:Inter,system-ui,sans-serif;touch-action:manipulation}
32 38 .kaa-panel.kaa-open{display:flex}
@@ -34,6 +40,7 @@
34 40 .kaa-panel:not(.kaa-full){right:16px;bottom:calc(86px + env(safe-area-inset-bottom,0px));width:min(392px,calc(100vw - 24px));
35 41 height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px));
36 42 border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85)}
43 + .kaa-panel.kaa-left:not(.kaa-full){right:auto;left:16px}
37 44 html.kaa-lock,html.kaa-lock body{overflow:hidden!important;overscroll-behavior:none;touch-action:none}
38 45 .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814);
39 46 color:var(--paper,#f5f3ee);flex:none;padding-top:max(11px,env(safe-area-inset-top,0px))}
@@ -279,7 +286,76 @@
279 286 fitVV();
280 287 }
281 288
282 btn.addEventListener("click", function () {
289 + /* ---------- bulle déplaçable (v3) ----------
290 + pointerdown sur la bulle → si le doigt/curseur bouge de plus de 6 px c'est
291 + un GLISSER (la bulle suit, bornée à 8 px des bords) ; sinon c'est un CLIC
292 + (ouverture normale). Position mémorisée par site (localStorage) et
293 + re-bornée quand le viewport change. En mode fenêtré, le panneau s'ancre à
294 + gauche si la bulle est dans la moitié gauche de l'écran. */
295 + var POSKEY = "ka-agent-pos-" + SITE;
296 + var drag = { on: false, moved: false, id: null, sx: 0, sy: 0, bx: 0, by: 0 };
297 +
298 + function clampPos(l, t) {
299 + var m = 8, w = btn.offsetWidth || 58, h = btn.offsetHeight || 58;
300 + return {
301 + l: Math.min(Math.max(l, m), Math.max(m, window.innerWidth - w - m)),
302 + t: Math.min(Math.max(t, m), Math.max(m, window.innerHeight - h - m)),
303 + };
304 + }
305 + function applyPos(l, t) {
306 + var p = clampPos(l, t);
307 + btn.style.left = p.l + "px";
308 + btn.style.top = p.t + "px";
309 + btn.style.right = "auto";
310 + btn.style.bottom = "auto";
311 + panel.classList.toggle("kaa-left", p.l + (btn.offsetWidth || 58) / 2 < window.innerWidth / 2);
312 + }
313 + function savePos() {
314 + var r = btn.getBoundingClientRect();
315 + try { localStorage.setItem(POSKEY, JSON.stringify({ l: r.left, t: r.top })); } catch (e) {}
316 + }
317 + btn.addEventListener("pointerdown", function (e) {
318 + if (!e.isPrimary) return;
319 + drag.on = true; drag.moved = false; drag.id = e.pointerId;
320 + drag.sx = e.clientX; drag.sy = e.clientY;
321 + var r = btn.getBoundingClientRect();
322 + drag.bx = r.left; drag.by = r.top;
323 + try { btn.setPointerCapture(e.pointerId); } catch (err) {} // capture immédiate : un glisser rapide sort de la bulle avant le 1er move
324 + });
325 + btn.addEventListener("pointermove", function (e) {
326 + if (!drag.on || e.pointerId !== drag.id) return;
327 + var dx = e.clientX - drag.sx, dy = e.clientY - drag.sy;
328 + if (!drag.moved && dx * dx + dy * dy < 36) return; // < 6 px : encore un clic
329 + if (!drag.moved) {
330 + drag.moved = true;
331 + btn.classList.add("kaa-drag");
332 + }
333 + applyPos(drag.bx + dx, drag.by + dy);
334 + });
335 + function dragEnd(e) {
336 + if (!drag.on || e.pointerId !== drag.id) return;
337 + drag.on = false;
338 + if (drag.moved) {
339 + btn.classList.remove("kaa-drag");
340 + savePos();
341 + setTimeout(function () { drag.moved = false; }, 0); // filet si le click ne suit pas
342 + }
343 + }
344 + btn.addEventListener("pointerup", dragEnd);
345 + btn.addEventListener("pointercancel", dragEnd);
346 + window.addEventListener("resize", function () {
347 + if (!btn.style.left) return;
348 + var r = btn.getBoundingClientRect();
349 + applyPos(r.left, r.top);
350 + });
351 + (function restorePos() {
352 + var p = null;
353 + try { p = JSON.parse(localStorage.getItem(POSKEY) || "null"); } catch (e) {}
354 + if (p && typeof p.l === "number" && typeof p.t === "number") applyPos(p.l, p.t);
355 + })();
356 +
357 + btn.addEventListener("click", function (e) {
358 + if (drag.moved) { drag.moved = false; e.preventDefault(); return; } // fin de glisser, pas un clic
283 359 panel.classList.add("kaa-open", "kaa-full"); // plein écran d'office
284 360 render();
285 361 if (fineInput.matches) ta.focus();
286 362