shadok-ai 0.8.18 → 0.8.19
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 +98 -43
- package/public/notify.js +2 -2
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
fields (see the coarse-pointer query) fix it at the source instead. -->
|
|
10
10
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
11
11
|
<title>shadok-ai — cockpit</title>
|
|
12
|
-
|
|
12
|
+
<!-- The idle favicon: a resting Shadok. paint() swaps in the pumping / "!" / "?"
|
|
13
|
+
variants once the JS runs; this is only the pre-script icon. -->
|
|
14
|
+
<link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2314161d%22%2F%3E%3Cg%20fill%3D%22%23f0a848%22%3E%3Crect%20x%3D%2211.4%22%20y%3D%2221%22%20width%3D%221.7%22%20height%3D%229%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%2217.4%22%20y%3D%2221%22%20width%3D%221.7%22%20height%3D%229%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%229.2%22%20y%3D%2228.7%22%20width%3D%224.6%22%20height%3D%221.7%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%2216.4%22%20y%3D%2228.7%22%20width%3D%224.6%22%20height%3D%221.7%22%20rx%3D%220.85%22%2F%3E%3C%2Fg%3E%3Cg%20fill%3D%22%23f0a848%22%3E%3Cellipse%20cx%3D%2215%22%20cy%3D%2216.8%22%20rx%3D%227.4%22%20ry%3D%227.6%22%2F%3E%3Cg%20transform%3D%22rotate(6%2019.6%2015.6)%22%3E%3Crect%20x%3D%2218.6%22%20y%3D%2215%22%20width%3D%222.1%22%20height%3D%228.4%22%20rx%3D%221.05%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M14.6%2010%20C%2014.6%206.6%2C%2016.6%204.8%2C%2018.9%204.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%223%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%2219.5%22%20cy%3D%224.7%22%20r%3D%223%22%2F%3E%3Cpath%20d%3D%22M22.1%203.5%20L26.4%205%20L22.1%206.5%20Z%22%2F%3E%3Ccircle%20cx%3D%2220.2%22%20cy%3D%224.2%22%20r%3D%220.95%22%20fill%3D%22%2314161d%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
|
13
15
|
<style>
|
|
14
16
|
:root {
|
|
15
17
|
--bg: #14161d;
|
|
@@ -1005,7 +1007,7 @@
|
|
|
1005
1007
|
.freetext-row input { flex: 1; }
|
|
1006
1008
|
#thinking { display: none; align-self: flex-start; color: var(--text-dim); font-family: var(--mono); font-size: 12.5px; padding: 0 22px 6px; }
|
|
1007
1009
|
#thinking.visible { display: flex; align-items: center; gap: 6px; }
|
|
1008
|
-
/* Shimmering text (an amber sweep) +
|
|
1010
|
+
/* Shimmering text (an amber sweep) + a little Shadok pumping alongside. */
|
|
1009
1011
|
#thinking .tw-text {
|
|
1010
1012
|
background: linear-gradient(90deg, var(--text-dim) 30%, var(--phosphor) 50%, var(--text-dim) 70%);
|
|
1011
1013
|
background-size: 220% 100%;
|
|
@@ -1014,17 +1016,20 @@
|
|
|
1014
1016
|
animation: tw-shimmer 1.9s linear infinite;
|
|
1015
1017
|
}
|
|
1016
1018
|
@keyframes tw-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1019
|
+
/* The pumping Shadok: `color` feeds its `currentColor` fill so it follows the
|
|
1020
|
+
palette. The body bobs and the arm swings — the pump — on the same beat. */
|
|
1021
|
+
#thinking .tw-shadok { display: inline-flex; color: var(--amber); }
|
|
1022
|
+
#thinking .tw-shadok svg { width: 20px; height: 20px; overflow: visible; }
|
|
1023
|
+
/* Body bobs with translate (no transform-origin needed — that unit is a
|
|
1024
|
+
cross-browser trap on SVG). The arm swings from its own top via fill-box, so
|
|
1025
|
+
it reads as working a pump handle. */
|
|
1026
|
+
#thinking .tw-shadok .sh-body { animation: tw-pump 0.7s ease-in-out infinite; }
|
|
1027
|
+
#thinking .tw-shadok .sh-arm { animation: tw-arm 0.7s ease-in-out infinite; transform-box: fill-box; transform-origin: top center; }
|
|
1028
|
+
@keyframes tw-pump { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(1.5px); } }
|
|
1029
|
+
@keyframes tw-arm { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(28deg); } }
|
|
1025
1030
|
@media (prefers-reduced-motion: reduce) {
|
|
1026
1031
|
#thinking .tw-text { animation: none; color: var(--phosphor); -webkit-text-fill-color: currentColor; }
|
|
1027
|
-
#thinking .tw-
|
|
1032
|
+
#thinking .tw-shadok .sh-body, #thinking .tw-shadok .sh-arm { animation: none; }
|
|
1028
1033
|
}
|
|
1029
1034
|
|
|
1030
1035
|
/* Composer */
|
|
@@ -1900,7 +1905,9 @@
|
|
|
1900
1905
|
</div>
|
|
1901
1906
|
|
|
1902
1907
|
<div id="transcripts"></div>
|
|
1903
|
-
|
|
1908
|
+
<!-- The Shadok is injected into .tw-shadok by the script (one drawing shared
|
|
1909
|
+
with the favicon, `shadokInner`); it pumps via CSS. -->
|
|
1910
|
+
<div id="thinking"><span class="tw-text">Shadok is pumping</span><span class="tw-shadok" aria-hidden="true"></span></div>
|
|
1904
1911
|
|
|
1905
1912
|
<form id="composer" hidden>
|
|
1906
1913
|
<textarea id="promptInput" rows="1"
|
|
@@ -3160,33 +3167,67 @@
|
|
|
3160
3167
|
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
3161
3168
|
return v || fallback;
|
|
3162
3169
|
}
|
|
3163
|
-
/**
|
|
3164
|
-
|
|
3165
|
-
*
|
|
3166
|
-
*
|
|
3167
|
-
|
|
3170
|
+
/** The Shadok itself — the cockpit's mascot, which famously PUMPS. One drawing
|
|
3171
|
+
* serves every use: the favicon states here and the in-page "pumping"
|
|
3172
|
+
* indicator. `dx` shifts it left to clear room for a "!" / "?"; `bob`/`armRot`
|
|
3173
|
+
* pose a single frame of the pump (the favicon animates by re-generating with
|
|
3174
|
+
* new values, like the old spinner did); `anim` instead tags the body and arm
|
|
3175
|
+
* groups so CSS can drive them (used only in the DOM indicator, never a data
|
|
3176
|
+
* URI — CSS animation inside a favicon is unreliable). Legs stay planted. */
|
|
3177
|
+
function shadokInner(fill, { dx = 0, bob = 0, armRot = 6, anim = false } = {}) {
|
|
3168
3178
|
const base = themeColor("--bg", "#14161d");
|
|
3169
|
-
const
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3179
|
+
const bodyCls = anim ? ' class="sh-body"' : "";
|
|
3180
|
+
const armCls = anim ? ' class="sh-arm"' : "";
|
|
3181
|
+
const bodyTf = anim ? "" : ` transform="translate(0 ${bob})"`;
|
|
3182
|
+
const armTf = anim ? "" : ` transform="rotate(${armRot} 19.6 15.6)"`;
|
|
3183
|
+
return (
|
|
3184
|
+
`<g transform="translate(${dx} 0)">` +
|
|
3185
|
+
// legs + feet (planted, not part of the bob)
|
|
3186
|
+
`<g fill="${fill}">` +
|
|
3187
|
+
`<rect x="11.4" y="21" width="1.7" height="9" rx="0.85"/><rect x="17.4" y="21" width="1.7" height="9" rx="0.85"/>` +
|
|
3188
|
+
`<rect x="9.2" y="28.7" width="4.6" height="1.7" rx="0.85"/><rect x="16.4" y="28.7" width="4.6" height="1.7" rx="0.85"/>` +
|
|
3189
|
+
`</g>` +
|
|
3190
|
+
// body + pumping arm + long neck + head + beak + eye
|
|
3191
|
+
`<g${bodyCls} fill="${fill}"${bodyTf}>` +
|
|
3192
|
+
`<ellipse cx="15" cy="16.8" rx="7.4" ry="7.6"/>` +
|
|
3193
|
+
`<g${armCls}${armTf}><rect x="18.6" y="15" width="2.1" height="8.4" rx="1.05"/></g>` +
|
|
3194
|
+
`<path d="M14.6 10 C 14.6 6.6, 16.6 4.8, 18.9 4.8" stroke="${fill}" stroke-width="3" fill="none" stroke-linecap="round"/>` +
|
|
3195
|
+
`<circle cx="19.5" cy="4.7" r="3"/><path d="M22.1 3.5 L26.4 5 L22.1 6.5 Z"/>` +
|
|
3196
|
+
`<circle cx="20.2" cy="4.2" r="0.95" fill="${base}"/>` +
|
|
3197
|
+
`</g></g>`
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
function faviconWrap(inner) {
|
|
3180
3201
|
const base = themeColor("--bg", "#14161d");
|
|
3202
|
+
return (
|
|
3203
|
+
"data:image/svg+xml," +
|
|
3204
|
+
encodeURIComponent(
|
|
3205
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
|
|
3206
|
+
`<rect width="32" height="32" rx="7" fill="${base}"/>${inner}</svg>`,
|
|
3207
|
+
)
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
/** The favicon: an accent Shadok on the dark ground. A waiting state adds a
|
|
3211
|
+
* bold "!" (something to answer) or "?" (something to read) in its colour, and
|
|
3212
|
+
* the Shadok slides left to make room — the coloured glyph is what the user
|
|
3213
|
+
* asked to distinguish the two. No symbol → the idle Shadok. */
|
|
3214
|
+
function faviconSVG(symbol, symColor) {
|
|
3181
3215
|
const accent = themeColor("--amber", "#f0a848");
|
|
3182
|
-
const
|
|
3183
|
-
const
|
|
3184
|
-
`<
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3216
|
+
const shadok = shadokInner(accent, symbol ? { dx: -3.6 } : {});
|
|
3217
|
+
const sym = symbol
|
|
3218
|
+
? `<text x="25.8" y="13" font-family="monospace" font-size="15" font-weight="800" fill="${symColor || accent}" text-anchor="middle">${symbol}</text>`
|
|
3219
|
+
: "";
|
|
3220
|
+
return faviconWrap(shadok + sym);
|
|
3221
|
+
}
|
|
3222
|
+
/** The "working" favicon: the Shadok pumping. `phase` (0..PUMP_FRAMES-1) rides
|
|
3223
|
+
* a cycle — the body bobs and the arm swings, re-generated each tick. */
|
|
3224
|
+
const PUMP_FRAMES = 8;
|
|
3225
|
+
function faviconPumpingSVG(phase) {
|
|
3226
|
+
const accent = themeColor("--amber", "#f0a848");
|
|
3227
|
+
const p = (Math.sin((phase / PUMP_FRAMES) * 2 * Math.PI - Math.PI / 2) + 1) / 2; // 0..1..0
|
|
3228
|
+
const bob = +(p * 1.8).toFixed(2);
|
|
3229
|
+
const armRot = Math.round(-8 + p * 32); // -8 (up) .. +24 (down)
|
|
3230
|
+
return faviconWrap(shadokInner(accent, { bob, armRot }));
|
|
3190
3231
|
}
|
|
3191
3232
|
/** What each channel is asking for, as the pure module wants it. */
|
|
3192
3233
|
function attentionInput() {
|
|
@@ -3202,8 +3243,8 @@
|
|
|
3202
3243
|
// costs no timer at all.
|
|
3203
3244
|
let blinkTimer = null;
|
|
3204
3245
|
let blinkPhase = 0;
|
|
3205
|
-
let workTimer = null; // animates the "working" favicon (
|
|
3206
|
-
let
|
|
3246
|
+
let workTimer = null; // animates the "working" favicon (the pumping Shadok)
|
|
3247
|
+
let workPhase = 0;
|
|
3207
3248
|
function stopWork() { if (workTimer) { clearInterval(workTimer); workTimer = null; } }
|
|
3208
3249
|
function paint() {
|
|
3209
3250
|
// The ESM bridge lands after the document is parsed (gotcha #10): until it
|
|
@@ -3223,20 +3264,24 @@
|
|
|
3223
3264
|
phase: blinkPhase,
|
|
3224
3265
|
});
|
|
3225
3266
|
document.title = s.badge + titleBase;
|
|
3226
|
-
// "working" = animated
|
|
3267
|
+
// "working" = the animated pumping Shadok. The module only returns that mode
|
|
3227
3268
|
// when NOTHING of higher priority (blocked / unread) is waiting — priority is
|
|
3228
3269
|
// therefore handled upstream: a waiting agent always beats a busy one.
|
|
3229
3270
|
if (s.mode === "working") {
|
|
3230
3271
|
if (blinkTimer) { clearInterval(blinkTimer); blinkTimer = null; blinkPhase = 0; }
|
|
3231
3272
|
if (!workTimer) {
|
|
3232
|
-
const tick = () => {
|
|
3273
|
+
const tick = () => { workPhase = (workPhase + 1) % PUMP_FRAMES; favicon.href = faviconPumpingSVG(workPhase); };
|
|
3233
3274
|
tick();
|
|
3234
|
-
workTimer = setInterval(tick,
|
|
3275
|
+
workTimer = setInterval(tick, 110);
|
|
3235
3276
|
}
|
|
3236
3277
|
return;
|
|
3237
3278
|
}
|
|
3238
3279
|
stopWork();
|
|
3239
|
-
|
|
3280
|
+
// A waiting Shadok carries the glyph the user asked for: "!" when it needs an
|
|
3281
|
+
// answer, "?" when something's there to read. The colour is notifyState's
|
|
3282
|
+
// (RED / RED_DIM on the away-blink, AMBER for unread); idle → no glyph.
|
|
3283
|
+
const symbol = s.mode === "blocked" ? "!" : s.mode === "unread" ? "?" : null;
|
|
3284
|
+
favicon.href = faviconSVG(symbol, s.color);
|
|
3240
3285
|
if (s.blink && !blinkTimer) {
|
|
3241
3286
|
blinkTimer = setInterval(() => { blinkPhase ^= 1; paint(); }, window.BLINK_MS || 900);
|
|
3242
3287
|
} else if (!s.blink && blinkTimer) {
|
|
@@ -3246,6 +3291,16 @@
|
|
|
3246
3291
|
}
|
|
3247
3292
|
}
|
|
3248
3293
|
function refreshBadge() { paint(); }
|
|
3294
|
+
// The in-page "pumping" Shadok, drawn once from the SAME `shadokInner` as the
|
|
3295
|
+
// favicon. `currentColor` lets the CSS palette (var(--amber)) tint it, so it
|
|
3296
|
+
// follows a theme change with no redraw; `anim` tags the body/arm for the CSS
|
|
3297
|
+
// pump. The element sits earlier in the document, so it's here to fill now.
|
|
3298
|
+
{
|
|
3299
|
+
const twShadok = document.querySelector("#thinking .tw-shadok");
|
|
3300
|
+
if (twShadok)
|
|
3301
|
+
twShadok.innerHTML =
|
|
3302
|
+
`<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">${shadokInner("currentColor", { anim: true })}</svg>`;
|
|
3303
|
+
}
|
|
3249
3304
|
// Coming back must stop the blink at once, not at the next tick. Both signals
|
|
3250
3305
|
// matter: switching tabs fires visibilitychange, switching application only
|
|
3251
3306
|
// fires focus/blur.
|
|
@@ -3393,7 +3448,7 @@
|
|
|
3393
3448
|
$("chanBarName").title = t.cwd || "";
|
|
3394
3449
|
updateSendDisabled(); // t === active here; also honours a pending upload
|
|
3395
3450
|
thinking.classList.toggle("visible", t.status === "busy");
|
|
3396
|
-
// Showing/hiding "Shadok is
|
|
3451
|
+
// Showing/hiding "Shadok is pumping" changes the transcript area's height:
|
|
3397
3452
|
// without re-pinning to the bottom, the last message slips below the fold
|
|
3398
3453
|
// (typically "cut off" right after sending a prompt).
|
|
3399
3454
|
pinBottom(t);
|
package/public/notify.js
CHANGED
|
@@ -65,8 +65,8 @@ export function notifyState(channels, view) {
|
|
|
65
65
|
: { color: RED, badge: "● ", blink: true, mode: "blocked" };
|
|
66
66
|
}
|
|
67
67
|
if (unread) return { color: AMBER, badge: "● ", blink: false, mode: "unread" };
|
|
68
|
-
// "working" has neither pip colour nor badge: the
|
|
69
|
-
//
|
|
68
|
+
// "working" has neither pip colour nor badge: the caller animates the favicon
|
|
69
|
+
// (the Shadok pumping) for as long as the mode is "working".
|
|
70
70
|
if (working) return { color: null, badge: "", blink: false, mode: "working" };
|
|
71
71
|
return { color: null, badge: "", blink: false, mode: null };
|
|
72
72
|
}
|