dsh-codex-subscription 1.13.1 → 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";
@@ -552,9 +1251,17 @@ window.__ModuleLoader__.load({
552
1251
  imageZoomIn: "放大",
553
1252
  imageFit: "适合窗口",
554
1253
  imageAnnotate: "标注部位",
1254
+ imageAnnotateCancel: "取消标注",
555
1255
  imageAnnotateHint: "点击图片添加编号标注",
556
1256
  imageAnnotation: "标注 {value}",
557
1257
  imageAnnotationPlaceholder: "描述这个部位要修改什么",
1258
+ imageRegions: "区域备注",
1259
+ imageCopyNotes: "复制备注",
1260
+ imageCopied: "已复制",
1261
+ imagePrevious: "上一张图片",
1262
+ imageNext: "下一张图片",
1263
+ imageZoomHint: "滚轮缩放 · 拖动查看 · 双击切换原始大小",
1264
+ imageActual: "原始大小",
558
1265
  imageEditPrompt: "描述你想怎样修改这张图",
559
1266
  imageEditDefault: "编辑这张图片。",
560
1267
  imageRegionNotes: "部位修改:",
@@ -737,9 +1444,17 @@ window.__ModuleLoader__.load({
737
1444
  imageZoomIn: "Zoom in",
738
1445
  imageFit: "Fit to window",
739
1446
  imageAnnotate: "Annotate",
1447
+ imageAnnotateCancel: "Cancel marking",
740
1448
  imageAnnotateHint: "Click the image to add a numbered note",
741
1449
  imageAnnotation: "Note {value}",
742
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%",
743
1458
  imageEditPrompt: "Describe how you want to change this image",
744
1459
  imageEditDefault: "Edit this image.",
745
1460
  imageRegionNotes: "Region changes:",
@@ -792,19 +1507,6 @@ window.__ModuleLoader__.load({
792
1507
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
793
1508
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
794
1509
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
795
- `;
796
- const IMAGE_STYLE = String.raw`
797
- .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)}}
798
- `;
799
- const IMAGE_LAYOUT_STYLE = String.raw`
800
- .codexGeneratedImageWorkspace{display:grid;min-height:0;grid-template-columns:minmax(0,1fr)}
801
- .codexGeneratedImageWorkspace.has-comments{grid-template-columns:minmax(0,1fr) 296px}
802
- .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))}
803
- .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}
804
- .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)}
805
- .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)}
806
- .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)}
807
- @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}}
808
1510
  `;
809
1511
  const unwrap = (response) => {
810
1512
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
@@ -839,7 +1541,6 @@ window.__ModuleLoader__.load({
839
1541
  if (cleaned === "") return fallback;
840
1542
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
841
1543
  };
842
- 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`;
843
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;
844
1545
  async function sha256Hex(data) {
845
1546
  const value = await crypto.subtle.digest("SHA-256", data);
@@ -899,25 +1600,16 @@ window.__ModuleLoader__.load({
899
1600
  URL.revokeObjectURL(url);
900
1601
  }
901
1602
  }
902
- function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, t }) {
1603
+ function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
903
1604
  const [attempt, setAttempt] = (0, react.useState)(0);
904
1605
  const [error, setError] = (0, react.useState)(false);
905
- const [open, setOpen] = (0, react.useState)(false);
906
1606
  const [src, setSrc] = (0, react.useState)();
907
- const [zoom, setZoom] = (0, react.useState)(1);
908
- const [annotationMode, setAnnotationMode] = (0, react.useState)(false);
909
- const [annotations, setAnnotations] = (0, react.useState)([]);
910
- const [selectedAnnotation, setSelectedAnnotation] = (0, react.useState)();
911
- const [editBusy, setEditBusy] = (0, react.useState)(false);
912
- const [editError, setEditError] = (0, react.useState)(false);
913
- const [downloadState, setDownloadState] = (0, react.useState)("idle");
914
1607
  const triggerRef = (0, react.useRef)(null);
915
- const dialogRef = (0, react.useRef)(null);
916
1608
  (0, react.useEffect)(() => {
917
1609
  let live = true;
918
1610
  setError(false);
919
1611
  setSrc(void 0);
920
- loadImage(attachment).then((value) => {
1612
+ Promise.resolve().then(() => loadImage(attachment)).then((value) => {
921
1613
  if (live) setSrc(value);
922
1614
  }).catch(() => {
923
1615
  if (live) setError(true);
@@ -930,100 +1622,15 @@ window.__ModuleLoader__.load({
930
1622
  loadImage,
931
1623
  attempt
932
1624
  ]);
933
- const close = () => {
934
- setOpen(false);
935
- window.requestAnimationFrame(() => triggerRef.current?.focus());
936
- };
937
- (0, react.useEffect)(() => {
938
- if (!open) return void 0;
939
- const previousOverflow = document.body.style.overflow;
940
- document.body.style.overflow = "hidden";
941
- dialogRef.current?.focus();
942
- const keydown = (event) => {
943
- if (event.key === "Escape") {
944
- event.preventDefault();
945
- close();
946
- return;
947
- }
948
- if (event.key === "Tab") {
949
- const focusable = [...dialogRef.current.querySelectorAll("button:not(:disabled),a[href],input:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])")];
950
- if (focusable.length === 0) return;
951
- const first = focusable[0];
952
- const last = focusable.at(-1);
953
- if (event.shiftKey && document.activeElement === first) {
954
- event.preventDefault();
955
- last.focus();
956
- } else if (!event.shiftKey && document.activeElement === last) {
957
- event.preventDefault();
958
- first.focus();
959
- }
960
- }
961
- };
962
- document.addEventListener("keydown", keydown);
963
- return () => {
964
- document.body.style.overflow = previousOverflow;
965
- document.removeEventListener("keydown", keydown);
966
- };
967
- }, [open]);
968
1625
  const label = attachment.name ?? t("imageLabel");
969
1626
  const downloadName = imageDownloadName(attachment);
970
- const previewMeta = attachment.width + " × " + attachment.height + " · " + imageByteSize(attachment.bytes);
971
- const imageMeta = original === void 0 ? previewMeta : original.width + " × " + original.height + " · " + imageByteSize(original.bytes) + " · " + t("imagePreviewShort") + " " + previewMeta;
972
- const addAnnotation = (event) => {
973
- if (!annotationMode || event.target.closest(".codexGeneratedImagePin")) return;
974
- const bounds = event.currentTarget.getBoundingClientRect();
975
- const annotation = {
976
- id: crypto.randomUUID(),
977
- x: Math.max(0, Math.min(1, (event.clientX - bounds.left) / bounds.width)),
978
- y: Math.max(0, Math.min(1, (event.clientY - bounds.top) / bounds.height)),
979
- note: ""
980
- };
981
- setAnnotations((value) => [...value, annotation]);
982
- setSelectedAnnotation(annotation.id);
983
- };
984
- const continueEditing = async () => {
985
- if (editBusy || src === void 0) return;
986
- setEditBusy(true);
987
- setEditError(false);
988
- try {
989
- await attachForEdit(src, downloadName, buildImageEditDraft({
990
- annotations,
991
- translate: t
992
- }));
993
- close();
994
- } catch {
995
- setEditError(true);
996
- } finally {
997
- setEditBusy(false);
998
- }
999
- };
1000
1627
  const downloadOriginal = async () => {
1001
- if (downloadState === "pending" || original === void 0) return;
1002
- setDownloadState("pending");
1003
- try {
1004
- triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
1005
- setDownloadState("idle");
1006
- } catch {
1007
- setDownloadState("failed");
1008
- throw new Error("original image download failed");
1009
- }
1628
+ if (original === void 0) return;
1629
+ triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
1010
1630
  };
1011
1631
  const openImage = () => {
1012
1632
  if (src === void 0) return;
1013
- const viewer = getImageViewer?.();
1014
- const actions = [];
1015
- actions.push({
1016
- id: "continue-editing",
1017
- label: t("imageEdit"),
1018
- pendingLabel: t("imageEditPreparing"),
1019
- errorLabel: t("imageEditFailed"),
1020
- closeOnSuccess: true,
1021
- onInvoke: ({ annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
1022
- annotations: nextAnnotations,
1023
- translate: t
1024
- }))
1025
- });
1026
- if (viewer?.open?.({
1633
+ const request = {
1027
1634
  items: [{
1028
1635
  id: attachment.attachmentId ?? downloadName,
1029
1636
  src,
@@ -1036,14 +1643,24 @@ window.__ModuleLoader__.load({
1036
1643
  errorLabel: t("imageDownloadFailed"),
1037
1644
  onInvoke: downloadOriginal
1038
1645
  },
1039
- 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
+ }]
1040
1657
  }],
1041
1658
  opener: triggerRef.current,
1042
1659
  source: "codex-generated",
1043
1660
  annotations: true
1044
- }) === true) return;
1045
- setZoom(1);
1046
- setOpen(true);
1661
+ };
1662
+ if ((getImageViewer?.())?.open?.(request) === true) return;
1663
+ getInternalImageViewer?.()?.open?.(request);
1047
1664
  };
1048
1665
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1049
1666
  type: "button",
@@ -1051,151 +1668,7 @@ window.__ModuleLoader__.load({
1051
1668
  onClick: () => setAttempt((value) => value + 1),
1052
1669
  children: t("imageLoadFailed")
1053
1670
  });
1054
- const lightbox = !open || src === void 0 ? null : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1055
- ref: dialogRef,
1056
- className: "codexGeneratedImageLightbox",
1057
- role: "dialog",
1058
- "aria-modal": "true",
1059
- "aria-label": t("imagePreview"),
1060
- tabIndex: -1,
1061
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1062
- className: "codexGeneratedImageTopbar",
1063
- 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", {
1064
- className: "codexGeneratedImageActions",
1065
- children: [
1066
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1067
- type: "button",
1068
- className: annotationMode ? "is-active" : "",
1069
- "aria-pressed": annotationMode,
1070
- onClick: () => setAnnotationMode((value) => !value),
1071
- children: t("imageAnnotate")
1072
- }),
1073
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1074
- type: "button",
1075
- "aria-label": t("imageZoomOut"),
1076
- disabled: zoom <= .5,
1077
- onClick: () => setZoom((value) => Math.max(.5, value - .25)),
1078
- children: "−"
1079
- }),
1080
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [Math.round(zoom * 100), "%"] }),
1081
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1082
- type: "button",
1083
- "aria-label": t("imageZoomIn"),
1084
- disabled: zoom >= 3,
1085
- onClick: () => setZoom((value) => Math.min(3, value + .25)),
1086
- children: "+"
1087
- }),
1088
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1089
- type: "button",
1090
- onClick: () => setZoom(1),
1091
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
1092
- }),
1093
- original === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
1094
- href: src,
1095
- download: downloadName,
1096
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
1097
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1098
- type: "button",
1099
- disabled: downloadState === "pending",
1100
- onClick: () => {
1101
- downloadOriginal().catch(() => void 0);
1102
- },
1103
- 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")]
1104
- }),
1105
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1106
- type: "button",
1107
- disabled: editBusy,
1108
- onClick: () => {
1109
- continueEditing();
1110
- },
1111
- children: editBusy ? t("imageEditPreparing") : editError ? t("imageEditFailed") : t("imageEdit")
1112
- }),
1113
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1114
- type: "button",
1115
- className: "codexGeneratedImageClose",
1116
- "aria-label": t("imageClosePreview"),
1117
- onClick: close,
1118
- children: "×"
1119
- })
1120
- ]
1121
- })]
1122
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1123
- className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
1124
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1125
- className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
1126
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1127
- className: "codexGeneratedImageSurface",
1128
- onClick: addAnnotation,
1129
- style: { transform: "scale(" + zoom + ")" },
1130
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1131
- src,
1132
- alt: label
1133
- }), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1134
- type: "button",
1135
- className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
1136
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
1137
- style: {
1138
- left: annotation.x * 100 + "%",
1139
- top: annotation.y * 100 + "%"
1140
- },
1141
- onClick: (event) => {
1142
- event.stopPropagation();
1143
- setSelectedAnnotation(annotation.id);
1144
- },
1145
- children: index + 1
1146
- }, annotation.id))]
1147
- }), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1148
- className: "codexGeneratedImageAnnotateHint",
1149
- children: t("imageAnnotateHint")
1150
- }) : null]
1151
- }), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1152
- className: "codexGeneratedImageComments",
1153
- "aria-label": t("imageAnnotate"),
1154
- 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", {
1155
- className: "codexGeneratedImageCommentList",
1156
- children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
1157
- className: annotation.id === selectedAnnotation ? "is-active" : "",
1158
- onClick: () => setSelectedAnnotation(annotation.id),
1159
- children: [
1160
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
1161
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1162
- value: annotation.note,
1163
- rows: 3,
1164
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
1165
- placeholder: t("imageAnnotationPlaceholder"),
1166
- onFocus: () => setSelectedAnnotation(annotation.id),
1167
- onChange: (event) => {
1168
- const note = event.target.value;
1169
- setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
1170
- ...item,
1171
- note
1172
- } : item));
1173
- },
1174
- onKeyDown: (event) => {
1175
- if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
1176
- event.preventDefault();
1177
- event.stopPropagation();
1178
- setSelectedAnnotation(void 0);
1179
- }
1180
- }
1181
- }),
1182
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1183
- type: "button",
1184
- "aria-label": t("imageRemoveAnnotation"),
1185
- onClick: (event) => {
1186
- event.stopPropagation();
1187
- setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
1188
- if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
1189
- },
1190
- children: "×"
1191
- })
1192
- ]
1193
- }, annotation.id))
1194
- })]
1195
- }) : null]
1196
- })]
1197
- }), document.body);
1198
- 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", {
1199
1672
  ref: triggerRef,
