shadok-ai 0.16.25 → 0.16.26

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/index.html +99 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.16.25",
3
+ "version": "0.16.26",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test --test-timeout=120000 test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -1256,6 +1256,18 @@
1256
1256
  border-radius: 8px; padding: 7px 14px;
1257
1257
  }
1258
1258
  #lightboxDl:hover { background: rgba(0, 0, 0, 0.75); }
1259
+ /* Media gallery popin: a wider panel than the default so the thumbnail grid
1260
+ breathes, and a square-cell auto-fill grid that reflows from phone to
1261
+ desktop with no breakpoints of its own. */
1262
+ .gal-panel { width: min(760px, 100%); max-height: min(82vh, 820px); }
1263
+ .gal-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
1264
+ .gal-cell {
1265
+ position: relative; aspect-ratio: 1 / 1; padding: 0; overflow: hidden; cursor: zoom-in;
1266
+ border: 1px solid var(--line); border-radius: 8px; background: var(--bg-raised);
1267
+ }
1268
+ .gal-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
1269
+ .gal-cell:hover { border-color: var(--amber); }
1270
+ .gal-empty { color: var(--text-dim); font-size: 13px; text-align: center; padding: 24px 4px; }
1259
1271
  .att-file .att-ic { color: var(--amber); flex: none; }
1260
1272
  .att-file .att-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1261
1273
  /* Composer attachment strip: the SAME dropped/pasted files, previewed above the
@@ -2367,11 +2379,11 @@
2367
2379
  #chanBar .gauge .label { display: none; }
2368
2380
  #chanBar .gauge .value { color: var(--text-dim); font-size: 12px; }
2369
2381
  #chanBar .gauge:nth-of-type(3) { display: none; } /* the session id: in the tooltip of the menu */
2370
- #chanBar .chan-actions #chanCron, #chanBar .chan-actions #chanTerm {
2382
+ #chanBar .chan-actions #chanGallery, #chanBar .chan-actions #chanCron, #chanBar .chan-actions #chanTerm {
2371
2383
  position: relative; align-self: stretch; display: inline-flex; align-items: center; gap: 6px;
2372
2384
  border: 0; border-radius: 0; background: none; padding: 0 10px; color: var(--text-dim); font-size: 14px;
2373
2385
  }
2374
- #chanBar .chan-actions #chanCron:hover, #chanBar .chan-actions #chanTerm:hover { color: var(--text); background: none; }
2386
+ #chanBar .chan-actions #chanGallery:hover, #chanBar .chan-actions #chanCron:hover, #chanBar .chan-actions #chanTerm:hover { color: var(--text); background: none; }
2375
2387
  #chanBar .chan-actions #chanTerm.on { color: var(--text); font-weight: 600; background: none; border: 0; }
2376
2388
  #chanBar .chan-actions #chanTerm.on::after {
2377
2389
  content: ""; position: absolute; left: 10px; right: 10px; bottom: 0; height: 3px; border-radius: 3px 3px 0 0; background: var(--amber);
@@ -3151,6 +3163,7 @@
3151
3163
  </div>
3152
3164
  <span class="chan-actions">
3153
3165
  <button id="chanUpdate" class="icon" title="Claude Code updated — reload this agent to apply it" aria-label="Reload to apply update" hidden>↻ update</button>
3166
+ <button id="chanGallery" class="icon" title="Media gallery — every image in this conversation" aria-label="Media gallery"><svg viewBox="0 0 24 24" width="15" height="15" style="vertical-align:-2px" aria-hidden="true"><rect x="3.5" y="5" width="17" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="8.5" cy="9.5" r="1.4" fill="currentColor"/><path d="M5 17l4.5-4.5 3 3 2.5-2.5L19 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
3154
3167
  <button id="chanCron" class="icon" title="Scheduled prompts for this agent (monitoring / reporting)" aria-label="Schedule"><svg viewBox="0 0 24 24" width="15" height="15" style="vertical-align:-2px" aria-hidden="true"><circle cx="12" cy="13" r="6.3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 9.8V13l2.2 1.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4.8 6.6 7.2 4.6M19.2 6.6 16.8 4.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><span class="ct-txt">Schedule</span></button>
