dsh-codex-subscription 1.13.0 → 1.14.0

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/lib/client.js CHANGED
@@ -28,7 +28,6 @@ window.__ModuleLoader__.load({
28
28
  //#endregion
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
- let react_dom = require("react-dom");
32
31
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
33
32
  let react_jsx_runtime = require("react/jsx-runtime");
34
33
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/16/solid/esm/BoltIcon.js
@@ -79,6 +78,705 @@ window.__ModuleLoader__.load({
79
78
  return original === void 0 ? void 0 : { original };
80
79
  }
81
80
  //#endregion
81
+ //#region src/subscription-image-viewer-styles.js
82
+ const SUBSCRIPTION_IMAGE_VIEWER_CSS = String.raw`
83
+ .dcsiv-root{position:fixed;inset:0;z-index:1000;pointer-events:auto;overflow:hidden;background:rgba(7,8,10,.68);color:var(--dsw-alias-label-primary-inverted,#fff);outline:0;backdrop-filter:blur(13px) saturate(.72);-webkit-backdrop-filter:blur(13px) saturate(.72)}
84
+ .dcsiv-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
85
+ .dcsiv-topbar{position:absolute;right:50%;bottom:22px;z-index:6;max-width:calc(100vw - 36px);padding:5px;border:1px solid rgba(255,255,255,.12);border-radius:999px;background:rgba(38,39,43,.86);box-shadow:0 10px 34px rgba(0,0,0,.3);transform:translateX(50%);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}
86
+ .dcsiv-actions{display:flex;align-items:center;gap:2px;overflow-x:auto;scrollbar-width:none}.dcsiv-actions::-webkit-scrollbar{display:none}.dcsiv-button,.dcsiv-download{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 10px;border:0;border-radius:999px;background:transparent;color:rgba(255,255,255,.9);font:inherit;font-size:12px;text-decoration:none;white-space:nowrap;cursor:pointer}.dcsiv-button:hover,.dcsiv-download:hover,.dcsiv-button[data-active=true]{background:rgba(255,255,255,.12)}.dcsiv-button:focus-visible,.dcsiv-download:focus-visible,.dcsiv-close-floating:focus-visible{outline:2px solid rgba(255,255,255,.9);outline-offset:2px}.dcsiv-button:disabled,.dcsiv-download:disabled{opacity:.38;cursor:default}.dcsiv-icon-only{width:32px;padding:0}.dcsiv-zoom{min-width:44px;color:rgba(255,255,255,.68);font-size:12px;font-variant-numeric:tabular-nums;text-align:center}
87
+ .dcsiv-close-floating{position:absolute;top:20px;right:20px;z-index:8;display:grid;place-items:center;width:42px;height:42px;padding:0;border:1px solid rgba(255,255,255,.1);border-radius:50%;background:rgba(48,49,53,.82);box-shadow:0 8px 24px rgba(0,0,0,.28);color:#fff;cursor:pointer;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.dcsiv-close-floating:hover{background:rgba(66,67,72,.92)}
88
+ .dcsiv-workspace{position:absolute;inset:0;display:grid;min-width:0;min-height:0;padding:68px 24px 72px;box-sizing:border-box}
89
+ .dcsiv-stage{position:relative;display:grid;place-items:center;min-width:0;min-height:0;overflow:hidden;padding:0;touch-action:none;user-select:none}.dcsiv-stage[data-dragging=true]{cursor:grabbing}.dcsiv-stage[data-annotating=true]{cursor:crosshair}
90
+ .dcsiv-surface{position:relative;display:inline-flex;max-width:100%;max-height:100%;transform-origin:center;will-change:transform}.dcsiv-image{display:block;max-width:calc(100vw - 72px);max-height:calc(100vh - 154px);border-radius:12px;object-fit:contain;box-shadow:0 22px 60px rgba(0,0,0,.46);user-select:none;-webkit-user-drag:none}
91
+ .dcsiv-annotation{position:absolute;z-index:5;width:24px;height:24px;transform-origin:center;pointer-events:none}.dcsiv-pin{position:absolute;inset:0;display:grid;place-items:center;width:24px;height:24px;padding:0;border:2px solid #fff;border-radius:50%;background:rgba(23,24,27,.94);box-shadow:0 4px 16px rgba(0,0,0,.35);color:#fff;font:inherit;font-size:11px;font-weight:700;cursor:pointer;pointer-events:auto}.dcsiv-pin[data-active=true]{background:var(--dsw-alias-state-business-primary,#3964fe)}
92
+ .dcsiv-inline-note{position:absolute;bottom:34px;box-sizing:border-box;display:grid;width:min(300px,calc(100vw - 40px));grid-template-columns:24px minmax(0,1fr) 24px;align-items:center;gap:7px;padding:7px 8px;border:1px solid rgba(255,255,255,.12);border-radius:18px;background:rgba(32,33,37,.94);box-shadow:0 14px 38px rgba(0,0,0,.38);color:#fff;pointer-events:auto;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.dcsiv-annotation[data-x=right] .dcsiv-inline-note{left:-8px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note{right:-8px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note{left:50%;transform:translateX(-50%)}.dcsiv-annotation[data-y=down] .dcsiv-inline-note{top:34px;bottom:auto}.dcsiv-inline-note::after{position:absolute;width:9px;height:9px;background:rgba(32,33,37,.94);content:'';transform:rotate(45deg)}.dcsiv-annotation[data-y=up] .dcsiv-inline-note::after{bottom:-5px}.dcsiv-annotation[data-y=down] .dcsiv-inline-note::after{top:-5px}.dcsiv-annotation[data-x=right] .dcsiv-inline-note::after{left:13px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note::after{right:13px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note::after{left:calc(50% - 4px)}.dcsiv-inline-index{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--dsw-alias-state-business-primary,#3964fe);color:#fff;font-size:10px;font-weight:700}.dcsiv-inline-note textarea{box-sizing:border-box;width:100%;min-height:24px;max-height:92px;resize:none;overflow:auto;border:0;outline:0;background:transparent;color:#fff;font:inherit;font-size:12px;line-height:18px}.dcsiv-inline-note textarea::placeholder{color:rgba(255,255,255,.44)}.dcsiv-note-remove{display:grid;place-items:center;width:24px;height:24px;padding:0;border:0;border-radius:50%;background:transparent;color:rgba(255,255,255,.68);cursor:pointer}.dcsiv-note-remove:hover{background:rgba(255,255,255,.1);color:#fff}
93
+ .dcsiv-nav{position:absolute;top:50%;z-index:4;width:42px;height:42px;padding:0;transform:translateY(-50%);background:rgba(48,49,53,.82);box-shadow:0 8px 24px rgba(0,0,0,.28);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.dcsiv-prev{left:6px}.dcsiv-next{right:6px}.dcsiv-counter{position:absolute;bottom:8px;left:50%;padding:5px 10px;border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.72);font-size:11px;transform:translateX(-50%);backdrop-filter:blur(16px)}
94
+ .dcsiv-hint{display:none}
95
+ .dcsiv-copy-notes{position:absolute;right:20px;bottom:22px;z-index:6;display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 11px;border:1px solid rgba(255,255,255,.1);border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.82);font:inherit;font-size:12px;cursor:pointer;backdrop-filter:blur(16px)}
96
+ @media(max-width:760px){.dcsiv-close-floating{top:12px;right:12px;width:40px;height:40px}.dcsiv-workspace{padding:60px 12px 68px}.dcsiv-image{max-width:calc(100vw - 24px);max-height:calc(100vh - 136px)}.dcsiv-topbar{bottom:12px;max-width:calc(100vw - 24px)}.dcsiv-button{padding:0 8px}.dcsiv-button span.dcsiv-label{display:none}.dcsiv-inline-note{width:min(260px,calc(100vw - 40px))}.dcsiv-copy-notes{display:none}}
97
+ @media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}}
98
+ `;
99
+ //#endregion
100
+ //#region src/subscription-image-viewer.jsx
101
+ const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
102
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
103
+ const bytesLabel = (bytes) => bytes === void 0 ? void 0 : bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
104
+ const downloadName = (name) => {
105
+ const cleaned = String(name || "image.png").replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
106
+ return cleaned === "" ? "image.png" : cleaned;
107
+ };
108
+ const noteText = (annotations, t) => annotations.map((annotation, index) => {
109
+ return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
110
+ }).filter((line) => !line.endsWith(": ")).join("\n");
111
+ function ViewerAction({ action, annotations, item, service, t }) {
112
+ const [state, setState] = (0, react.useState)("idle");
113
+ const invoke = async () => {
114
+ if (state === "pending") return;
115
+ setState("pending");
116
+ try {
117
+ await action.onInvoke({
118
+ annotations,
119
+ item,
120
+ src: item.src
121
+ });
122
+ setState("idle");
123
+ if (action.closeOnSuccess) service.close();
124
+ } catch {
125
+ setState("failed");
126
+ }
127
+ };
128
+ const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
129
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
130
+ type: "button",
131
+ className: "dcsiv-button",
132
+ disabled: state === "pending",
133
+ onClick: () => {
134
+ invoke();
135
+ },
136
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
137
+ className: "dcsiv-label",
138
+ children: label ?? t("imageEdit")
139
+ })
140
+ });
141
+ }
142
+ function ViewerDownload({ download, item, t }) {
143
+ const [state, setState] = (0, react.useState)("idle");
144
+ const invoke = async () => {
145
+ if (state === "pending") return;
146
+ setState("pending");
147
+ try {
148
+ await download.onInvoke({
149
+ item,
150
+ src: item.src
151
+ });
152
+ setState("idle");
153
+ } catch {
154
+ setState("failed");
155
+ }
156
+ };
157
+ const label = state === "pending" ? download.pendingLabel ?? t("imageDownloadPreparing") : state === "failed" ? download.errorLabel ?? t("imageDownloadFailed") : t("imageDownload");
158
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
159
+ type: "button",
160
+ className: "dcsiv-download",
161
+ disabled: state === "pending",
162
+ onClick: () => {
163
+ invoke();
164
+ },
165
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
166
+ className: "dcsiv-label",
167
+ children: label
168
+ })]
169
+ });
170
+ }
171
+ function SubscriptionImageViewerOverlay({ service, t }) {
172
+ const request = (0, react.useSyncExternalStore)(service.subscribe, service.getSnapshot);
173
+ const [index, setIndex] = (0, react.useState)(0);
174
+ const [transform, setTransform] = (0, react.useState)({
175
+ zoom: 1,
176
+ x: 0,
177
+ y: 0
178
+ });
179
+ const [dragging, setDragging] = (0, react.useState)(false);
180
+ const [annotating, setAnnotating] = (0, react.useState)(false);
181
+ const [annotationsByImage, setAnnotationsByImage] = (0, react.useState)(service.getAnnotationsSnapshot);
182
+ const annotationsByImageRef = (0, react.useRef)(annotationsByImage);
183
+ const [selected, setSelected] = (0, react.useState)();
184
+ const [focusNote, setFocusNote] = (0, react.useState)();
185
+ const [copied, setCopied] = (0, react.useState)(false);
186
+ const rootRef = (0, react.useRef)(null);
187
+ const stageRef = (0, react.useRef)(null);
188
+ const surfaceRef = (0, react.useRef)(null);
189
+ const imageRef = (0, react.useRef)(null);
190
+ const pointersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
191
+ const gestureRef = (0, react.useRef)();
192
+ const transformRef = (0, react.useRef)(transform);
193
+ transformRef.current = transform;
194
+ annotationsByImageRef.current = annotationsByImage;
195
+ (0, react.useEffect)(() => {
196
+ if (request === void 0) return;
197
+ setIndex(request.index);
198
+ setTransform({
199
+ zoom: 1,
200
+ x: 0,
201
+ y: 0
202
+ });
203
+ setDragging(false);
204
+ setAnnotating(false);
205
+ setSelected(void 0);
206
+ setCopied(false);
207
+ }, [request?.revision]);
208
+ const item = request?.items[index];
209
+ const annotations = item === void 0 ? [] : annotationsByImage[item.id] ?? [];
210
+ const setAnnotations = (0, react.useCallback)((update) => {
211
+ if (item === void 0) return;
212
+ const previous = annotationsByImageRef.current[item.id] ?? [];
213
+ const next = typeof update === "function" ? update(previous) : update;
214
+ const snapshot = {
215
+ ...annotationsByImageRef.current,
216
+ [item.id]: next
217
+ };
218
+ annotationsByImageRef.current = snapshot;
219
+ service.setAnnotations(item.id, next);
220
+ setAnnotationsByImage(snapshot);
221
+ }, [item?.id, service]);
222
+ const boundedPan = (0, react.useCallback)((zoom, x, y) => {
223
+ const stage = stageRef.current;
224
+ const surface = surfaceRef.current;
225
+ if (stage === null || surface === null || zoom <= 1) return {
226
+ x: 0,
227
+ y: 0
228
+ };
229
+ const limitX = Math.max(0, (surface.offsetWidth * zoom - stage.clientWidth) / 2) + 28;
230
+ const limitY = Math.max(0, (surface.offsetHeight * zoom - stage.clientHeight) / 2) + 28;
231
+ return {
232
+ x: clamp(x, -limitX, limitX),
233
+ y: clamp(y, -limitY, limitY)
234
+ };
235
+ }, []);
236
+ const setZoomAt = (0, react.useCallback)((nextZoom, clientX, clientY) => {
237
+ const stage = stageRef.current;
238
+ if (stage === null) return;
239
+ setTransform((current) => {
240
+ const next = clamp(nextZoom, .5, 8);
241
+ const box = stage.getBoundingClientRect();
242
+ const px = clientX - box.left - box.width / 2;
243
+ const py = clientY - box.top - box.height / 2;
244
+ const ratio = next / current.zoom;
245
+ return {
246
+ zoom: next,
247
+ ...boundedPan(next, px - (px - current.x) * ratio, py - (py - current.y) * ratio)
248
+ };
249
+ });
250
+ }, [boundedPan]);
251
+ const fit = (0, react.useCallback)(() => {
252
+ setTransform({
253
+ zoom: 1,
254
+ x: 0,
255
+ y: 0
256
+ });
257
+ }, []);
258
+ const actual = (0, react.useCallback)(() => {
259
+ const image = imageRef.current;
260
+ const surface = surfaceRef.current;
261
+ if (image === null || surface === null || image.naturalWidth === 0) return;
262
+ const zoom = clamp(image.naturalWidth / Math.max(1, surface.offsetWidth), 1, 8);
263
+ setTransform({
264
+ zoom,
265
+ x: 0,
266
+ y: 0
267
+ });
268
+ }, []);
269
+ (0, react.useEffect)(() => {
270
+ if (request === void 0) return void 0;
271
+ const previousOverflow = document.body.style.overflow;
272
+ document.body.style.overflow = "hidden";
273
+ rootRef.current?.focus();
274
+ const onKeyDown = (event) => {
275
+ if (event.key === "Escape") {
276
+ event.preventDefault();
277
+ if (event.target instanceof Element && event.target.closest(".dcsiv-inline-note") !== null) {
278
+ setSelected(void 0);
279
+ return;
280
+ }
281
+ service.close();
282
+ return;
283
+ }
284
+ const editing = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement;
285
+ if (!editing && event.key === "ArrowLeft" && request.items.length > 1) {
286
+ event.preventDefault();
287
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
288
+ } else if (!editing && event.key === "ArrowRight" && request.items.length > 1) {
289
+ event.preventDefault();
290
+ setIndex((value) => (value + 1) % request.items.length);
291
+ } else if (!editing && (event.key === "+" || event.key === "=")) {
292
+ event.preventDefault();
293
+ const box = stageRef.current?.getBoundingClientRect();
294
+ if (box) setZoomAt(transformRef.current.zoom * 1.2, box.left + box.width / 2, box.top + box.height / 2);
295
+ } else if (!editing && event.key === "-") {
296
+ event.preventDefault();
297
+ const box = stageRef.current?.getBoundingClientRect();
298
+ if (box) setZoomAt(transformRef.current.zoom / 1.2, box.left + box.width / 2, box.top + box.height / 2);
299
+ } else if (!editing && event.key.toLowerCase() === "f") {
300
+ event.preventDefault();
301
+ fit();
302
+ } else if (event.key === "Tab") {
303
+ const controls = [...rootRef.current.querySelectorAll("button:not(:disabled),a[href],input:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])")];
304
+ const first = controls[0];
305
+ const last = controls.at(-1);
306
+ if (event.shiftKey && document.activeElement === first) {
307
+ event.preventDefault();
308
+ last?.focus();
309
+ } else if (!event.shiftKey && document.activeElement === last) {
310
+ event.preventDefault();
311
+ first?.focus();
312
+ }
313
+ }
314
+ };
315
+ document.addEventListener("keydown", onKeyDown);
316
+ return () => {
317
+ document.body.style.overflow = previousOverflow;
318
+ document.removeEventListener("keydown", onKeyDown);
319
+ };
320
+ }, [
321
+ request,
322
+ service,
323
+ fit,
324
+ setZoomAt
325
+ ]);
326
+ (0, react.useEffect)(() => {
327
+ if (focusNote === void 0) return;
328
+ (rootRef.current?.querySelector(`[data-note-id="${CSS.escape(focusNote)}"] textarea`))?.focus();
329
+ setFocusNote(void 0);
330
+ }, [
331
+ focusNote,
332
+ selected,
333
+ annotations.length
334
+ ]);
335
+ (0, react.useEffect)(() => {
336
+ setTransform({
337
+ zoom: 1,
338
+ x: 0,
339
+ y: 0
340
+ });
341
+ setDragging(false);
342
+ setAnnotating(false);
343
+ setSelected(void 0);
344
+ }, [item?.id]);
345
+ const onWheel = (0, react.useCallback)((event) => {
346
+ event.preventDefault();
347
+ setZoomAt(transformRef.current.zoom * Math.exp(-event.deltaY * .0015), event.clientX, event.clientY);
348
+ }, [setZoomAt]);
349
+ (0, react.useEffect)(() => {
350
+ const stage = stageRef.current;
351
+ if (stage === null || request === void 0) return void 0;
352
+ stage.addEventListener("wheel", onWheel, { passive: false });
353
+ return () => stage.removeEventListener("wheel", onWheel);
354
+ }, [onWheel, request]);
355
+ const onPointerDown = (event) => {
356
+ const target = event.target;
357
+ if (event.button !== 0 || annotating || target instanceof Element && target.closest("button,textarea,input,a,select,[contenteditable=true]") !== null) return;
358
+ pointersRef.current.set(event.pointerId, {
359
+ x: event.clientX,
360
+ y: event.clientY
361
+ });
362
+ if (pointersRef.current.size === 2) {
363
+ event.currentTarget.setPointerCapture(event.pointerId);
364
+ const [a, b] = [...pointersRef.current.values()];
365
+ gestureRef.current = {
366
+ kind: "pinch",
367
+ distance: Math.hypot(a.x - b.x, a.y - b.y),
368
+ transform
369
+ };
370
+ } else if (!annotating && transform.zoom > 1) {
371
+ event.currentTarget.setPointerCapture(event.pointerId);
372
+ gestureRef.current = {
373
+ kind: "pan",
374
+ x: event.clientX,
375
+ y: event.clientY,
376
+ transform
377
+ };
378
+ setDragging(true);
379
+ }
380
+ };
381
+ const onPointerMove = (event) => {
382
+ if (!pointersRef.current.has(event.pointerId)) return;
383
+ pointersRef.current.set(event.pointerId, {
384
+ x: event.clientX,
385
+ y: event.clientY
386
+ });
387
+ const gesture = gestureRef.current;
388
+ if (gesture?.kind === "pinch" && pointersRef.current.size >= 2) {
389
+ const [a, b] = [...pointersRef.current.values()];
390
+ const distance = Math.max(1, Math.hypot(a.x - b.x, a.y - b.y));
391
+ setZoomAt(gesture.transform.zoom * distance / Math.max(1, gesture.distance), (a.x + b.x) / 2, (a.y + b.y) / 2);
392
+ } else if (gesture?.kind === "pan") {
393
+ const pan = boundedPan(gesture.transform.zoom, gesture.transform.x + event.clientX - gesture.x, gesture.transform.y + event.clientY - gesture.y);
394
+ setTransform({
395
+ zoom: gesture.transform.zoom,
396
+ ...pan
397
+ });
398
+ }
399
+ };
400
+ const endPointer = (event) => {
401
+ pointersRef.current.delete(event.pointerId);
402
+ if (pointersRef.current.size === 0) {
403
+ gestureRef.current = void 0;
404
+ setDragging(false);
405
+ }
406
+ };
407
+ const addAnnotation = (event) => {
408
+ if (!annotating || event.target.closest(".dcsiv-annotation")) return;
409
+ const bounds = surfaceRef.current?.getBoundingClientRect();
410
+ if (bounds === void 0) return;
411
+ const annotation = {
412
+ id: crypto.randomUUID(),
413
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
414
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1),
415
+ note: ""
416
+ };
417
+ setAnnotations((current) => [...current, annotation]);
418
+ setAnnotating(false);
419
+ setSelected(annotation.id);
420
+ setFocusNote(annotation.id);
421
+ };
422
+ const copyNotes = async () => {
423
+ const text = noteText(annotations, t);
424
+ if (text === "" || typeof navigator?.clipboard?.writeText !== "function") return;
425
+ try {
426
+ await navigator.clipboard.writeText(text);
427
+ setCopied(true);
428
+ window.setTimeout(() => {
429
+ setCopied(false);
430
+ }, 1200);
431
+ } catch {
432
+ setCopied(false);
433
+ }
434
+ };
435
+ if (request === void 0 || item === void 0) return null;
436
+ const meta = [item.width && item.height ? `${item.width} × ${item.height}` : void 0, bytesLabel(item.bytes)].filter(Boolean).join(" · ");
437
+ const showCounter = request.items.length > 1;
438
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
439
+ ref: rootRef,
440
+ className: "dcsiv-root",
441
+ role: "dialog",
442
+ "aria-modal": "true",
443
+ "aria-label": t("imagePreview"),
444
+ tabIndex: -1,
445
+ children: [
446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
447
+ className: "dcsiv-title dcsiv-sr-only",
448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.name }), meta !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: meta }) : null]
449
+ }),
450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
451
+ className: "dcsiv-topbar",
452
+ role: "toolbar",
453
+ "aria-label": t("imagePreview"),
454
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
455
+ className: "dcsiv-actions",
456
+ children: [
457
+ request.annotations ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
458
+ type: "button",
459
+ className: "dcsiv-button",
460
+ "data-active": annotating,
461
+ "aria-label": annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
462
+ "aria-pressed": annotating,
463
+ onClick: () => setAnnotating((value) => !value),
464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
465
+ className: "dcsiv-label",
466
+ children: annotating ? t("imageAnnotateCancel") : t("imageAnnotate")
467
+ })]
468
+ }) : null,
469
+ annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
470
+ type: "button",
471
+ className: "dcsiv-button",
472
+ "data-active": selected !== void 0,
473
+ onClick: () => {
474
+ const first = annotations[0];
475
+ setSelected((current) => current === void 0 ? first.id : void 0);
476
+ if (selected === void 0) setFocusNote(first.id);
477
+ },
478
+ children: [
479
+ annotations.length,
480
+ " ",
481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
482
+ className: "dcsiv-label",
483
+ children: t("imageRegions")
484
+ })
485
+ ]
486
+ }) : null,
487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
488
+ type: "button",
489
+ className: "dcsiv-button",
490
+ "aria-label": t("imageFit"),
491
+ onClick: fit,
492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
493
+ className: "dcsiv-label",
494
+ children: t("imageFit")
495
+ })]
496
+ }),
497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
498
+ type: "button",
499
+ className: "dcsiv-button",
500
+ onClick: actual,
501
+ children: t("imageActual")
502
+ }),
503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
504
+ className: "dcsiv-zoom",
505
+ children: [Math.round(transform.zoom * 100), "%"]
506
+ }),
507
+ item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
508
+ className: "dcsiv-download",
509
+ href: item.src,
510
+ download: downloadName(item.name),
511
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
512
+ className: "dcsiv-label",
513
+ children: t("imageDownload")
514
+ })]
515
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerDownload, {
516
+ download: item.download,
517
+ item,
518
+ t
519
+ }),
520
+ item.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerAction, {
521
+ action,
522
+ annotations,
523
+ item,
524
+ service,
525
+ t
526
+ }, action.id))
527
+ ]
528
+ })
529
+ }),
530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
531
+ type: "button",
532
+ className: "dcsiv-close-floating",
533
+ "aria-label": t("imageClosePreview"),
534
+ onClick: () => service.close(),
535
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
536
+ }),
537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
538
+ className: "dcsiv-workspace",
539
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
540
+ ref: stageRef,
541
+ className: "dcsiv-stage",
542
+ "data-dragging": dragging,
543
+ "data-annotating": annotating,
544
+ onClick: (event) => {
545
+ if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
546
+ },
547
+ onPointerDown,
548
+ onPointerMove,
549
+ onPointerUp: endPointer,
550
+ onPointerCancel: endPointer,
551
+ onDoubleClick: () => {
552
+ if (transform.zoom === 1) actual();
553
+ else fit();
554
+ },
555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
556
+ ref: surfaceRef,
557
+ className: "dcsiv-surface",
558
+ onClick: addAnnotation,
559
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
560
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
561
+ ref: imageRef,
562
+ className: "dcsiv-image",
563
+ src: item.src,
564
+ alt: item.name,
565
+ draggable: "false"
566
+ }), annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
567
+ className: "dcsiv-annotation",
568
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
569
+ "data-y": annotation.y < .28 ? "down" : "up",
570
+ style: {
571
+ left: `${annotation.x * 100}%`,
572
+ top: `${annotation.y * 100}%`,
573
+ transform: `translate(-50%,-50%) scale(${1 / transform.zoom})`
574
+ },
575
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
576
+ type: "button",
577
+ className: "dcsiv-pin",
578
+ "data-active": selected === annotation.id,
579
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
580
+ onClick: (event) => {
581
+ event.stopPropagation();
582
+ const opening = selected !== annotation.id;
583
+ setSelected(opening ? annotation.id : void 0);
584
+ if (opening) setFocusNote(annotation.id);
585
+ },
586
+ children: position + 1
587
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
588
+ className: "dcsiv-inline-note",
589
+ "data-note-id": annotation.id,
590
+ onClick: (event) => event.stopPropagation(),
591
+ children: [
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
593
+ className: "dcsiv-inline-index",
594
+ children: position + 1
595
+ }),
596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
597
+ value: annotation.note,
598
+ rows: 1,
599
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
600
+ placeholder: t("imageAnnotationPlaceholder"),
601
+ onChange: (event) => {
602
+ const note = event.target.value;
603
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
604
+ ...entry,
605
+ note
606
+ } : entry));
607
+ },
608
+ onKeyDown: (event) => {
609
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
610
+ event.preventDefault();
611
+ event.stopPropagation();
612
+ event.nativeEvent?.stopImmediatePropagation?.();
613
+ setSelected(void 0);
614
+ }
615
+ }
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
618
+ type: "button",
619
+ className: "dcsiv-note-remove",
620
+ "aria-label": t("imageRemoveAnnotation"),
621
+ onClick: (event) => {
622
+ event.stopPropagation();
623
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
624
+ setSelected(void 0);
625
+ },
626
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
627
+ })
628
+ ]
629
+ }) : null]
630
+ }, annotation.id))]
631
+ }), showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
633
+ type: "button",
634
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
635
+ "aria-label": t("imagePrevious"),
636
+ onClick: (event) => {
637
+ event.stopPropagation();
638
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
639
+ },
640
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
641
+ }),
642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
643
+ type: "button",
644
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
645
+ "aria-label": t("imageNext"),
646
+ onClick: (event) => {
647
+ event.stopPropagation();
648
+ setIndex((value) => (value + 1) % request.items.length);
649
+ },
650
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
651
+ }),
652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
653
+ className: "dcsiv-counter",
654
+ children: [
655
+ index + 1,
656
+ " / ",
657
+ request.items.length
658
+ ]
659
+ })
660
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
661
+ className: "dcsiv-hint",
662
+ children: t("imageAnnotateHint")
663
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
664
+ className: "dcsiv-hint",
665
+ children: t("imageZoomHint")
666
+ }) : null]
667
+ }), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
668
+ type: "button",
669
+ className: "dcsiv-copy-notes",
670
+ onClick: () => {
671
+ copyNotes();
672
+ },
673
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
674
+ }) : null]
675
+ })
676
+ ]
677
+ });
678
+ }
679
+ //#endregion
680
+ //#region src/subscription-image-viewer.js
681
+ const boundedNumber = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
682
+ const downloadOf = (value) => typeof value?.onInvoke === "function" ? {
683
+ pendingLabel: typeof value.pendingLabel === "string" && value.pendingLabel !== "" ? value.pendingLabel : void 0,
684
+ errorLabel: typeof value.errorLabel === "string" && value.errorLabel !== "" ? value.errorLabel : void 0,
685
+ onInvoke: value.onInvoke
686
+ } : void 0;
687
+ const actionsOf = (value) => Array.isArray(value) ? value.flatMap((action, position) => {
688
+ if (typeof action?.onInvoke !== "function" || typeof action?.label !== "string" || action.label.trim() === "") return [];
689
+ return [{
690
+ id: typeof action.id === "string" && action.id !== "" ? action.id : `action-${position + 1}`,
691
+ label: action.label,
692
+ pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
693
+ errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
694
+ closeOnSuccess: action.closeOnSuccess === true,
695
+ onInvoke: action.onInvoke
696
+ }];
697
+ }) : [];
698
+ function normalizeSubscriptionViewerRequest(request) {
699
+ const items = (Array.isArray(request?.items) ? request.items : []).flatMap((item, position) => {
700
+ if (typeof item?.src !== "string" || item.src === "") return [];
701
+ return [{
702
+ id: typeof item.id === "string" && item.id !== "" ? item.id : `image-${position + 1}`,
703
+ src: item.src,
704
+ name: typeof item.name === "string" && item.name !== "" ? item.name : `Image ${position + 1}`,
705
+ width: boundedNumber(item.width, void 0),
706
+ height: boundedNumber(item.height, void 0),
707
+ bytes: boundedNumber(item.bytes, void 0),
708
+ download: downloadOf(item.download),
709
+ actions: actionsOf(item.actions)
710
+ }];
711
+ });
712
+ if (items.length === 0) return void 0;
713
+ const requestedIndex = Number.isInteger(request?.index) ? request.index : 0;
714
+ return {
715
+ items,
716
+ index: Math.max(0, Math.min(items.length - 1, requestedIndex)),
717
+ opener: typeof HTMLElement !== "undefined" && request?.opener instanceof HTMLElement ? request.opener : void 0,
718
+ source: typeof request?.source === "string" ? request.source : "dsh-codex-subscription",
719
+ annotations: request?.annotations !== false
720
+ };
721
+ }
722
+ const copyAnnotations = (annotations) => annotations.map((annotation) => ({ ...annotation }));
723
+ /**
724
+ * Local image viewer state for subscription-generated images.
725
+ *
726
+ * This deliberately stays private to the subscription client. An installed
727
+ * native image viewer can take the request first, while this service provides
728
+ * the same baseline experience when that optional plugin is absent.
729
+ */
730
+ var SubscriptionImageViewerService = class {
731
+ #listeners = /* @__PURE__ */ new Set();
732
+ #revision = 0;
733
+ #snapshot;
734
+ #annotationsByImage = /* @__PURE__ */ new Map();
735
+ constructor() {
736
+ this.subscribe = (listener) => {
737
+ this.#listeners.add(listener);
738
+ return () => {
739
+ this.#listeners.delete(listener);
740
+ };
741
+ };
742
+ this.getSnapshot = () => this.#snapshot;
743
+ this.getAnnotationsSnapshot = () => Object.fromEntries([...this.#annotationsByImage].map(([id, annotations]) => [id, copyAnnotations(annotations)]));
744
+ }
745
+ setAnnotations(imageId, annotations) {
746
+ if (typeof imageId !== "string" || imageId === "" || !Array.isArray(annotations)) return;
747
+ if (annotations.length === 0) this.#annotationsByImage.delete(imageId);
748
+ else this.#annotationsByImage.set(imageId, copyAnnotations(annotations));
749
+ }
750
+ open(request) {
751
+ const normalized = normalizeSubscriptionViewerRequest(request);
752
+ if (normalized === void 0) return false;
753
+ this.#revision += 1;
754
+ this.#snapshot = {
755
+ ...normalized,
756
+ revision: this.#revision
757
+ };
758
+ this.#emit();
759
+ return true;
760
+ }
761
+ close() {
762
+ if (this.#snapshot === void 0) return;
763
+ const opener = this.#snapshot.opener;
764
+ this.#snapshot = void 0;
765
+ this.#emit();
766
+ if (typeof window === "undefined") opener?.focus();
767
+ else {
768
+ const focus = () => {
769
+ opener?.focus();
770
+ };
771
+ if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(focus);
772
+ else focus();
773
+ }
774
+ }
775
+ #emit() {
776
+ for (const listener of this.#listeners) listener();
777
+ }
778
+ };
779
+ //#endregion
82
780
  //#region src/settings-contract.js
83
781
  const SETTINGS_NAMESPACE = "codex-subscription";
84
782
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
@@ -371,6 +1069,7 @@ window.__ModuleLoader__.load({
371
1069
  "settingsScope",
372
1070
  "modelDirectories",
373
1071
  "conversation",
1072
+ "uiConversation",
374
1073
  "sessions"
375
1074
  ];
376
1075
  const NS = "settings.codexSubscription";
@@ -403,10 +1102,8 @@ window.__ModuleLoader__.load({
403
1102
  loadFailed: "无法读取账户状态。",
404
1103
  accountRetry: "重试",
405
1104
  diagnostics: "支持诊断",
406
- diagnosticsHint: "仅含环境与请求状态,不含凭据。",
407
- diagnosticsOpen: "展开",
408
- diagnosticsClose: "收起",
409
1105
  diagnosticsLoad: "生成诊断",
1106
+ diagnosticsLoading: "生成中…",
410
1107
  diagnosticsCopy: "复制诊断",
411
1108
  diagnosticsCopied: "已复制",
412
1109
  diagnosticsFailed: "无法生成诊断信息。",
@@ -446,7 +1143,6 @@ window.__ModuleLoader__.load({
446
1143
  unlimited: "不限额",
447
1144
  monthlyCreditLimit: "Credits 月度消费上限",
448
1145
  resetCredits: "额度重置",
449
- resetCreditsValue: "{count} 次可用",
450
1146
  resetCreditDefaultName: "额度重置",
451
1147
  resetUse: "使用",
452
1148
  resetPreparing: "准备中…",
@@ -555,9 +1251,17 @@ window.__ModuleLoader__.load({
555
1251
  imageZoomIn: "放大",
556
1252
  imageFit: "适合窗口",
557
1253
  imageAnnotate: "标注部位",
1254
+ imageAnnotateCancel: "取消标注",
558
1255
  imageAnnotateHint: "点击图片添加编号标注",
559
1256
  imageAnnotation: "标注 {value}",
560
1257
  imageAnnotationPlaceholder: "描述这个部位要修改什么",
1258
+ imageRegions: "区域备注",
1259
+ imageCopyNotes: "复制备注",
1260
+ imageCopied: "已复制",
1261
+ imagePrevious: "上一张图片",
1262
+ imageNext: "下一张图片",
1263
+ imageZoomHint: "滚轮缩放 · 拖动查看 · 双击切换原始大小",
1264
+ imageActual: "原始大小",
561
1265
  imageEditPrompt: "描述你想怎样修改这张图",
562
1266
  imageEditDefault: "编辑这张图片。",
563
1267
  imageRegionNotes: "部位修改:",
@@ -591,10 +1295,8 @@ window.__ModuleLoader__.load({
591
1295
  loadFailed: "Could not read account status.",
592
1296
  accountRetry: "Retry",
593
1297
  diagnostics: "Support diagnostics",
594
- diagnosticsHint: "Environment and request status only; no credentials.",
595
- diagnosticsOpen: "Show",
596
- diagnosticsClose: "Hide",
597
1298
  diagnosticsLoad: "Create report",
1299
+ diagnosticsLoading: "Creating…",
598
1300
  diagnosticsCopy: "Copy report",
599
1301
  diagnosticsCopied: "Copied",
600
1302
  diagnosticsFailed: "Could not create diagnostics.",
@@ -634,7 +1336,6 @@ window.__ModuleLoader__.load({
634
1336
  unlimited: "Unlimited",
635
1337
  monthlyCreditLimit: "Monthly Credits spending cap",
636
1338
  resetCredits: "Quota resets",
637
- resetCreditsValue: "{count} available",
638
1339
  resetCreditDefaultName: "Quota reset",
639
1340
  resetUse: "Use",
640
1341
  resetPreparing: "Preparing…",
@@ -743,9 +1444,17 @@ window.__ModuleLoader__.load({
743
1444
  imageZoomIn: "Zoom in",
744
1445
  imageFit: "Fit to window",
745
1446
  imageAnnotate: "Annotate",
1447
+ imageAnnotateCancel: "Cancel marking",
746
1448
  imageAnnotateHint: "Click the image to add a numbered note",
747
1449
  imageAnnotation: "Note {value}",
748
1450
  imageAnnotationPlaceholder: "Describe what should change in this area",
1451
+ imageRegions: "Region notes",
1452
+ imageCopyNotes: "Copy notes",
1453
+ imageCopied: "Copied",
1454
+ imagePrevious: "Previous image",
1455
+ imageNext: "Next image",
1456
+ imageZoomHint: "Wheel to zoom · drag to pan · double-click for 100%",
1457
+ imageActual: "100%",
749
1458
  imageEditPrompt: "Describe how you want to change this image",
750
1459
  imageEditDefault: "Edit this image.",
751
1460
  imageRegionNotes: "Region changes:",
@@ -775,7 +1484,7 @@ window.__ModuleLoader__.load({
775
1484
  .codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
776
1485
  .codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}.codexSubscriptionInput{width:100%;box-sizing:border-box}
777
1486
  .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionRecover .codexSubscriptionError{flex:1}.codexSubscriptionRecover button{flex:0 0 auto}
778
- .codexSubscriptionDiagnostics{padding:10px 12px;gap:8px;background:transparent;color:var(--dsw-alias-label-secondary)}.codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}.codexSubscriptionDiagnostics .codexSubscriptionNote{font-size:11px;line-height:17px}.codexSubscriptionDiagnosticsToggle{min-height:28px!important;padding:0 8px!important;border:0!important;color:var(--dsw-alias-label-tertiary)!important}.codexSubscriptionDiagnosticsToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}.codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
1487
+ .codexSubscriptionDiagnostics{padding:8px 12px;gap:8px;background:transparent;color:var(--dsw-alias-label-secondary)}.codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}.codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}.codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
779
1488
  .codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
780
1489
  .codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}.codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
781
1490
  .codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
@@ -785,7 +1494,7 @@ window.__ModuleLoader__.load({
785
1494
  .codexSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
786
1495
  .codexSubscriptionCreditSection{display:flex;flex-direction:column;gap:7px}.codexSubscriptionCreditNote{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionCreditRows{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.codexSubscriptionCreditBalance,.codexSubscriptionSpendLimit{min-width:0;border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-module-platform)}
787
1496
  .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
788
- .codexSubscriptionCreditRows{display:flex;flex-direction:column;gap:6px}.codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetCard{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionResetCard .codexSubscriptionResetMeta{flex:1}.codexSubscriptionResetCard strong{overflow:hidden;font-size:12px;line-height:18px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.codexSubscriptionResetCard .codexSubscriptionActions{flex:0 0 auto}.codexSubscriptionResetCard .codexSubscriptionResetUse{min-height:28px;padding:0 10px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
1497
+ .codexSubscriptionCreditRows{display:flex;flex-direction:column;gap:6px}.codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetCard{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionResetCard .codexSubscriptionResetMeta{flex:1}.codexSubscriptionResetCard strong{overflow:hidden;font-size:12px;line-height:18px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.codexSubscriptionResetCard .codexSubscriptionActions{flex:0 0 auto}.codexSubscriptionResetCard .codexSubscriptionResetUse{min-height:28px;padding:0 10px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
789
1498
  .codexSubscriptionResetUse:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionResetUse:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}
790
1499
  .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
791
1500
  .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
@@ -798,19 +1507,6 @@ window.__ModuleLoader__.load({
798
1507
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
799
1508
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
800
1509
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
801
- `;
802
- const IMAGE_STYLE = String.raw`
803
- .codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:grid;grid-template-rows:auto minmax(0,1fr);background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary);outline:0}.codexGeneratedImageTopbar{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:58px;padding:8px 14px;border-bottom:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-base,#111) 92%,transparent);backdrop-filter:blur(18px)}.codexGeneratedImageTopbar>div:first-child{display:flex;min-width:0;flex-direction:column}.codexGeneratedImageTopbar strong{overflow:hidden;font-size:13px;font-weight:600;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexGeneratedImageTopbar small{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.codexGeneratedImageActions{display:flex;align-items:center;gap:6px}.codexGeneratedImageActions button,.codexGeneratedImageActions a{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:5px;height:32px;padding:0 10px;border:1px solid transparent;border-radius:16px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-decoration:none;cursor:pointer}.codexGeneratedImageActions button:hover,.codexGeneratedImageActions a:hover,.codexGeneratedImageActions button.is-active{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageActions button:focus-visible,.codexGeneratedImageActions a:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexGeneratedImageActions button:disabled{opacity:.4;cursor:default}.codexGeneratedImageActions>span{min-width:38px;color:var(--dsw-alias-label-tertiary);font-size:11px;text-align:center}.codexGeneratedImageClose{font-size:20px!important}.codexGeneratedImageCanvas{position:relative;display:grid;place-items:center;min-height:0;overflow:auto;padding:24px;background:var(--dsw-alias-bg-base,#111)}.codexGeneratedImageSurface{position:relative;display:inline-flex;max-width:calc(100vw - 48px);max-height:calc(100vh - 150px);transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageSurface img{display:block;max-width:100%;max-height:calc(100vh - 150px);border-radius:12px;object-fit:contain}.codexGeneratedImageCanvas.is-annotating .codexGeneratedImageSurface{cursor:crosshair}.codexGeneratedImagePin{position:absolute;display:grid;place-items:center;width:24px;height:24px;padding:0;border:2px solid white;border-radius:50%;background:var(--dsw-alias-label-primary);box-shadow:0 1px 4px rgba(0,0,0,.35);color:var(--dsw-alias-bg-base,#111);font:inherit;font-size:11px;font-weight:700;transform:translate(-50%,-50%);cursor:pointer}.codexGeneratedImagePin.is-active{background:var(--dsw-alias-state-business-primary,#3964fe);color:white}.codexGeneratedImageAnnotateHint{position:absolute;bottom:14px;left:50%;padding:6px 10px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 90%,transparent);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-secondary);font-size:11px;transform:translateX(-50%)}@media(max-width:760px){.codexGeneratedImageTopbar{align-items:flex-start;flex-direction:column}.codexGeneratedImageActions{width:100%;overflow-x:auto}.codexGeneratedImageActions>span{display:none}.codexGeneratedImageCanvas{padding:12px}.codexGeneratedImageSurface{max-width:calc(100vw - 24px);max-height:calc(100vh - 180px)}.codexGeneratedImageSurface img{max-height:calc(100vh - 180px)}}
804
- `;
805
- const IMAGE_LAYOUT_STYLE = String.raw`
806
- .codexGeneratedImageWorkspace{display:grid;min-height:0;grid-template-columns:minmax(0,1fr)}
807
- .codexGeneratedImageWorkspace.has-comments{grid-template-columns:minmax(0,1fr) 296px}
808
- .codexGeneratedImageComments{display:flex;min-width:0;flex-direction:column;gap:12px;overflow:auto;padding:18px 14px;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base))}
809
- .codexGeneratedImageComments>header{display:flex;flex-direction:column;gap:2px}.codexGeneratedImageComments>header strong{font-size:13px;font-weight:600;line-height:20px}.codexGeneratedImageComments>header small{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
810
- .codexGeneratedImageCommentList{display:flex;flex-direction:column;gap:8px}.codexGeneratedImageCommentList article{display:grid;grid-template-columns:24px minmax(0,1fr) 24px;align-items:start;gap:7px;padding:9px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3)}.codexGeneratedImageCommentList article.is-active{border-color:var(--dsw-alias-state-business-primary,#3964fe)}
811
- .codexGeneratedImageCommentList article>span{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-base,#111);font-size:10px;font-weight:700}.codexGeneratedImageCommentList textarea{box-sizing:border-box;width:100%;min-height:64px;resize:vertical;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px}.codexGeneratedImageCommentList textarea::placeholder{color:var(--dsw-alias-label-dimmed)}
812
- .codexGeneratedImageCommentList article>button{display:grid;place-items:center;width:22px;height:22px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:17px;cursor:pointer}.codexGeneratedImageCommentList article>button:hover{background:var(--dsw-alias-interactive-bg-hover)}
813
- @media(max-width:760px){.codexGeneratedImageWorkspace.has-comments{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) minmax(118px,34vh)}.codexGeneratedImageComments{border-top:1px solid var(--dsw-alias-border-l2);border-left:0;padding:10px 12px}}
814
1510
  `;
815
1511
  const unwrap = (response) => {
816
1512
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
@@ -845,7 +1541,6 @@ window.__ModuleLoader__.load({
845
1541
  if (cleaned === "") return fallback;
846
1542
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
847
1543
  };
848
- const imageByteSize = (bytes) => bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
849
1544
  const originalRefMatches = (left, right) => left.assetId === right.assetId && left.mediaType === right.mediaType && left.bytes === right.bytes && left.width === right.width && left.height === right.height && left.name === right.name && left.sha256 === right.sha256;
850
1545
  async function sha256Hex(data) {
851
1546
  const value = await crypto.subtle.digest("SHA-256", data);
@@ -905,25 +1600,16 @@ window.__ModuleLoader__.load({
905
1600
  URL.revokeObjectURL(url);
906
1601
  }
907
1602
  }
908
- function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, t }) {
1603
+ function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
909
1604
  const [attempt, setAttempt] = (0, react.useState)(0);
910
1605
  const [error, setError] = (0, react.useState)(false);
911
- const [open, setOpen] = (0, react.useState)(false);
912
1606
  const [src, setSrc] = (0, react.useState)();
913
- const [zoom, setZoom] = (0, react.useState)(1);
914
- const [annotationMode, setAnnotationMode] = (0, react.useState)(false);
915
- const [annotations, setAnnotations] = (0, react.useState)([]);
916
- const [selectedAnnotation, setSelectedAnnotation] = (0, react.useState)();
917
- const [editBusy, setEditBusy] = (0, react.useState)(false);
918
- const [editError, setEditError] = (0, react.useState)(false);
919
- const [downloadState, setDownloadState] = (0, react.useState)("idle");
920
1607
  const triggerRef = (0, react.useRef)(null);
921
- const dialogRef = (0, react.useRef)(null);
922
1608
  (0, react.useEffect)(() => {
923
1609
  let live = true;
924
1610
  setError(false);
925
1611
  setSrc(void 0);
926
- loadImage(attachment).then((value) => {
1612
+ Promise.resolve().then(() => loadImage(attachment)).then((value) => {
927
1613
  if (live) setSrc(value);
928
1614
  }).catch(() => {
929
1615
  if (live) setError(true);
@@ -936,100 +1622,15 @@ window.__ModuleLoader__.load({
936
1622
  loadImage,
937
1623
  attempt
938
1624
  ]);
939
- const close = () => {
940
- setOpen(false);
941
- window.requestAnimationFrame(() => triggerRef.current?.focus());
942
- };
943
- (0, react.useEffect)(() => {
944
- if (!open) return void 0;
945
- const previousOverflow = document.body.style.overflow;
946
- document.body.style.overflow = "hidden";
947
- dialogRef.current?.focus();
948
- const keydown = (event) => {
949
- if (event.key === "Escape") {
950
- event.preventDefault();
951
- close();
952
- return;
953
- }
954
- if (event.key === "Tab") {
955
- const focusable = [...dialogRef.current.querySelectorAll("button:not(:disabled),a[href],input:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])")];
956
- if (focusable.length === 0) return;
957
- const first = focusable[0];
958
- const last = focusable.at(-1);
959
- if (event.shiftKey && document.activeElement === first) {
960
- event.preventDefault();
961
- last.focus();
962
- } else if (!event.shiftKey && document.activeElement === last) {
963
- event.preventDefault();
964
- first.focus();
965
- }
966
- }
967
- };
968
- document.addEventListener("keydown", keydown);
969
- return () => {
970
- document.body.style.overflow = previousOverflow;
971
- document.removeEventListener("keydown", keydown);
972
- };
973
- }, [open]);
974
1625
  const label = attachment.name ?? t("imageLabel");
975
1626
  const downloadName = imageDownloadName(attachment);
976
- const previewMeta = attachment.width + " × " + attachment.height + " · " + imageByteSize(attachment.bytes);
977
- const imageMeta = original === void 0 ? previewMeta : original.width + " × " + original.height + " · " + imageByteSize(original.bytes) + " · " + t("imagePreviewShort") + " " + previewMeta;
978
- const addAnnotation = (event) => {
979
- if (!annotationMode || event.target.closest(".codexGeneratedImagePin")) return;
980
- const bounds = event.currentTarget.getBoundingClientRect();
981
- const annotation = {
982
- id: crypto.randomUUID(),
983
- x: Math.max(0, Math.min(1, (event.clientX - bounds.left) / bounds.width)),
984
- y: Math.max(0, Math.min(1, (event.clientY - bounds.top) / bounds.height)),
985
- note: ""
986
- };
987
- setAnnotations((value) => [...value, annotation]);
988
- setSelectedAnnotation(annotation.id);
989
- };
990
- const continueEditing = async () => {
991
- if (editBusy || src === void 0) return;
992
- setEditBusy(true);
993
- setEditError(false);
994
- try {
995
- await attachForEdit(src, downloadName, buildImageEditDraft({
996
- annotations,
997
- translate: t
998
- }));
999
- close();
1000
- } catch {
1001
- setEditError(true);
1002
- } finally {
1003
- setEditBusy(false);
1004
- }
1005
- };
1006
1627
  const downloadOriginal = async () => {
1007
- if (downloadState === "pending" || original === void 0) return;
1008
- setDownloadState("pending");
1009
- try {
1010
- triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
1011
- setDownloadState("idle");
1012
- } catch {
1013
- setDownloadState("failed");
1014
- throw new Error("original image download failed");
1015
- }
1628
+ if (original === void 0) return;
1629
+ triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
1016
1630
  };
1017
1631
  const openImage = () => {
1018
1632
  if (src === void 0) return;
1019
- const viewer = getImageViewer?.();
1020
- const actions = [];
1021
- actions.push({
1022
- id: "continue-editing",
1023
- label: t("imageEdit"),
1024
- pendingLabel: t("imageEditPreparing"),
1025
- errorLabel: t("imageEditFailed"),
1026
- closeOnSuccess: true,
1027
- onInvoke: ({ annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
1028
- annotations: nextAnnotations,
1029
- translate: t
1030
- }))
1031
- });
1032
- if (viewer?.open?.({
1633
+ const request = {
1033
1634
  items: [{
1034
1635
  id: attachment.attachmentId ?? downloadName,
1035
1636
  src,
@@ -1042,14 +1643,24 @@ window.__ModuleLoader__.load({
1042
1643
  errorLabel: t("imageDownloadFailed"),
1043
1644
  onInvoke: downloadOriginal
1044
1645
  },
1045
- actions
1646
+ actions: [{
1647
+ id: "continue-editing",
1648
+ label: t("imageEdit"),
1649
+ pendingLabel: t("imageEditPreparing"),
1650
+ errorLabel: t("imageEditFailed"),
1651
+ closeOnSuccess: true,
1652
+ onInvoke: ({ annotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
1653
+ annotations,
1654
+ translate: t
1655
+ }))
1656
+ }]
1046
1657
  }],
1047
1658
  opener: triggerRef.current,
1048
1659
  source: "codex-generated",
1049
1660
  annotations: true
1050
- }) === true) return;
1051
- setZoom(1);
1052
- setOpen(true);
1661
+ };
1662
+ if ((getImageViewer?.())?.open?.(request) === true) return;
1663
+ getInternalImageViewer?.()?.open?.(request);
1053
1664
  };
1054
1665
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1055
1666
  type: "button",
@@ -1057,151 +1668,7 @@ window.__ModuleLoader__.load({
1057
1668
  onClick: () => setAttempt((value) => value + 1),
1058
1669
  children: t("imageLoadFailed")
1059
1670
  });
1060
- const lightbox = !open || src === void 0 ? null : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1061
- ref: dialogRef,
1062
- className: "codexGeneratedImageLightbox",
1063
- role: "dialog",
1064
- "aria-modal": "true",
1065
- "aria-label": t("imagePreview"),
1066
- tabIndex: -1,
1067
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1068
- className: "codexGeneratedImageTopbar",
1069
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: imageMeta })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1070
- className: "codexGeneratedImageActions",
1071
- children: [
1072
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1073
- type: "button",
1074
- className: annotationMode ? "is-active" : "",
1075
- "aria-pressed": annotationMode,
1076
- onClick: () => setAnnotationMode((value) => !value),
1077
- children: t("imageAnnotate")
1078
- }),
1079
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1080
- type: "button",
1081
- "aria-label": t("imageZoomOut"),
1082
- disabled: zoom <= .5,
1083
- onClick: () => setZoom((value) => Math.max(.5, value - .25)),
1084
- children: "−"
1085
- }),
1086
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [Math.round(zoom * 100), "%"] }),
1087
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1088
- type: "button",
1089
- "aria-label": t("imageZoomIn"),
1090
- disabled: zoom >= 3,
1091
- onClick: () => setZoom((value) => Math.min(3, value + .25)),
1092
- children: "+"
1093
- }),
1094
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1095
- type: "button",
1096
- onClick: () => setZoom(1),
1097
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
1098
- }),
1099
- original === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
1100
- href: src,
1101
- download: downloadName,
1102
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
1103
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1104
- type: "button",
1105
- disabled: downloadState === "pending",
1106
- onClick: () => {
1107
- downloadOriginal().catch(() => void 0);
1108
- },
1109
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), downloadState === "pending" ? t("imageDownloadPreparing") : downloadState === "failed" ? t("imageDownloadFailed") : t("imageDownload")]
1110
- }),
1111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1112
- type: "button",
1113
- disabled: editBusy,
1114
- onClick: () => {
1115
- continueEditing();
1116
- },
1117
- children: editBusy ? t("imageEditPreparing") : editError ? t("imageEditFailed") : t("imageEdit")
1118
- }),
1119
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1120
- type: "button",
1121
- className: "codexGeneratedImageClose",
1122
- "aria-label": t("imageClosePreview"),
1123
- onClick: close,
1124
- children: "×"
1125
- })
1126
- ]
1127
- })]
1128
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1129
- className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
1130
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1131
- className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
1132
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1133
- className: "codexGeneratedImageSurface",
1134
- onClick: addAnnotation,
1135
- style: { transform: "scale(" + zoom + ")" },
1136
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1137
- src,
1138
- alt: label
1139
- }), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1140
- type: "button",
1141
- className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
1142
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
1143
- style: {
1144
- left: annotation.x * 100 + "%",
1145
- top: annotation.y * 100 + "%"
1146
- },
1147
- onClick: (event) => {
1148
- event.stopPropagation();
1149
- setSelectedAnnotation(annotation.id);
1150
- },
1151
- children: index + 1
1152
- }, annotation.id))]
1153
- }), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1154
- className: "codexGeneratedImageAnnotateHint",
1155
- children: t("imageAnnotateHint")
1156
- }) : null]
1157
- }), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1158
- className: "codexGeneratedImageComments",
1159
- "aria-label": t("imageAnnotate"),
1160
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("imageAnnotate") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("imageAnnotateHint") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1161
- className: "codexGeneratedImageCommentList",
1162
- children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
1163
- className: annotation.id === selectedAnnotation ? "is-active" : "",
1164
- onClick: () => setSelectedAnnotation(annotation.id),
1165
- children: [
1166
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
1167
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1168
- value: annotation.note,
1169
- rows: 3,
1170
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
1171
- placeholder: t("imageAnnotationPlaceholder"),
1172
- onFocus: () => setSelectedAnnotation(annotation.id),
1173
- onChange: (event) => {
1174
- const note = event.target.value;
1175
- setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
1176
- ...item,
1177
- note
1178
- } : item));
1179
- },
1180
- onKeyDown: (event) => {
1181
- if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
1182
- event.preventDefault();
1183
- event.stopPropagation();
1184
- setSelectedAnnotation(void 0);
1185
- }
1186
- }
1187
- }),
1188
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1189
- type: "button",
1190
- "aria-label": t("imageRemoveAnnotation"),
1191
- onClick: (event) => {
1192
- event.stopPropagation();
1193
- setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
1194
- if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
1195
- },
1196
- children: "×"
1197
- })
1198
- ]
1199
- }, annotation.id))
1200
- })]
1201
- }) : null]
1202
- })]
1203
- }), document.body);
1204
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1671
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1205
1672
  ref: triggerRef,
1206
1673
  type: "button",
1207
1674
  className: "codexGeneratedImageFrame",
@@ -1212,9 +1679,9 @@ window.__ModuleLoader__.load({
1212
1679
  src,
1213
1680
  alt: label
1214
1681
  })
1215
- }), lightbox] });
1682
+ });
1216
1683
  }
1217
- function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, t }) {
1684
+ function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
1218
1685
  const settled = block?.kind === "tool-result";
1219
1686
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
1220
1687
  const failed = settled && block.isError === true;
@@ -1257,6 +1724,7 @@ window.__ModuleLoader__.load({
1257
1724
  loadImage,
1258
1725
  attachForEdit,
1259
1726
  getImageViewer,
1727
+ getInternalImageViewer,
1260
1728
  t
1261
1729
  })
1262
1730
  }),
@@ -2278,7 +2746,6 @@ window.__ModuleLoader__.load({
2278
2746
  });
2279
2747
  }
2280
2748
  function DiagnosticsCard({ rpc, t }) {
2281
- const [diagnosticsOpen, setDiagnosticsOpen] = (0, react.useState)(false);
2282
2749
  const [report, setReport] = (0, react.useState)();
2283
2750
  const [busy, setBusy] = (0, react.useState)(false);
2284
2751
  const [copied, setCopied] = (0, react.useState)(false);
@@ -2295,46 +2762,36 @@ window.__ModuleLoader__.load({
2295
2762
  };
2296
2763
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2297
2764
  className: "codexSubscriptionCard codexSubscriptionDiagnostics",
2298
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2299
- className: "codexSubscriptionSectionHead",
2300
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2301
- className: "codexSubscriptionSectionTitle",
2302
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("diagnostics") })
2303
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2304
- className: "codexSubscriptionActions",
2305
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2306
- className: "codexSubscriptionDiagnosticsToggle",
2307
- type: "button",
2308
- variant: "outline",
2309
- "aria-expanded": diagnosticsOpen,
2310
- onClick: () => setDiagnosticsOpen((value) => !value),
2311
- children: t(diagnosticsOpen ? "diagnosticsClose" : "diagnosticsOpen")
2312
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2313
- className: "codexSubscriptionLink",
2314
- href: SUPPORT_ISSUE_URL,
2315
- target: "_blank",
2316
- rel: "noreferrer",
2317
- children: t("feedbackOpen")
2318
- })]
2319
- })]
2320
- }), diagnosticsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2321
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2322
- className: "codexSubscriptionNote",
2323
- children: t("diagnosticsHint")
2324
- }),
2765
+ children: [
2325
2766
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2326
- className: "codexSubscriptionActions",
2327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2328
- type: "button",
2329
- variant: "outline",
2330
- disabled: busy,
2331
- onClick: load,
2332
- children: busy ? t("resetPreparing") : t("diagnosticsLoad")
2333
- }), report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2334
- type: "button",
2335
- variant: "outline",
2336
- onClick: copy,
2337
- children: copied ? t("diagnosticsCopied") : t("diagnosticsCopy")
2767
+ className: "codexSubscriptionSectionHead",
2768
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2769
+ className: "codexSubscriptionSectionTitle",
2770
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("diagnostics") })
2771
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2772
+ className: "codexSubscriptionActions",
2773
+ children: [
2774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2775
+ type: "button",
2776
+ variant: "outline",
2777
+ disabled: busy,
2778
+ onClick: load,
2779
+ children: busy ? t("diagnosticsLoading") : t("diagnosticsLoad")
2780
+ }),
2781
+ report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2782
+ type: "button",
2783
+ variant: "outline",
2784
+ onClick: copy,
2785
+ children: copied ? t("diagnosticsCopied") : t("diagnosticsCopy")
2786
+ }),
2787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2788
+ className: "codexSubscriptionLink",
2789
+ href: SUPPORT_ISSUE_URL,
2790
+ target: "_blank",
2791
+ rel: "noreferrer",
2792
+ children: t("feedbackOpen")
2793
+ })
2794
+ ]
2338
2795
  })]
2339
2796
  }),
2340
2797
  report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(report, null, 2) }),
@@ -2343,7 +2800,7 @@ window.__ModuleLoader__.load({
2343
2800
  role: "alert",
2344
2801
  children: t("diagnosticsFailed")
2345
2802
  }) : null
2346
- ] }) : null]
2803
+ ]
2347
2804
  });
2348
2805
  }
2349
2806
  function ResetTime({ resetsAt, t }) {
@@ -2700,10 +3157,7 @@ window.__ModuleLoader__.load({
2700
3157
  }) : null,
2701
3158
  visibleUsage?.resetCredits?.availableCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2702
3159
  className: "codexSubscriptionCreditBalance",
2703
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2704
- className: "codexSubscriptionResetSummary",
2705
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCredits") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fill(t("resetCreditsValue"), { count: visibleUsage.resetCredits.availableCount }) })]
2706
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditList, {
3160
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCredits") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditList, {
2707
3161
  rpc,
2708
3162
  t,
2709
3163
  count: visibleUsage.resetCredits.availableCount,
@@ -2803,6 +3257,7 @@ window.__ModuleLoader__.load({
2803
3257
  });
2804
3258
  }
2805
3259
  function apply(ctx) {
3260
+ const imageViewer = new SubscriptionImageViewerService();
2806
3261
  ctx.effect(() => ctx.locale.register(NS, {
2807
3262
  zh,
2808
3263
  en
@@ -2810,7 +3265,7 @@ window.__ModuleLoader__.load({
2810
3265
  ctx.effect(() => {
2811
3266
  const tag = document.createElement("style");
2812
3267
  tag.dataset.plugin = "dsh-codex-subscription";
2813
- tag.textContent = STYLE + IMAGE_STYLE + IMAGE_LAYOUT_STYLE;
3268
+ tag.textContent = STYLE + SUBSCRIPTION_IMAGE_VIEWER_CSS;
2814
3269
  document.head.append(tag);
2815
3270
  return () => tag.remove();
2816
3271
  }, "codex-subscription: style");
@@ -2827,6 +3282,15 @@ window.__ModuleLoader__.load({
2827
3282
  };
2828
3283
  }, "codex-subscription: preferences");
2829
3284
  const t = ctx.locale.bind(NS);
3285
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3286
+ name: "shell.overlay",
3287
+ id: "codex-subscription-image-viewer",
3288
+ order: 20,
3289
+ inject: () => ({
3290
+ service: imageViewer,
3291
+ t
3292
+ })
3293
+ }, SubscriptionImageViewerOverlay));
2830
3294
  ctx.slots.inject("settings.section", () => ctx.slots.register({
2831
3295
  name: "settings.section",
2832
3296
  id: "codex-subscription",
@@ -2876,6 +3340,7 @@ window.__ModuleLoader__.load({
2876
3340
  if (ctx.get("remote.session") === void 0) installDirectorySlots(ctx);
2877
3341
  else ctx.inject(["remote.session"], installDirectorySlots);
2878
3342
  const conversation = ctx.get("conversation");
3343
+ const uiConversation = ctx.get("uiConversation");
2879
3344
  ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
2880
3345
  name: "tool.call.toolview",
2881
3346
  key: "codex_image_generate",
@@ -2884,7 +3349,7 @@ window.__ModuleLoader__.load({
2884
3349
  sessionId,
2885
3350
  rpc: connection.rpc,
2886
3351
  t,
2887
- loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
3352
+ loadImage: (attachment) => uiConversation.imageUrl(sessionId, attachment),
2888
3353
  getImageViewer: () => {
2889
3354
  try {
2890
3355
  return ctx.get("nativeImageViewer");
@@ -2892,6 +3357,7 @@ window.__ModuleLoader__.load({
2892
3357
  return;
2893
3358
  }
2894
3359
  },
3360
+ getInternalImageViewer: () => imageViewer,
2895
3361
  attachForEdit: async (src, filename, draft) => {
2896
3362
  const actx = sessions.scope(sessionId);
2897
3363
  if (actx === void 0 || typeof conversation.createDraftImages !== "function" || conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");