shadok-ai 0.16.15 → 0.16.16

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 +80 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.16.15",
3
+ "version": "0.16.16",
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
@@ -1221,6 +1221,38 @@
1221
1221
  padding: 6px 10px; background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
1222
1222
  color: var(--text-dim); text-decoration: none; font-family: var(--mono); font-size: 12px; }
1223
1223
  .att-file:hover { color: var(--amber); border-color: var(--amber); }
1224
+ /* Any image in the chat (agent file card, user attachment, markdown image)
1225
+ opens full-size in the lightbox on click. */
1226
+ .file-img img, .att-img img, .bubble.md img { cursor: zoom-in; }
1227
+ #lightbox {
1228
+ position: fixed; inset: 0; z-index: 5000;
1229
+ display: flex; align-items: center; justify-content: center;
1230
+ padding: 40px; background: rgba(6, 7, 10, 0.85);
1231
+ -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
1232
+ }
1233
+ #lightbox[hidden] { display: none; }
1234
+ #lightbox img {
1235
+ max-width: 94vw; max-height: 86vh; object-fit: contain;
1236
+ border-radius: 8px; box-shadow: 0 16px 50px rgba(0, 0, 0, 0.6);
1237
+ cursor: default; background: var(--bg-raised);
1238
+ }
1239
+ #lightboxClose {
1240
+ position: absolute; top: 14px; right: 16px;
1241
+ width: 40px; height: 40px; border-radius: 50%;
1242
+ display: flex; align-items: center; justify-content: center;
1243
+ font-size: 22px; line-height: 1; color: #fff; cursor: pointer;
1244
+ background: rgba(0, 0, 0, 0.45); border: 1px solid rgba(255, 255, 255, 0.3);
1245
+ }
1246
+ #lightboxClose:hover { background: rgba(0, 0, 0, 0.7); }
1247
+ #lightboxBar { position: absolute; left: 50%; bottom: 20px; transform: translateX(-50%); max-width: 90vw; }
1248
+ #lightboxDl {
1249
+ display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
1250
+ white-space: nowrap; color: #fff; text-decoration: none;
1251
+ font-family: var(--mono); font-size: 12px;
1252
+ background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.25);
1253
+ border-radius: 8px; padding: 7px 14px;
1254
+ }
1255
+ #lightboxDl:hover { background: rgba(0, 0, 0, 0.75); }
1224
1256
  .att-file .att-ic { color: var(--amber); flex: none; }
1225
1257
  .att-file .att-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1226
1258
  /* Composer attachment strip: the SAME dropped/pasted files, previewed above the
@@ -3202,6 +3234,13 @@
3202
3234
  </aside>
3203
3235
  </main>
3204
3236
 
3237
+ <!-- Full-size image viewer: click any chat image to open it here. -->
3238
+ <div id="lightbox" role="dialog" aria-modal="true" aria-label="Image preview" hidden>
3239
+ <button id="lightboxClose" type="button" aria-label="Close image preview">×</button>
3240
+ <img id="lightboxImg" alt="">
3241
+ <div id="lightboxBar"><a id="lightboxDl" download>⭳ Download</a></div>
3242
+ </div>
3243
+
3205
3244
  <link rel="stylesheet" href="/vendor/xterm.css">
3206
3245
  <script src="/vendor/xterm.js"></script>
3207
3246
  <script src="/vendor/addon-fit.js"></script>
@@ -9077,6 +9116,44 @@
9077
9116
  sendTo(active, { type: "key", key: b.dataset.key });
9078
9117
  })
9079
9118
  );
9119
+ // ── Image lightbox: click any chat image to view it full-size ───────────
9120
+ // One viewer for every image in the chat — agent file cards (`.file-img`), the
9121
+ // user's own attachments (`.att-img`, whose <a> would otherwise open a new
9122
+ // tab) and images inside rendered markdown (`.bubble.md img`). A delegated
9123
+ // click on the transcripts container covers every tab with one listener.
9124
+ const lightbox = $("lightbox"), lightboxImg = $("lightboxImg"), lightboxDl = $("lightboxDl");
9125
+ let lightboxReturnFocus = null;
9126
+ function openLightbox(src, name) {
9127
+ if (!src) return;
9128
+ lightboxImg.src = src;
9129
+ lightboxImg.alt = name || "";
9130
+ lightboxDl.href = src;
9131
+ lightboxDl.download = name || "image";
9132
+ lightboxDl.textContent = name ? "⭳ " + name : "⭳ Download";
9133
+ lightboxReturnFocus = document.activeElement;
9134
+ lightbox.hidden = false;
9135
+ $("lightboxClose").focus();
9136
+ }
9137
+ function closeLightbox() {
9138
+ if (lightbox.hidden) return;
9139
+ lightbox.hidden = true;
9140
+ lightboxImg.removeAttribute("src"); // stop the load, drop the decode
9141
+ if (lightboxReturnFocus && lightboxReturnFocus.focus) lightboxReturnFocus.focus();
9142
+ lightboxReturnFocus = null;
9143
+ }
9144
+ transcripts.addEventListener("click", (e) => {
9145
+ const img = e.target.closest("img");
9146
+ if (!img || !img.closest(".file-img, .att-img, .bubble.md")) return;
9147
+ e.preventDefault(); // an .att-img image sits in an <a target="_blank">
9148
+ openLightbox(img.currentSrc || img.src, img.alt || "");
9149
+ });
9150
+ lightbox.addEventListener("click", (e) => {
9151
+ // A click anywhere but the image itself (or the download link) closes it.
9152
+ if (e.target === lightboxImg || e.target.closest("#lightboxDl")) return;
9153
+ closeLightbox();
9154
+ });
9155
+ $("lightboxClose").addEventListener("click", closeLightbox);
9156
+
9080
9157
  // The Escape key sends esc to the active session (like the engine room's Esc
9081
9158
  // button). Precedence: an open overlay/panel is closed first (Escape does not
9082
9159
  // reach down to the session); a pending clickable dialog is left intact;
@@ -9085,6 +9162,9 @@
9085
9162
  // The inline edit calls stopPropagation, so this handler never sees it.
9086
9163
  document.addEventListener("keydown", (e) => {
9087
9164
  if (e.key !== "Escape") return;
9165
+ // The image viewer sits on top of everything, including the terminal, so it
9166
+ // is closed before any other Escape handling.
9167
+ if (!lightbox.hidden) { closeLightbox(); e.preventDefault(); return; }
9088
9168
  // In the interactive terminal, xterm owns every key (incl. Esc) — the raw
9089
9169
  // keystroke already flows to the pane via term.onData; don't double-handle.
9090
9170
  if (active && active.termOn) return;