shadok-ai 0.2.31 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/index.html +30 -2
- package/public/notify.js +17 -9
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -2495,11 +2495,22 @@
|
|
|
2495
2495
|
pip + `</svg>`;
|
|
2496
2496
|
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
2497
2497
|
}
|
|
2498
|
+
/** Favicon « en train de bosser » : la marque + un arc qui tourne (angle animé). */
|
|
2499
|
+
function faviconWorkingSVG(angle) {
|
|
2500
|
+
const svg =
|
|
2501
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
|
|
2502
|
+
`<rect width="32" height="32" rx="7" fill="#14161d"/>` +
|
|
2503
|
+
`<text x="15" y="23" font-family="monospace" font-size="20" fill="#f0a848" text-anchor="middle">›</text>` +
|
|
2504
|
+
`<g transform="rotate(${angle} 24 8)"><circle cx="24" cy="8" r="6" fill="none" stroke="#f0a848" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="26 40"/></g>` +
|
|
2505
|
+
`</svg>`;
|
|
2506
|
+
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
2507
|
+
}
|
|
2498
2508
|
/** What each channel is asking for, as the pure module wants it. */
|
|
2499
2509
|
function attentionInput() {
|
|
2500
2510
|
return tabs.map((t) => ({
|
|
2501
2511
|
mood: t.btnEl.classList.contains("needs-answer") ? "needs-answer"
|
|
2502
|
-
: t.btnEl.classList.contains("unread") ? "unread"
|
|
2512
|
+
: t.btnEl.classList.contains("unread") ? "unread"
|
|
2513
|
+
: t.btnEl.classList.contains("working") ? "working" : null,
|
|
2503
2514
|
muted: !!t.muted,
|
|
2504
2515
|
}));
|
|
2505
2516
|
}
|
|
@@ -2508,10 +2519,14 @@
|
|
|
2508
2519
|
// costs no timer at all.
|
|
2509
2520
|
let blinkTimer = null;
|
|
2510
2521
|
let blinkPhase = 0;
|
|
2522
|
+
let workTimer = null; // anime le favicon « working » (spinner)
|
|
2523
|
+
let workAngle = 0;
|
|
2524
|
+
function stopWork() { if (workTimer) { clearInterval(workTimer); workTimer = null; } }
|
|
2511
2525
|
function paint() {
|
|
2512
2526
|
// The ESM bridge lands after the document is parsed (gotcha #10): until it
|
|
2513
2527
|
// does, keep the plain badge rather than throwing on the first paint.
|
|
2514
2528
|
if (!window.notifyState) {
|
|
2529
|
+
stopWork();
|
|
2515
2530
|
favicon.href = faviconSVG(null);
|
|
2516
2531
|
document.title = titleBase;
|
|
2517
2532
|
return;
|
|
@@ -2524,8 +2539,21 @@
|
|
|
2524
2539
|
away: document.hidden || !document.hasFocus(),
|
|
2525
2540
|
phase: blinkPhase,
|
|
2526
2541
|
});
|
|
2527
|
-
favicon.href = faviconSVG(s.color);
|
|
2528
2542
|
document.title = s.badge + titleBase;
|
|
2543
|
+
// « working » = favicon animé (spinner). Le module ne renvoie ce mode que si
|
|
2544
|
+
// RIEN de plus prioritaire (bloqué / non-lu) n'attend — la priorité est donc
|
|
2545
|
+
// gérée en amont : un agent qui attend gagne toujours sur un agent qui bosse.
|
|
2546
|
+
if (s.mode === "working") {
|
|
2547
|
+
if (blinkTimer) { clearInterval(blinkTimer); blinkTimer = null; blinkPhase = 0; }
|
|
2548
|
+
if (!workTimer) {
|
|
2549
|
+
const tick = () => { workAngle = (workAngle + 30) % 360; favicon.href = faviconWorkingSVG(workAngle); };
|
|
2550
|
+
tick();
|
|
2551
|
+
workTimer = setInterval(tick, 120);
|
|
2552
|
+
}
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
stopWork();
|
|
2556
|
+
favicon.href = faviconSVG(s.color);
|
|
2529
2557
|
if (s.blink && !blinkTimer) {
|
|
2530
2558
|
blinkTimer = setInterval(() => { blinkPhase ^= 1; paint(); }, window.BLINK_MS || 900);
|
|
2531
2559
|
} else if (!s.blink && blinkTimer) {
|
package/public/notify.js
CHANGED
|
@@ -46,19 +46,27 @@ export function notifyState(channels, view) {
|
|
|
46
46
|
|
|
47
47
|
let blocked = false;
|
|
48
48
|
let unread = false;
|
|
49
|
+
let working = false;
|
|
49
50
|
for (const c of channels || []) {
|
|
50
51
|
if (!c || c.muted) continue; // un canal muté ne remonte aucun signal global
|
|
51
52
|
if (c.mood === "needs-answer") blocked = true;
|
|
52
53
|
else if (c.mood === "unread") unread = true;
|
|
54
|
+
else if (c.mood === "working") working = true;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
// Priorité : « il y a quelque chose à faire » (bloqué) prime sur « quelque
|
|
58
|
+
// chose est arrivé » (non-lu), qui prime sur « ça bosse ». `mode` dit à
|
|
59
|
+
// l'appelant quel favicon dessiner (le mode "working" est ANIMÉ côté DOM).
|
|
60
|
+
if (blocked) {
|
|
61
|
+
// Seul un agent bloqué justifie de clignoter, et seulement si tu es ailleurs.
|
|
62
|
+
if (!away) return { color: RED, badge: "● ", blink: false, mode: "blocked" };
|
|
63
|
+
return phase
|
|
64
|
+
? { color: RED_DIM, badge: "◉ ", blink: true, mode: "blocked" }
|
|
65
|
+
: { color: RED, badge: "● ", blink: true, mode: "blocked" };
|
|
66
|
+
}
|
|
67
|
+
if (unread) return { color: AMBER, badge: "● ", blink: false, mode: "unread" };
|
|
68
|
+
// « working » n'a ni couleur de pip ni badge : le favicon est animé (spinner)
|
|
69
|
+
// par l'appelant tant que le mode vaut "working".
|
|
70
|
+
if (working) return { color: null, badge: "", blink: false, mode: "working" };
|
|
71
|
+
return { color: null, badge: "", blink: false, mode: null };
|
|
64
72
|
}
|