3155
3168
  <button id="chanStop" class="icon stop" title="End session" aria-label="End session"><svg viewBox="0 0 24 24" width="15" height="15" style="vertical-align:-2px" aria-hidden="true"><path d="M12 4v7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8.5 6.8a6 6 0 1 0 7 0" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></button>
3156
3169
  <button id="chanTerm" class="icon" title="Terminal — the raw TUI for this agent" aria-label="Terminal"><svg viewBox="0 0 24 24" width="15" height="15" style="vertical-align:-2px" aria-hidden="true"><rect x="2.5" y="4" width="19" height="16" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 9.5l3 2.5-3 2.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.5 15h5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><span class="ct-txt">Terminal</span></button>
@@ -3256,6 +3269,27 @@
3256
3269
  <div id="lightboxBar"><a id="lightboxDl" download>⭳ Download</a></div>
3257
3270
  </div>
3258
3271
 
3272
+ <!-- Media gallery: every image in the ACTIVE agent's conversation, as a grid of
3273
+ thumbnails. Clicking one opens the lightbox above (the "classic flow"). The
3274
+ grid is scanned from the live transcript DOM, so it covers all three image
3275
+ kinds the lightbox already knows (agent file cards, user attachments,
3276
+ markdown images) with no extra bookkeeping. -->
3277
+ <div id="galleryOverlay" class="overlay" hidden>
3278
+ <div class="sec-panel pinned gal-panel">
3279
+ <div class="pin-head">
3280
+ <div class="sec-head">
3281
+ <strong>Media gallery</strong>
3282
+ <button id="galleryClose" title="Close">✕</button>
3283
+ </div>
3284
+ <p class="sec-note" id="galleryNote">Every image in this conversation. Click one to view it full-size.</p>
3285
+ </div>
3286
+ <div class="pin-body">
3287
+ <div id="galleryGrid" class="gal-grid"></div>
3288
+ <div id="galleryEmpty" class="gal-empty" hidden>No images in this conversation yet.</div>
3289
+ </div>
3290
+ </div>
3291
+ </div>
3292
+
3259
3293
  <link rel="stylesheet" href="/vendor/xterm.css">
3260
3294
  <script src="/vendor/xterm.js"></script>
3261
3295
  <script src="/vendor/addon-fit.js"></script>
@@ -5519,6 +5553,9 @@
5519
5553
  // Viewing the channel clears its "unread response" highlight.
5520
5554
  if (tab.btnEl.classList.contains("unread")) setTabMood(tab, null);
5521
5555
  const switched = !!prev && prev !== tab && !restoring;
5556
+ // The gallery is per-agent; switching agents under it would show the old
5557
+ // one's media. Close it rather than silently mislabel the grid.
5558
+ if (switched) closeGallery();
5522
5559
  tabs.forEach((t) => {
5523
5560
  t.btnEl.classList.toggle("active", t === tab);
5524
5561
  t.transcriptEl.hidden = t !== tab;
@@ -6027,6 +6064,8 @@
6027
6064
  // talking about an agent that does not exist.
6028
6065
  $("chanCron").disabled = true;
6029
6066
  $("chanStop").disabled = true;
6067
+ $("chanGallery").disabled = true;
6068
+ closeGallery(); // no agent → its media grid is stale; don't leave it up
6030
6069
  paintChanUpdate();
6031
6070
  // Two different truths behind the same empty panel — the server tells us
6032
6071
  // which (see syncFirstAgent), so a newcomer is never invited to create a
@@ -6039,6 +6078,7 @@
6039
6078
  $("startingState").classList.remove("visible");
6040
6079
  $("chanCron").disabled = !t.sessionId;
6041
6080
  $("chanStop").disabled = false;
6081
+ $("chanGallery").disabled = !t.sessionId;
6042
6082
  paintChanUpdate();
6043
6083
  $("led").className = "led " +
6044
6084
  ({ setup: "", connecting: "busy", ready: "on", busy: "busy", dead: "off" }[t.status]);
@@ -9171,6 +9211,59 @@
9171
9211
  });