1200
1673
  type: "button",
1201
1674
  className: "codexGeneratedImageFrame",
@@ -1206,9 +1679,9 @@ window.__ModuleLoader__.load({
1206
1679
  src,
1207
1680
  alt: label
1208
1681
  })
1209
- }), lightbox] });
1682
+ });
1210
1683
  }
1211
- function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, t }) {
1684
+ function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
1212
1685
  const settled = block?.kind === "tool-result";
1213
1686
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
1214
1687
  const failed = settled && block.isError === true;
@@ -1251,6 +1724,7 @@ window.__ModuleLoader__.load({
1251
1724
  loadImage,
1252
1725
  attachForEdit,
1253
1726
  getImageViewer,
1727
+ getInternalImageViewer,
1254
1728
  t
1255
1729
  })
1256
1730
  }),
@@ -2783,6 +3257,7 @@ window.__ModuleLoader__.load({
2783
3257
  });
2784
3258
  }
2785
3259
  function apply(ctx) {
3260
+ const imageViewer = new SubscriptionImageViewerService();
2786
3261
  ctx.effect(() => ctx.locale.register(NS, {
2787
3262
  zh,
2788
3263
  en
@@ -2790,7 +3265,7 @@ window.__ModuleLoader__.load({
2790
3265
  ctx.effect(() => {
2791
3266
  const tag = document.createElement("style");
2792
3267
  tag.dataset.plugin = "dsh-codex-subscription";
2793
- tag.textContent = STYLE + IMAGE_STYLE + IMAGE_LAYOUT_STYLE;
3268
+ tag.textContent = STYLE + SUBSCRIPTION_IMAGE_VIEWER_CSS;
2794
3269
  document.head.append(tag);
2795
3270
  return () => tag.remove();
2796
3271
  }, "codex-subscription: style");
@@ -2807,6 +3282,15 @@ window.__ModuleLoader__.load({
2807
3282
  };
2808
3283
  }, "codex-subscription: preferences");
2809
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));
2810
3294
  ctx.slots.inject("settings.section", () => ctx.slots.register({
2811
3295
  name: "settings.section",
2812
3296
  id: "codex-subscription",
@@ -2856,6 +3340,7 @@ window.__ModuleLoader__.load({
2856
3340
  if (ctx.get("remote.session") === void 0) installDirectorySlots(ctx);
2857
3341
  else ctx.inject(["remote.session"], installDirectorySlots);
2858
3342
  const conversation = ctx.get("conversation");
3343
+ const uiConversation = ctx.get("uiConversation");
2859
3344
  ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
2860
3345
  name: "tool.call.toolview",
2861
3346
  key: "codex_image_generate",
@@ -2864,7 +3349,7 @@ window.__ModuleLoader__.load({
2864
3349
  sessionId,
2865
3350
  rpc: connection.rpc,
2866
3351
  t,
2867
- loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
3352
+ loadImage: (attachment) => uiConversation.imageUrl(sessionId, attachment),
2868
3353
  getImageViewer: () => {
2869
3354
  try {
2870
3355
  return ctx.get("nativeImageViewer");
@@ -2872,6 +3357,7 @@ window.__ModuleLoader__.load({
2872
3357
  return;
2873
3358
  }
2874
3359
  },
3360
+ getInternalImageViewer: () => imageViewer,
2875
3361
  attachForEdit: async (src, filename, draft) => {
2876
3362
  const actx = sessions.scope(sessionId);
2877
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");