9172
9212
  $("lightboxClose").addEventListener("click", closeLightbox);
9173
9213
 
9214
+ // Media gallery: the grid of every image in the active conversation. It reads
9215
+ // the live transcript DOM rather than a separate media model, so it stays in
9216
+ // lockstep with what the lightbox handles — the same three selectors — and a
9217
+ // newly streamed image shows up the next time the gallery is opened, with no
9218
+ // extra wiring. De-duped by src: the same file sent twice is one tile.
9219
+ function collectGalleryImages() {
9220
+ const host = active && active.transcriptEl;
9221
+ if (!host) return [];
9222
+ const out = [], seen = new Set();
9223
+ for (const img of host.querySelectorAll(".file-img img, .att-img img, .bubble.md img")) {
9224
+ const src = img.currentSrc || img.src;
9225
+ if (!src || seen.has(src)) continue;
9226
+ seen.add(src);
9227
+ out.push({ src, name: img.alt || "" });
9228
+ }
9229
+ return out;
9230
+ }
9231
+ function openGallery() {
9232
+ if (!active) return;
9233
+ const items = collectGalleryImages();
9234
+ const grid = $("galleryGrid");
9235
+ grid.innerHTML = "";
9236
+ for (const it of items) {
9237
+ const cell = document.createElement("button");
9238
+ cell.type = "button";
9239
+ cell.className = "gal-cell";
9240
+ cell.title = it.name || "image";
9241
+ const im = document.createElement("img");
9242
+ im.src = it.src; im.alt = it.name; im.loading = "lazy";
9243
+ cell.appendChild(im);
9244
+ // "Retombe sur le flow classique": a thumbnail opens the lightbox on top
9245
+ // of the gallery, so closing it returns here to pick another.
9246
+ cell.addEventListener("click", () => openLightbox(it.src, it.name));
9247
+ grid.appendChild(cell);
9248
+ }
9249
+ $("galleryEmpty").hidden = items.length > 0;
9250
+ $("galleryNote").textContent = items.length
9251
+ ? "Every image in this conversation — " + items.length + (items.length > 1 ? " images" : " image") + ". Click one to view it full-size."
9252
+ : "Every image in this conversation. Click one to view it full-size.";
9253
+ $("galleryOverlay").hidden = false;
9254
+ $("galleryClose").focus();
9255
+ }
9256
+ function closeGallery() {
9257
+ if ($("galleryOverlay").hidden) return;
9258
+ $("galleryOverlay").hidden = true;
9259
+ $("galleryGrid").innerHTML = ""; // drop the decoded thumbnails
9260
+ }
9261
+ $("chanGallery").addEventListener("click", openGallery);
9262
+ $("galleryClose").addEventListener("click", closeGallery);
9263
+ $("galleryOverlay").addEventListener("click", (e) => {
9264
+ if (e.target === $("galleryOverlay")) closeGallery();
9265
+ });
9266
+
9174
9267
  // The Escape key sends esc to the active session (like the engine room's Esc
9175
9268
  // button). Precedence: an open overlay/panel is closed first (Escape does not
9176
9269
  // reach down to the session); a pending clickable dialog is left intact;
@@ -9182,6 +9275,10 @@
9182
9275
  // The image viewer sits on top of everything, including the terminal, so it
9183
9276
  // is closed before any other Escape handling.
9184
9277
  if (!lightbox.hidden) { closeLightbox(); e.preventDefault(); return; }
9278
+ // The gallery is a modal overlay on top of the terminal too, so it is closed
9279
+ // before the terminal gets to claim Escape — but after the lightbox, which
9280
+ // may be stacked above it.
9281
+ if (!$("galleryOverlay").hidden) { closeGallery(); e.preventDefault(); return; }
9185
9282
  // In the interactive terminal, xterm owns every key (incl. Esc) — the raw
9186
9283
  // keystroke already flows to the pane via term.onData; don't double-handle.
9187
9284
  if (active && active.termOn) return;