dsh-codex-subscription 1.13.1 → 1.14.1

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
@@ -50,14 +49,193 @@ window.__ModuleLoader__.load({
50
49
  const ForwardRef = /*#__PURE__*/ react.forwardRef(BoltIcon);
51
50
  //#endregion
52
51
  //#region src/image-edit.js
53
- function buildImageEditDraft({ prompt = "", annotations = [], translate }) {
52
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
53
+ const validCoordinate = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
54
+ const cleanNote = (value) => typeof value === "string" ? value.trim() : "";
55
+ const coordinateError = (number) => {
56
+ const error = /* @__PURE__ */ new Error(`Annotation ${number} must have finite x and y coordinates between 0 and 1`);
57
+ error.code = "ANNOTATION_INVALID";
58
+ return error;
59
+ };
60
+ /**
61
+ * Validate the annotation contract shared by the draft builder and the
62
+ * reference-image renderer. Annotation numbers are their array positions so
63
+ * that they stay aligned with the pins shown to the user.
64
+ */
65
+ function normalizeImageEditAnnotations(annotations, { requireNotes = false } = {}) {
66
+ if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
67
+ return annotations.map((annotation, index) => {
68
+ const number = index + 1;
69
+ const note = cleanNote(annotation?.note);
70
+ if (requireNotes && note === "") {
71
+ const error = /* @__PURE__ */ new Error(`Annotation ${number} is missing a note; describe what should change`);
72
+ error.code = "ANNOTATION_INVALID";
73
+ throw error;
74
+ }
75
+ if (!isRecord(annotation) || !validCoordinate(annotation.x) || !validCoordinate(annotation.y)) throw coordinateError(number);
76
+ return {
77
+ number,
78
+ x: annotation.x,
79
+ y: annotation.y,
80
+ note
81
+ };
82
+ });
83
+ }
84
+ const formatPercent = (value) => {
85
+ return `${Number((value * 100).toFixed(2))}%`;
86
+ };
87
+ const formatPixel = (value) => {
88
+ const rounded = Number(value.toFixed(2));
89
+ return String(rounded);
90
+ };
91
+ const withNames = (value, sourceName, referenceName) => String(value).replaceAll("{sourceName}", sourceName).replaceAll("{referenceName}", referenceName);
92
+ const positiveImageDimension = (value) => Number.isSafeInteger(value) && value > 0;
93
+ function buildImageEditDraft({ prompt = "", annotations = [], translate, width, height, sourceName = "source.png", referenceName = "annotated-reference.png" }) {
54
94
  if (typeof translate !== "function") throw new TypeError("translate must be a function");
55
- const notes = annotations.map((annotation, index) => ({
56
- number: index + 1,
57
- note: typeof annotation?.note === "string" ? annotation.note.trim() : ""
58
- })).filter((annotation) => annotation.note !== "").map((annotation) => String(annotation.number) + ". " + annotation.note);
59
- const base = prompt.trim() === "" ? translate("imageEditDefault") : prompt.trim();
60
- return notes.length === 0 ? base : base + "\n\n" + translate("imageRegionNotes") + "\n" + notes.join("\n");
95
+ const base = typeof prompt === "string" && prompt.trim() !== "" ? prompt.trim() : translate("imageEditDefault");
96
+ if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
97
+ if (annotations.length === 0) return base;
98
+ const hasWidth = width !== void 0;
99
+ if (hasWidth !== (height !== void 0) || hasWidth && (!positiveImageDimension(width) || !positiveImageDimension(height))) throw new Error("width and height must be positive integers when provided");
100
+ const normalized = normalizeImageEditAnnotations(annotations, { requireNotes: true });
101
+ const source = typeof sourceName === "string" && sourceName.trim() !== "" ? sourceName.trim() : "source.png";
102
+ const reference = typeof referenceName === "string" && referenceName.trim() !== "" ? referenceName.trim() : "annotated-reference.png";
103
+ const guide = withNames(translate("imageEditReferenceGuide"), source, reference);
104
+ const location = translate("imageEditLocation");
105
+ const notes = normalized.map(({ number, x, y, note }) => {
106
+ const pixels = hasWidth ? ` (pixel x=${formatPixel(x * Math.max(0, width - 1))} of ${width}, y=${formatPixel(y * Math.max(0, height - 1))} of ${height})` : "";
107
+ return `${number}. ${location}: x=${formatPercent(x)} (normalized ${x}), y=${formatPercent(y)} (normalized ${y})${pixels}; ${note}`;
108
+ });
109
+ return [
110
+ base,
111
+ "",
112
+ guide,
113
+ "",
114
+ translate("imageRegionNotes"),
115
+ ...notes
116
+ ].join("\n");
117
+ }
118
+ //#endregion
119
+ //#region src/image-edit-reference.js
120
+ const TWO_PI = Math.PI * 2;
121
+ const PIN_FILL = "#e11d48";
122
+ const PIN_OUTER_STROKE = "#111827";
123
+ const PIN_INNER_STROKE = "#ffffff";
124
+ const finiteDimension = (value) => Number.isSafeInteger(value) && value > 0;
125
+ const clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
126
+ const clampCenter = (value, size, edge) => {
127
+ const margin = Math.min(edge, size / 2);
128
+ return clamp$1(value, margin, size - margin);
129
+ };
130
+ function getBitmapFactory(options) {
131
+ const factory = options?.createImageBitmap ?? options?.bitmapFactory ?? globalThis.createImageBitmap;
132
+ if (typeof factory !== "function") throw new Error("createImageBitmap is not available");
133
+ return factory;
134
+ }
135
+ function getCanvasFactory(options) {
136
+ const injected = options?.createCanvas ?? options?.canvasFactory;
137
+ if (typeof injected === "function") return injected;
138
+ const document = globalThis.document;
139
+ if (document !== void 0 && typeof document.createElement === "function") return (width, height) => {
140
+ const canvas = document.createElement("canvas");
141
+ canvas.width = width;
142
+ canvas.height = height;
143
+ return canvas;
144
+ };
145
+ throw new Error("A canvas factory is not available");
146
+ }
147
+ function drawPin(context, number, x, y, width, height) {
148
+ const label = String(number);
149
+ const fontSize = Math.max(12, Math.min(40, Math.round(Math.min(width, height) * .03)));
150
+ const radius = Math.max(12, fontSize * .8, fontSize * (.3 * label.length + .35));
151
+ const outerStroke = Math.max(2, Math.min(4, radius * .25));
152
+ const innerStroke = Math.max(1, Math.min(2, radius * .13));
153
+ const edge = radius + outerStroke + 2;
154
+ const targetX = x * Math.max(0, width - 1);
155
+ const targetY = y * Math.max(0, height - 1);
156
+ const centerX = clampCenter(targetX, width, edge);
157
+ const centerY = clampCenter(targetY, height, edge);
158
+ context.save?.();
159
+ if ((centerX !== targetX || centerY !== targetY) && typeof context.moveTo === "function" && typeof context.lineTo === "function") {
160
+ context.beginPath();
161
+ context.moveTo(targetX, targetY);
162
+ context.lineTo(centerX, centerY);
163
+ context.lineWidth = outerStroke * 2;
164
+ context.strokeStyle = PIN_OUTER_STROKE;
165
+ context.stroke();
166
+ context.beginPath();
167
+ context.moveTo(targetX, targetY);
168
+ context.lineTo(centerX, centerY);
169
+ context.lineWidth = innerStroke;
170
+ context.strokeStyle = PIN_INNER_STROKE;
171
+ context.stroke();
172
+ }
173
+ context.beginPath();
174
+ context.arc(centerX, centerY, radius, 0, TWO_PI);
175
+ context.fillStyle = PIN_FILL;
176
+ context.fill();
177
+ context.lineWidth = outerStroke;
178
+ context.strokeStyle = PIN_OUTER_STROKE;
179
+ context.stroke();
180
+ context.lineWidth = innerStroke;
181
+ context.strokeStyle = PIN_INNER_STROKE;
182
+ context.stroke();
183
+ context.font = `700 ${fontSize}px sans-serif`;
184
+ context.fillStyle = PIN_INNER_STROKE;
185
+ context.textAlign = "center";
186
+ context.textBaseline = "middle";
187
+ context.fillText(label, centerX, centerY);
188
+ context.restore?.();
189
+ }
190
+ function encodePng(canvas) {
191
+ if (typeof canvas?.toBlob !== "function") throw new Error("Canvas PNG encoding is not available");
192
+ return new Promise((resolve, reject) => {
193
+ let settled = false;
194
+ const finish = (callback, value) => {
195
+ if (settled) return;
196
+ settled = true;
197
+ callback(value);
198
+ };
199
+ try {
200
+ canvas.toBlob((value) => {
201
+ if (value === null || value === void 0) finish(reject, /* @__PURE__ */ new Error("Canvas failed to encode the annotation reference as PNG"));
202
+ else finish(resolve, value);
203
+ }, "image/png");
204
+ } catch (error) {
205
+ finish(reject, error);
206
+ }
207
+ });
208
+ }
209
+ /**
210
+ * Create the second image sent with an annotated edit. It contains the clean
211
+ * source plus numbered pins, with no note text. `options` is intentionally
212
+ * injectable so the rendering path can be exercised without a browser:
213
+ * `{ createImageBitmap, createCanvas }`.
214
+ */
215
+ async function createAnnotatedImageReference(blob, annotations, options = {}) {
216
+ const normalized = normalizeImageEditAnnotations(annotations);
217
+ const createImageBitmap = getBitmapFactory(options);
218
+ const createCanvas = getCanvasFactory(options);
219
+ const bitmap = await createImageBitmap(blob);
220
+ try {
221
+ const width = bitmap?.width;
222
+ const height = bitmap?.height;
223
+ if (!finiteDimension(width) || !finiteDimension(height)) throw new Error("The source image has invalid dimensions");
224
+ if (Math.min(width, height) < 64) throw new Error("The source image is too small for readable annotation pins");
225
+ const canvas = await createCanvas(width, height);
226
+ if (canvas === null || canvas === void 0) throw new Error("Canvas factory returned no canvas");
227
+ canvas.width = width;
228
+ canvas.height = height;
229
+ const context = canvas.getContext?.("2d");
230
+ if (context === null || context === void 0) throw new Error("Canvas 2D context is not available");
231
+ context.clearRect?.(0, 0, width, height);
232
+ context.drawImage?.(bitmap, 0, 0, width, height);
233
+ if (typeof context.drawImage !== "function") throw new Error("Canvas 2D context cannot draw the source image");
234
+ for (const annotation of normalized) drawPin(context, annotation.number, annotation.x, annotation.y, width, height);
235
+ return await encodePng(canvas);
236
+ } finally {
237
+ if (typeof bitmap?.close === "function") bitmap.close();
238
+ }
61
239
  }
62
240
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
63
241
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
@@ -79,6 +257,704 @@ window.__ModuleLoader__.load({
79
257
  return original === void 0 ? void 0 : { original };
80
258
  }
81
259
  //#endregion
260
+ //#region src/subscription-image-viewer-styles.js
261
+ const SUBSCRIPTION_IMAGE_VIEWER_CSS = String.raw`
262
+ .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)}
263
+ .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}
264
+ .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)}
265
+ .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}
266
+ .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)}
267
+ .dcsiv-workspace{position:absolute;inset:0;display:grid;min-width:0;min-height:0;padding:68px 24px 72px;box-sizing:border-box}
268
+ .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}
269
+ .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}
270
+ .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)}
271
+ .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}
272
+ .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)}
273
+ .dcsiv-hint{display:none}
274
+ .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)}
275
+ @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}}
276
+ @media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}}
277
+ `;
278
+ //#endregion
279
+ //#region src/subscription-image-viewer.jsx
280
+ const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
281
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
282
+ 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`;
283
+ const downloadName = (name) => {
284
+ const cleaned = String(name || "image.png").replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
285
+ return cleaned === "" ? "image.png" : cleaned;
286
+ };
287
+ const noteText = (annotations, t) => annotations.map((annotation, index) => {
288
+ return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
289
+ }).filter((line) => !line.endsWith(": ")).join("\n");
290
+ function ViewerAction({ action, annotations, item, service, t }) {
291
+ const [state, setState] = (0, react.useState)("idle");
292
+ const invoke = async () => {
293
+ if (state === "pending") return;
294
+ setState("pending");
295
+ try {
296
+ await action.onInvoke({
297
+ annotations,
298
+ item,
299
+ src: item.src
300
+ });
301
+ setState("idle");
302
+ if (action.closeOnSuccess) service.close();
303
+ } catch {
304
+ setState("failed");
305
+ }
306
+ };
307
+ const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
308
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
309
+ type: "button",
310
+ className: "dcsiv-button",
311
+ disabled: state === "pending",
312
+ onClick: () => {
313
+ invoke();
314
+ },
315
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
316
+ className: "dcsiv-label",
317
+ children: label ?? t("imageEdit")
318
+ })
319
+ });
320
+ }
321
+ function ViewerDownload({ download, item, t }) {
322
+ const [state, setState] = (0, react.useState)("idle");
323
+ const invoke = async () => {
324
+ if (state === "pending") return;
325
+ setState("pending");
326
+ try {
327
+ await download.onInvoke({
328
+ item,
329
+ src: item.src
330
+ });
331
+ setState("idle");
332
+ } catch {
333
+ setState("failed");
334
+ }
335
+ };
336
+ const label = state === "pending" ? download.pendingLabel ?? t("imageDownloadPreparing") : state === "failed" ? download.errorLabel ?? t("imageDownloadFailed") : t("imageDownload");
337
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
338
+ type: "button",
339
+ className: "dcsiv-download",
340
+ disabled: state === "pending",
341
+ onClick: () => {
342
+ invoke();
343
+ },
344
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
345
+ className: "dcsiv-label",
346
+ children: label
347
+ })]
348
+ });
349
+ }
350
+ function SubscriptionImageViewerOverlay({ service, t }) {
351
+ const request = (0, react.useSyncExternalStore)(service.subscribe, service.getSnapshot);
352
+ const [index, setIndex] = (0, react.useState)(0);
353
+ const [transform, setTransform] = (0, react.useState)({
354
+ zoom: 1,
355
+ x: 0,
356
+ y: 0
357
+ });
358
+ const [dragging, setDragging] = (0, react.useState)(false);
359
+ const [annotating, setAnnotating] = (0, react.useState)(false);
360
+ const [annotationsByImage, setAnnotationsByImage] = (0, react.useState)(service.getAnnotationsSnapshot);
361
+ const annotationsByImageRef = (0, react.useRef)(annotationsByImage);
362
+ const [selected, setSelected] = (0, react.useState)();
363
+ const [focusNote, setFocusNote] = (0, react.useState)();
364
+ const [copied, setCopied] = (0, react.useState)(false);
365
+ const rootRef = (0, react.useRef)(null);
366
+ const stageRef = (0, react.useRef)(null);
367
+ const surfaceRef = (0, react.useRef)(null);
368
+ const imageRef = (0, react.useRef)(null);
369
+ const pointersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
370
+ const gestureRef = (0, react.useRef)();
371
+ const transformRef = (0, react.useRef)(transform);
372
+ transformRef.current = transform;
373
+ annotationsByImageRef.current = annotationsByImage;
374
+ (0, react.useEffect)(() => {
375
+ if (request === void 0) return;
376
+ setIndex(request.index);
377
+ setTransform({
378
+ zoom: 1,
379
+ x: 0,
380
+ y: 0
381
+ });
382
+ setDragging(false);
383
+ setAnnotating(false);
384
+ setSelected(void 0);
385
+ setCopied(false);
386
+ }, [request?.revision]);
387
+ const item = request?.items[index];
388
+ const annotations = item === void 0 ? [] : annotationsByImage[item.id] ?? [];
389
+ const setAnnotations = (0, react.useCallback)((update) => {
390
+ if (item === void 0) return;
391
+ const previous = annotationsByImageRef.current[item.id] ?? [];
392
+ const next = typeof update === "function" ? update(previous) : update;
393
+ const snapshot = {
394
+ ...annotationsByImageRef.current,
395
+ [item.id]: next
396
+ };
397
+ annotationsByImageRef.current = snapshot;
398
+ service.setAnnotations(item.id, next);
399
+ setAnnotationsByImage(snapshot);
400
+ }, [item?.id, service]);
401
+ const boundedPan = (0, react.useCallback)((zoom, x, y) => {
402
+ const stage = stageRef.current;
403
+ const surface = surfaceRef.current;
404
+ if (stage === null || surface === null || zoom <= 1) return {
405
+ x: 0,
406
+ y: 0
407
+ };
408
+ const limitX = Math.max(0, (surface.offsetWidth * zoom - stage.clientWidth) / 2) + 28;
409
+ const limitY = Math.max(0, (surface.offsetHeight * zoom - stage.clientHeight) / 2) + 28;
410
+ return {
411
+ x: clamp(x, -limitX, limitX),
412
+ y: clamp(y, -limitY, limitY)
413
+ };
414
+ }, []);
415
+ const setZoomAt = (0, react.useCallback)((nextZoom, clientX, clientY) => {
416
+ const stage = stageRef.current;
417
+ if (stage === null) return;
418
+ setTransform((current) => {
419
+ const next = clamp(nextZoom, .5, 8);
420
+ const box = stage.getBoundingClientRect();
421
+ const px = clientX - box.left - box.width / 2;
422
+ const py = clientY - box.top - box.height / 2;
423
+ const ratio = next / current.zoom;
424
+ return {
425
+ zoom: next,
426
+ ...boundedPan(next, px - (px - current.x) * ratio, py - (py - current.y) * ratio)
427
+ };
428
+ });
429
+ }, [boundedPan]);
430
+ const fit = (0, react.useCallback)(() => {
431
+ setTransform({
432
+ zoom: 1,
433
+ x: 0,
434
+ y: 0
435
+ });
436
+ }, []);
437
+ const actual = (0, react.useCallback)(() => {
438
+ const image = imageRef.current;
439
+ const surface = surfaceRef.current;
440
+ if (image === null || surface === null || image.naturalWidth === 0) return;
441
+ const zoom = clamp(image.naturalWidth / Math.max(1, surface.offsetWidth), 1, 8);
442
+ setTransform({
443
+ zoom,
444
+ x: 0,
445
+ y: 0
446
+ });
447
+ }, []);
448
+ (0, react.useEffect)(() => {
449
+ if (request === void 0) return void 0;
450
+ const previousOverflow = document.body.style.overflow;
451
+ document.body.style.overflow = "hidden";
452
+ rootRef.current?.focus();
453
+ const onKeyDown = (event) => {
454
+ if (event.key === "Escape") {
455
+ event.preventDefault();
456
+ if (event.target instanceof Element && event.target.closest(".dcsiv-inline-note") !== null) {
457
+ setSelected(void 0);
458
+ return;
459
+ }
460
+ service.close();
461
+ return;
462
+ }
463
+ const editing = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement;
464
+ if (!editing && event.key === "ArrowLeft" && request.items.length > 1) {
465
+ event.preventDefault();
466
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
467
+ } else if (!editing && event.key === "ArrowRight" && request.items.length > 1) {
468
+ event.preventDefault();
469
+ setIndex((value) => (value + 1) % request.items.length);
470
+ } else if (!editing && (event.key === "+" || event.key === "=")) {
471
+ event.preventDefault();
472
+ const box = stageRef.current?.getBoundingClientRect();
473
+ if (box) setZoomAt(transformRef.current.zoom * 1.2, box.left + box.width / 2, box.top + box.height / 2);
474
+ } else if (!editing && event.key === "-") {
475
+ event.preventDefault();
476
+ const box = stageRef.current?.getBoundingClientRect();
477
+ if (box) setZoomAt(transformRef.current.zoom / 1.2, box.left + box.width / 2, box.top + box.height / 2);
478
+ } else if (!editing && event.key.toLowerCase() === "f") {
479
+ event.preventDefault();
480
+ fit();
481
+ } else if (event.key === "Tab") {
482
+ const controls = [...rootRef.current.querySelectorAll("button:not(:disabled),a[href],input:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])")];
483
+ const first = controls[0];
484
+ const last = controls.at(-1);
485
+ if (event.shiftKey && document.activeElement === first) {
486
+ event.preventDefault();
487
+ last?.focus();
488
+ } else if (!event.shiftKey && document.activeElement === last) {
489
+ event.preventDefault();
490
+ first?.focus();
491
+ }
492
+ }
493
+ };
494
+ document.addEventListener("keydown", onKeyDown);
495
+ return () => {
496
+ document.body.style.overflow = previousOverflow;
497
+ document.removeEventListener("keydown", onKeyDown);
498
+ };
499
+ }, [
500
+ request,
501
+ service,
502
+ fit,
503
+ setZoomAt
504
+ ]);
505
+ (0, react.useEffect)(() => {
506
+ if (focusNote === void 0) return;
507
+ (rootRef.current?.querySelector(`[data-note-id="${CSS.escape(focusNote)}"] textarea`))?.focus();
508
+ setFocusNote(void 0);
509
+ }, [
510
+ focusNote,
511
+ selected,
512
+ annotations.length
513
+ ]);
514
+ (0, react.useEffect)(() => {
515
+ setTransform({
516
+ zoom: 1,
517
+ x: 0,
518
+ y: 0
519
+ });
520
+ setDragging(false);
521
+ setAnnotating(false);
522
+ setSelected(void 0);
523
+ }, [item?.id]);
524
+ const onWheel = (0, react.useCallback)((event) => {
525
+ event.preventDefault();
526
+ setZoomAt(transformRef.current.zoom * Math.exp(-event.deltaY * .0015), event.clientX, event.clientY);
527
+ }, [setZoomAt]);
528
+ (0, react.useEffect)(() => {
529
+ const stage = stageRef.current;
530
+ if (stage === null || request === void 0) return void 0;
531
+ stage.addEventListener("wheel", onWheel, { passive: false });
532
+ return () => stage.removeEventListener("wheel", onWheel);
533
+ }, [onWheel, request]);
534
+ const onPointerDown = (event) => {
535
+ const target = event.target;
536
+ if (event.button !== 0 || annotating || target instanceof Element && target.closest("button,textarea,input,a,select,[contenteditable=true]") !== null) return;
537
+ pointersRef.current.set(event.pointerId, {
538
+ x: event.clientX,
539
+ y: event.clientY
540
+ });
541
+ if (pointersRef.current.size === 2) {
542
+ event.currentTarget.setPointerCapture(event.pointerId);
543
+ const [a, b] = [...pointersRef.current.values()];
544
+ gestureRef.current = {
545
+ kind: "pinch",
546
+ distance: Math.hypot(a.x - b.x, a.y - b.y),
547
+ transform
548
+ };
549
+ } else if (!annotating && transform.zoom > 1) {
550
+ event.currentTarget.setPointerCapture(event.pointerId);
551
+ gestureRef.current = {
552
+ kind: "pan",
553
+ x: event.clientX,
554
+ y: event.clientY,
555
+ transform
556
+ };
557
+ setDragging(true);
558
+ }
559
+ };
560
+ const onPointerMove = (event) => {
561
+ if (!pointersRef.current.has(event.pointerId)) return;
562
+ pointersRef.current.set(event.pointerId, {
563
+ x: event.clientX,
564
+ y: event.clientY
565
+ });
566
+ const gesture = gestureRef.current;
567
+ if (gesture?.kind === "pinch" && pointersRef.current.size >= 2) {
568
+ const [a, b] = [...pointersRef.current.values()];
569
+ const distance = Math.max(1, Math.hypot(a.x - b.x, a.y - b.y));
570
+ setZoomAt(gesture.transform.zoom * distance / Math.max(1, gesture.distance), (a.x + b.x) / 2, (a.y + b.y) / 2);
571
+ } else if (gesture?.kind === "pan") {
572
+ const pan = boundedPan(gesture.transform.zoom, gesture.transform.x + event.clientX - gesture.x, gesture.transform.y + event.clientY - gesture.y);
573
+ setTransform({
574
+ zoom: gesture.transform.zoom,
575
+ ...pan
576
+ });
577
+ }
578
+ };
579
+ const endPointer = (event) => {
580
+ pointersRef.current.delete(event.pointerId);
581
+ if (pointersRef.current.size === 0) {
582
+ gestureRef.current = void 0;
583
+ setDragging(false);
584
+ }
585
+ };
586
+ const addAnnotation = (event) => {
587
+ if (!annotating || event.target.closest(".dcsiv-annotation")) return;
588
+ const bounds = surfaceRef.current?.getBoundingClientRect();
589
+ if (bounds === void 0) return;
590
+ const annotation = {
591
+ id: crypto.randomUUID(),
592
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
593
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1),
594
+ note: ""
595
+ };
596
+ setAnnotations((current) => [...current, annotation]);
597
+ setAnnotating(false);
598
+ setSelected(annotation.id);
599
+ setFocusNote(annotation.id);
600
+ };
601
+ const copyNotes = async () => {
602
+ const text = noteText(annotations, t);
603
+ if (text === "" || typeof navigator?.clipboard?.writeText !== "function") return;
604
+ try {
605
+ await navigator.clipboard.writeText(text);
606
+ setCopied(true);
607
+ window.setTimeout(() => {
608
+ setCopied(false);
609
+ }, 1200);
610
+ } catch {
611
+ setCopied(false);
612
+ }
613
+ };
614
+ if (request === void 0 || item === void 0) return null;
615
+ const meta = [item.width && item.height ? `${item.width} × ${item.height}` : void 0, bytesLabel(item.bytes)].filter(Boolean).join(" · ");
616
+ const showCounter = request.items.length > 1;
617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
618
+ ref: rootRef,
619
+ className: "dcsiv-root",
620
+ role: "dialog",
621
+ "aria-modal": "true",
622
+ "aria-label": t("imagePreview"),
623
+ tabIndex: -1,
624
+ children: [
625
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
626
+ className: "dcsiv-title dcsiv-sr-only",
627
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.name }), meta !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: meta }) : null]
628
+ }),
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
630
+ className: "dcsiv-topbar",
631
+ role: "toolbar",
632
+ "aria-label": t("imagePreview"),
633
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
634
+ className: "dcsiv-actions",
635
+ children: [
636
+ request.annotations ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
637
+ type: "button",
638
+ className: "dcsiv-button",
639
+ "data-active": annotating,
640
+ "aria-label": annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
641
+ "aria-pressed": annotating,
642
+ onClick: () => setAnnotating((value) => !value),
643
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
644
+ className: "dcsiv-label",
645
+ children: annotating ? t("imageAnnotateCancel") : t("imageAnnotate")
646
+ })]
647
+ }) : null,
648
+ annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
649
+ type: "button",
650
+ className: "dcsiv-button",
651
+ "data-active": selected !== void 0,
652
+ onClick: () => {
653
+ const first = annotations[0];
654
+ setSelected((current) => current === void 0 ? first.id : void 0);
655
+ if (selected === void 0) setFocusNote(first.id);
656
+ },
657
+ children: [
658
+ annotations.length,
659
+ " ",
660
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
661
+ className: "dcsiv-label",
662
+ children: t("imageRegions")
663
+ })
664
+ ]
665
+ }) : null,
666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
667
+ type: "button",
668
+ className: "dcsiv-button",
669
+ "aria-label": t("imageFit"),
670
+ onClick: fit,
671
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
672
+ className: "dcsiv-label",
673
+ children: t("imageFit")
674
+ })]
675
+ }),
676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
677
+ type: "button",
678
+ className: "dcsiv-button",
679
+ onClick: actual,
680
+ children: t("imageActual")
681
+ }),
682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
683
+ className: "dcsiv-zoom",
684
+ children: [Math.round(transform.zoom * 100), "%"]
685
+ }),
686
+ item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
687
+ className: "dcsiv-download",
688
+ href: item.src,
689
+ download: downloadName(item.name),
690
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
691
+ className: "dcsiv-label",
692
+ children: t("imageDownload")
693
+ })]
694
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerDownload, {
695
+ download: item.download,
696
+ item,
697
+ t
698
+ }),
699
+ item.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerAction, {
700
+ action,
701
+ annotations,
702
+ item,
703
+ service,
704
+ t
705
+ }, action.id))
706
+ ]
707
+ })
708
+ }),
709
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
710
+ type: "button",
711
+ className: "dcsiv-close-floating",
712
+ "aria-label": t("imageClosePreview"),
713
+ onClick: () => service.close(),
714
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
715
+ }),
716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
717
+ className: "dcsiv-workspace",
718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
719
+ ref: stageRef,
720
+ className: "dcsiv-stage",
721
+ "data-dragging": dragging,
722
+ "data-annotating": annotating,
723
+ onClick: (event) => {
724
+ if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
725
+ },
726
+ onPointerDown,
727
+ onPointerMove,
728
+ onPointerUp: endPointer,
729
+ onPointerCancel: endPointer,
730
+ onDoubleClick: () => {
731
+ if (transform.zoom === 1) actual();
732
+ else fit();
733
+ },
734
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
735
+ ref: surfaceRef,
736
+ className: "dcsiv-surface",
737
+ onClick: addAnnotation,
738
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
739
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
740
+ ref: imageRef,
741
+ className: "dcsiv-image",
742
+ src: item.src,
743
+ alt: item.name,
744
+ draggable: "false"
745
+ }), annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
+ className: "dcsiv-annotation",
747
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
748
+ "data-y": annotation.y < .28 ? "down" : "up",
749
+ style: {
750
+ left: `${annotation.x * 100}%`,
751
+ top: `${annotation.y * 100}%`,
752
+ transform: `translate(-50%,-50%) scale(${1 / transform.zoom})`
753
+ },
754
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
755
+ type: "button",
756
+ className: "dcsiv-pin",
757
+ "data-active": selected === annotation.id,
758
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
759
+ onClick: (event) => {
760
+ event.stopPropagation();
761
+ const opening = selected !== annotation.id;
762
+ setSelected(opening ? annotation.id : void 0);
763
+ if (opening) setFocusNote(annotation.id);
764
+ },
765
+ children: position + 1
766
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
767
+ className: "dcsiv-inline-note",
768
+ "data-note-id": annotation.id,
769
+ onClick: (event) => event.stopPropagation(),
770
+ children: [
771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
772
+ className: "dcsiv-inline-index",
773
+ children: position + 1
774
+ }),
775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
776
+ value: annotation.note,
777
+ rows: 1,
778
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
779
+ placeholder: t("imageAnnotationPlaceholder"),
780
+ onChange: (event) => {
781
+ const note = event.target.value;
782
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
783
+ ...entry,
784
+ note
785
+ } : entry));
786
+ },
787
+ onKeyDown: (event) => {
788
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
789
+ event.preventDefault();
790
+ event.stopPropagation();
791
+ event.nativeEvent?.stopImmediatePropagation?.();
792
+ setSelected(void 0);
793
+ }
794
+ }
795
+ }),
796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
797
+ type: "button",
798
+ className: "dcsiv-note-remove",
799
+ "aria-label": t("imageRemoveAnnotation"),
800
+ onClick: (event) => {
801
+ event.stopPropagation();
802
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
803
+ setSelected(void 0);
804
+ },
805
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
806
+ })
807
+ ]
808
+ }) : null]
809
+ }, annotation.id))]
810
+ }), showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
812
+ type: "button",
813
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
814
+ "aria-label": t("imagePrevious"),
815
+ onClick: (event) => {
816
+ event.stopPropagation();
817
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
818
+ },
819
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
820
+ }),
821
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
822
+ type: "button",
823
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
824
+ "aria-label": t("imageNext"),
825
+ onClick: (event) => {
826
+ event.stopPropagation();
827
+ setIndex((value) => (value + 1) % request.items.length);
828
+ },
829
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
830
+ }),
831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
832
+ className: "dcsiv-counter",
833
+ children: [
834
+ index + 1,
835
+ " / ",
836
+ request.items.length
837
+ ]
838
+ })
839
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
840
+ className: "dcsiv-hint",
841
+ children: t("imageAnnotateHint")
842
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
843
+ className: "dcsiv-hint",
844
+ children: t("imageZoomHint")
845
+ }) : null]
846
+ }), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
847
+ type: "button",
848
+ className: "dcsiv-copy-notes",
849
+ onClick: () => {
850
+ copyNotes();
851
+ },
852
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
853
+ }) : null]
854
+ })
855
+ ]
856
+ });
857
+ }
858
+ //#endregion
859
+ //#region src/subscription-image-viewer.js
860
+ const boundedNumber = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
861
+ const downloadOf = (value) => typeof value?.onInvoke === "function" ? {
862
+ pendingLabel: typeof value.pendingLabel === "string" && value.pendingLabel !== "" ? value.pendingLabel : void 0,
863
+ errorLabel: typeof value.errorLabel === "string" && value.errorLabel !== "" ? value.errorLabel : void 0,
864
+ onInvoke: value.onInvoke
865
+ } : void 0;
866
+ const actionsOf = (value) => Array.isArray(value) ? value.flatMap((action, position) => {
867
+ if (typeof action?.onInvoke !== "function" || typeof action?.label !== "string" || action.label.trim() === "") return [];
868
+ return [{
869
+ id: typeof action.id === "string" && action.id !== "" ? action.id : `action-${position + 1}`,
870
+ label: action.label,
871
+ pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
872
+ errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
873
+ closeOnSuccess: action.closeOnSuccess === true,
874
+ onInvoke: action.onInvoke
875
+ }];
876
+ }) : [];
877
+ function normalizeSubscriptionViewerRequest(request) {
878
+ const items = (Array.isArray(request?.items) ? request.items : []).flatMap((item, position) => {
879
+ if (typeof item?.src !== "string" || item.src === "") return [];
880
+ return [{
881
+ id: typeof item.id === "string" && item.id !== "" ? item.id : `image-${position + 1}`,
882
+ src: item.src,
883
+ name: typeof item.name === "string" && item.name !== "" ? item.name : `Image ${position + 1}`,
884
+ width: boundedNumber(item.width, void 0),
885
+ height: boundedNumber(item.height, void 0),
886
+ bytes: boundedNumber(item.bytes, void 0),
887
+ download: downloadOf(item.download),
888
+ actions: actionsOf(item.actions)
889
+ }];
890
+ });
891
+ if (items.length === 0) return void 0;
892
+ const requestedIndex = Number.isInteger(request?.index) ? request.index : 0;
893
+ return {
894
+ items,
895
+ index: Math.max(0, Math.min(items.length - 1, requestedIndex)),
896
+ opener: typeof HTMLElement !== "undefined" && request?.opener instanceof HTMLElement ? request.opener : void 0,
897
+ source: typeof request?.source === "string" ? request.source : "dsh-codex-subscription",
898
+ annotations: request?.annotations !== false
899
+ };
900
+ }
901
+ const copyAnnotations = (annotations) => annotations.map((annotation) => ({ ...annotation }));
902
+ /**
903
+ * Local image viewer state for subscription-generated images.
904
+ *
905
+ * This stays private to subscription image cards, which need annotation and
906
+ * edit actions that a host's generic native viewer may not implement.
907
+ */
908
+ var SubscriptionImageViewerService = class {
909
+ #listeners = /* @__PURE__ */ new Set();
910
+ #revision = 0;
911
+ #snapshot;
912
+ #annotationsByImage = /* @__PURE__ */ new Map();
913
+ constructor() {
914
+ this.subscribe = (listener) => {
915
+ this.#listeners.add(listener);
916
+ return () => {
917
+ this.#listeners.delete(listener);
918
+ };
919
+ };
920
+ this.getSnapshot = () => this.#snapshot;
921
+ this.getAnnotationsSnapshot = () => Object.fromEntries([...this.#annotationsByImage].map(([id, annotations]) => [id, copyAnnotations(annotations)]));
922
+ }
923
+ setAnnotations(imageId, annotations) {
924
+ if (typeof imageId !== "string" || imageId === "" || !Array.isArray(annotations)) return;
925
+ if (annotations.length === 0) this.#annotationsByImage.delete(imageId);
926
+ else this.#annotationsByImage.set(imageId, copyAnnotations(annotations));
927
+ }
928
+ open(request) {
929
+ const normalized = normalizeSubscriptionViewerRequest(request);
930
+ if (normalized === void 0) return false;
931
+ this.#revision += 1;
932
+ this.#snapshot = {
933
+ ...normalized,
934
+ revision: this.#revision
935
+ };
936
+ this.#emit();
937
+ return true;
938
+ }
939
+ close() {
940
+ if (this.#snapshot === void 0) return;
941
+ const opener = this.#snapshot.opener;
942
+ this.#snapshot = void 0;
943
+ this.#emit();
944
+ if (typeof window === "undefined") opener?.focus();
945
+ else {
946
+ const focus = () => {
947
+ opener?.focus();
948
+ };
949
+ if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(focus);
950
+ else focus();
951
+ }
952
+ }
953
+ #emit() {
954
+ for (const listener of this.#listeners) listener();
955
+ }
956
+ };
957
+ //#endregion
82
958
  //#region src/settings-contract.js
83
959
  const SETTINGS_NAMESPACE = "codex-subscription";
84
960
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
@@ -167,16 +1043,13 @@ window.__ModuleLoader__.load({
167
1043
  if (/\bspark\b/u.test(normalized(model))) return /\bspark\b/u.test(normalized(`${limit?.id ?? ""} ${limit?.name ?? ""}`));
168
1044
  return limit?.id === "codex";
169
1045
  };
170
- function selectModelQuota(usage, model) {
171
- const windows = Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : [];
172
- if (windows.length === 0) return void 0;
173
- const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
174
- return {
1046
+ function selectModelQuotaWindows(usage, model) {
1047
+ return (Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : []).map((selected) => ({
175
1048
  remainingPercent: selected.remainingPercent,
176
1049
  windowSeconds: selected.windowSeconds,
177
1050
  ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
178
1051
  ...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
179
- };
1052
+ })).sort((a, b) => a.windowSeconds - b.windowSeconds);
180
1053
  }
181
1054
  //#endregion
182
1055
  //#region src/login-progress.js
@@ -371,6 +1244,7 @@ window.__ModuleLoader__.load({
371
1244
  "settingsScope",
372
1245
  "modelDirectories",
373
1246
  "conversation",
1247
+ "uiConversation",
374
1248
  "sessions"
375
1249
  ];
376
1250
  const NS = "settings.codexSubscription";
@@ -379,6 +1253,8 @@ window.__ModuleLoader__.load({
379
1253
  const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
380
1254
  const QUICK_QUOTA_REFRESH_MS = 6e4;
381
1255
  const zh = {
1256
+ imageEditLocation: "位置",
1257
+ imageEditReferenceGuide: "本次编辑的干净源图为「{sourceName}」,编号定位参考图为「{referenceName}」。坐标以图片左上角为原点,x 向右、y 向下,百分比相对于整张图片。请查看这两张图片,将它们同时作为编辑工具的参考图,并在工具提示词中完整保留下方编号、位置和修改要求。只修改源图中对应位置的内容;定位参考图上的编号、圆点和引线仅用于定位,不得绘入最终结果。若无法读取两张图片或确定位置,请说明问题,不要猜测或忽略标注。",
382
1258
  nav: "Codex 订阅",
383
1259
  title: "Codex 订阅",
384
1260
  connected: "已登录",
@@ -552,18 +1428,28 @@ window.__ModuleLoader__.load({
552
1428
  imageZoomIn: "放大",
553
1429
  imageFit: "适合窗口",
554
1430
  imageAnnotate: "标注部位",
1431
+ imageAnnotateCancel: "取消标注",
555
1432
  imageAnnotateHint: "点击图片添加编号标注",
556
1433
  imageAnnotation: "标注 {value}",
557
1434
  imageAnnotationPlaceholder: "描述这个部位要修改什么",
1435
+ imageRegions: "区域备注",
1436
+ imageCopyNotes: "复制备注",
1437
+ imageCopied: "已复制",
1438
+ imagePrevious: "上一张图片",
1439
+ imageNext: "下一张图片",
1440
+ imageZoomHint: "滚轮缩放 · 拖动查看 · 双击切换原始大小",
1441
+ imageActual: "原始大小",
558
1442
  imageEditPrompt: "描述你想怎样修改这张图",
559
1443
  imageEditDefault: "编辑这张图片。",
560
1444
  imageRegionNotes: "部位修改:",
561
1445
  imageEdit: "在输入框中继续编辑",
562
1446
  imageEditPreparing: "正在添加到输入框…",
563
- imageEditFailed: "无法把图片添加到输入框。",
1447
+ imageEditFailed: "回填失败:请填写每个标记的备注,并确认输入框可接收图片后重试。",
564
1448
  imageRemoveAnnotation: "删除标注"
565
1449
  };
566
1450
  const en = {
1451
+ imageEditLocation: "Location",
1452
+ imageEditReferenceGuide: "The clean source for this edit is \"{sourceName}\"; \"{referenceName}\" is the numbered location reference. Coordinates start at the top-left, x increases rightward and y downward; percentages refer to the whole image. Inspect both images and pass both as references to the image-editing tool. Preserve every number, position and requested change below in the tool prompt. Edit the corresponding content in the clean source; numbers, dots and leader lines on the location reference are guidance only and must not appear in the final result. If either image cannot be read or a location is unclear, explain the problem instead of guessing or ignoring annotations.",
567
1453
  nav: "Codex",
568
1454
  title: "Codex subscription",
569
1455
  connected: "Signed in",
@@ -737,15 +1623,23 @@ window.__ModuleLoader__.load({
737
1623
  imageZoomIn: "Zoom in",
738
1624
  imageFit: "Fit to window",
739
1625
  imageAnnotate: "Annotate",
1626
+ imageAnnotateCancel: "Cancel marking",
740
1627
  imageAnnotateHint: "Click the image to add a numbered note",
741
1628
  imageAnnotation: "Note {value}",
742
1629
  imageAnnotationPlaceholder: "Describe what should change in this area",
1630
+ imageRegions: "Region notes",
1631
+ imageCopyNotes: "Copy notes",
1632
+ imageCopied: "Copied",
1633
+ imagePrevious: "Previous image",
1634
+ imageNext: "Next image",
1635
+ imageZoomHint: "Wheel to zoom · drag to pan · double-click for 100%",
1636
+ imageActual: "100%",
743
1637
  imageEditPrompt: "Describe how you want to change this image",
744
1638
  imageEditDefault: "Edit this image.",
745
1639
  imageRegionNotes: "Region changes:",
746
1640
  imageEdit: "Continue editing in composer",
747
1641
  imageEditPreparing: "Adding to composer…",
748
- imageEditFailed: "Could not add the image to the composer.",
1642
+ imageEditFailed: "Handoff failed. Add a note to every marker and ensure the composer accepts images, then retry.",
749
1643
  imageRemoveAnnotation: "Remove note"
750
1644
  };
751
1645
  const STYLE = `
@@ -782,7 +1676,7 @@ window.__ModuleLoader__.load({
782
1676
  .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)}
783
1677
  .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}
784
1678
  .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}
785
- .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}
1679
+ .codexComposerQuotaWindows{display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap}.codexComposerQuota{display:inline-flex;align-items:center;gap:5px;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}
786
1680
  .codexModelSelect{position:relative;min-width:0}.codexModelSelectTrigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:min(360px,45cqw);height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}.codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}.codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}.codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
787
1681
  .codexModelSelectMenu,.codexModelSelectSubmenu{position:absolute;z-index:30;box-sizing:border-box;width:max-content;min-width:min(240px,calc(100vw - 32px));max-width:min(420px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);overflow:hidden}.codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}.codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}.codexModelSelectCell{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;height:40px;box-sizing:border-box;padding:0 10px;border:0;border-radius:10px;background:transparent;color:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}.codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}.codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectCellLabel{flex:none;white-space:nowrap}.codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}.codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}.codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}.codexModelSelectGroupTitle{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.codexModelSelectOption{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;min-height:38px;box-sizing:border-box;padding:6px 8px;border:0;border-radius:10px;outline:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}.codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}.codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexModelSelectError,.codexModelSelectWarning{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
788
1682
  .codexModelSelectMenu{overflow:visible}
@@ -792,19 +1686,6 @@ window.__ModuleLoader__.load({
792
1686
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
793
1687
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
794
1688
  @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
1689
  `;
809
1690
  const unwrap = (response) => {
810
1691
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
@@ -839,7 +1720,6 @@ window.__ModuleLoader__.load({
839
1720
  if (cleaned === "") return fallback;
840
1721
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
841
1722
  };
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
1723
  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
1724
  async function sha256Hex(data) {
845
1725
  const value = await crypto.subtle.digest("SHA-256", data);
@@ -899,25 +1779,16 @@ window.__ModuleLoader__.load({
899
1779
  URL.revokeObjectURL(url);
900
1780
  }
901
1781
  }
902
- function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, t }) {
1782
+ function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
903
1783
  const [attempt, setAttempt] = (0, react.useState)(0);
904
1784
  const [error, setError] = (0, react.useState)(false);
905
- const [open, setOpen] = (0, react.useState)(false);
906
1785
  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
1786
  const triggerRef = (0, react.useRef)(null);
915
- const dialogRef = (0, react.useRef)(null);
916
1787
  (0, react.useEffect)(() => {
917
1788
  let live = true;
918
1789
  setError(false);
919
1790
  setSrc(void 0);
920
- loadImage(attachment).then((value) => {
1791
+ Promise.resolve().then(() => loadImage(attachment)).then((value) => {
921
1792
  if (live) setSrc(value);
922
1793
  }).catch(() => {
923
1794
  if (live) setError(true);
@@ -930,100 +1801,15 @@ window.__ModuleLoader__.load({
930
1801
  loadImage,
931
1802
  attempt
932
1803
  ]);
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
1804
  const label = attachment.name ?? t("imageLabel");
969
1805
  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
1806
  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
- }
1807
+ if (original === void 0) return;
1808
+ triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
1010
1809
  };
1011
1810
  const openImage = () => {
1012
1811
  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?.({
1812
+ const request = {
1027
1813
  items: [{
1028
1814
  id: attachment.attachmentId ?? downloadName,
1029
1815
  src,
@@ -1036,14 +1822,33 @@ window.__ModuleLoader__.load({
1036
1822
  errorLabel: t("imageDownloadFailed"),
1037
1823
  onInvoke: downloadOriginal
1038
1824
  },
1039
- actions
1825
+ actions: [{
1826
+ id: "continue-editing",
1827
+ label: t("imageEdit"),
1828
+ pendingLabel: t("imageEditPreparing"),
1829
+ errorLabel: t("imageEditFailed"),
1830
+ closeOnSuccess: true,
1831
+ onInvoke: ({ annotations = [] }) => {
1832
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1833
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1834
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
1835
+ return attachForEdit(src, sourceName, buildImageEditDraft({
1836
+ annotations,
1837
+ translate: t,
1838
+ width: attachment.width,
1839
+ height: attachment.height,
1840
+ sourceName,
1841
+ referenceName
1842
+ }), annotations, referenceName);
1843
+ }
1844
+ }]
1040
1845
  }],
1041
1846
  opener: triggerRef.current,
1042
1847
  source: "codex-generated",
1043
1848
  annotations: true
1044
- }) === true) return;
1045
- setZoom(1);
1046
- setOpen(true);
1849
+ };
1850
+ if (getInternalImageViewer?.()?.open?.(request) === true) return;
1851
+ (getImageViewer?.())?.open?.(request);
1047
1852
  };
1048
1853
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1049
1854
  type: "button",
@@ -1051,151 +1856,7 @@ window.__ModuleLoader__.load({
1051
1856
  onClick: () => setAttempt((value) => value + 1),
1052
1857
  children: t("imageLoadFailed")
1053
1858
  });
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", {
1859
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1199
1860
  ref: triggerRef,
1200
1861
  type: "button",
1201
1862
  className: "codexGeneratedImageFrame",
@@ -1206,9 +1867,9 @@ window.__ModuleLoader__.load({
1206
1867
  src,
1207
1868
  alt: label
1208
1869
  })
1209
- }), lightbox] });
1870
+ });
1210
1871
  }
1211
- function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, t }) {
1872
+ function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
1212
1873
  const settled = block?.kind === "tool-result";
1213
1874
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
1214
1875
  const failed = settled && block.isError === true;
@@ -1251,6 +1912,7 @@ window.__ModuleLoader__.load({
1251
1912
  loadImage,
1252
1913
  attachForEdit,
1253
1914
  getImageViewer,
1915
+ getInternalImageViewer,
1254
1916
  t
1255
1917
  })
1256
1918
  }),
@@ -1306,7 +1968,7 @@ window.__ModuleLoader__.load({
1306
1968
  return;
1307
1969
  }
1308
1970
  const usage = unwrap(await rpc.call(CHANNEL, "usage", { force: false }));
1309
- if (live) setQuota(selectModelQuota(usage, model));
1971
+ if (live) setQuota(selectModelQuotaWindows(usage, model));
1310
1972
  } catch {
1311
1973
  if (live) setQuota(void 0);
1312
1974
  } finally {
@@ -1579,8 +2241,19 @@ window.__ModuleLoader__.load({
1579
2241
  const codex = current?.provider === "openai-codex";
1580
2242
  const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
1581
2243
  const forecastMode = preferenceSnapshot.quickQuotaMode === QUICK_QUOTA_MODE_FORECAST;
1582
- const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
1583
- if (!quotaEnabled || quota === void 0) return null;
2244
+ const quotas = useQuickQuota(rpc, quotaEnabled, current?.model);
2245
+ if (!quotaEnabled || quotas === void 0 || quotas.length === 0) return null;
2246
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2247
+ className: "codexComposerQuotaWindows",
2248
+ children: quotas.map((quota, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexComposerQuotaWindow, {
2249
+ quota,
2250
+ mode: preferenceSnapshot.quickQuotaMode,
2251
+ forecastMode,
2252
+ t
2253
+ }, `${quota.windowSeconds}-${index}`))
2254
+ });
2255
+ }
2256
+ function CodexComposerQuotaWindow({ quota, mode, forecastMode, t }) {
1584
2257
  const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
1585
2258
  const display = percent(value);
1586
2259
  const forecast = forecastMode ? quota.forecast : void 0;
@@ -1589,17 +2262,20 @@ window.__ModuleLoader__.load({
1589
2262
  value: display,
1590
2263
  duration
1591
2264
  });
1592
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2265
+ const content = mode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
2266
+ className: "codexComposerQuotaBar",
2267
+ max: 100,
2268
+ value,
2269
+ "aria-hidden": "true"
2270
+ }) : forecast?.status === "calibrating" ? `${display}% · ${t("quickQuotaForecastCalibrating")}` : forecast?.status === "idle" ? `${display}% · ${t("quickQuotaForecastIdle")}` : forecast?.status === "ready" && forecast.survivesReset ? `${display}% · ${t("quickQuotaForecastUntilReset")}` : forecastMode && duration !== void 0 ? `${display}% · ≈${duration}` : `${display}%`;
2271
+ const durationLabel = windowLabel(quota.windowSeconds, t);
2272
+ const accessibleLabel = `${durationLabel}: ${label}`;
2273
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1593
2274
  className: "codexComposerQuota",
1594
2275
  role: "status",
1595
- "aria-label": label,
1596
- title: label,
1597
- children: preferenceSnapshot.quickQuotaMode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
1598
- className: "codexComposerQuotaBar",
1599
- max: 100,
1600
- value,
1601
- "aria-hidden": "true"
1602
- }) : forecast?.status === "calibrating" ? `${display}% · ${t("quickQuotaForecastCalibrating")}` : forecast?.status === "idle" ? `${display}% · ${t("quickQuotaForecastIdle")}` : forecast?.status === "ready" && forecast.survivesReset ? `${display}% · ${t("quickQuotaForecastUntilReset")}` : forecastMode && duration !== void 0 ? `${display}% · ≈${duration}` : `${display}%`
2276
+ "aria-label": accessibleLabel,
2277
+ title: accessibleLabel,
2278
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: durationLabel }), content]
1603
2279
  });
1604
2280
  }
1605
2281
  function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
@@ -2783,6 +3459,7 @@ window.__ModuleLoader__.load({
2783
3459
  });
2784
3460
  }
2785
3461
  function apply(ctx) {
3462
+ const imageViewer = new SubscriptionImageViewerService();
2786
3463
  ctx.effect(() => ctx.locale.register(NS, {
2787
3464
  zh,
2788
3465
  en
@@ -2790,7 +3467,7 @@ window.__ModuleLoader__.load({
2790
3467
  ctx.effect(() => {
2791
3468
  const tag = document.createElement("style");
2792
3469
  tag.dataset.plugin = "dsh-codex-subscription";
2793
- tag.textContent = STYLE + IMAGE_STYLE + IMAGE_LAYOUT_STYLE;
3470
+ tag.textContent = STYLE + SUBSCRIPTION_IMAGE_VIEWER_CSS;
2794
3471
  document.head.append(tag);
2795
3472
  return () => tag.remove();
2796
3473
  }, "codex-subscription: style");
@@ -2807,6 +3484,15 @@ window.__ModuleLoader__.load({
2807
3484
  };
2808
3485
  }, "codex-subscription: preferences");
2809
3486
  const t = ctx.locale.bind(NS);
3487
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3488
+ name: "shell.overlay",
3489
+ id: "codex-subscription-image-viewer",
3490
+ order: 20,
3491
+ inject: () => ({
3492
+ service: imageViewer,
3493
+ t
3494
+ })
3495
+ }, SubscriptionImageViewerOverlay));
2810
3496
  ctx.slots.inject("settings.section", () => ctx.slots.register({
2811
3497
  name: "settings.section",
2812
3498
  id: "codex-subscription",
@@ -2856,6 +3542,7 @@ window.__ModuleLoader__.load({
2856
3542
  if (ctx.get("remote.session") === void 0) installDirectorySlots(ctx);
2857
3543
  else ctx.inject(["remote.session"], installDirectorySlots);
2858
3544
  const conversation = ctx.get("conversation");
3545
+ const uiConversation = ctx.get("uiConversation");
2859
3546
  ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
2860
3547
  name: "tool.call.toolview",
2861
3548
  key: "codex_image_generate",
@@ -2864,7 +3551,7 @@ window.__ModuleLoader__.load({
2864
3551
  sessionId,
2865
3552
  rpc: connection.rpc,
2866
3553
  t,
2867
- loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
3554
+ loadImage: (attachment) => uiConversation.imageUrl(sessionId, attachment),
2868
3555
  getImageViewer: () => {
2869
3556
  try {
2870
3557
  return ctx.get("nativeImageViewer");
@@ -2872,13 +3559,19 @@ window.__ModuleLoader__.load({
2872
3559
  return;
2873
3560
  }
2874
3561
  },
2875
- attachForEdit: async (src, filename, draft) => {
3562
+ getInternalImageViewer: () => imageViewer,
3563
+ attachForEdit: async (src, filename, draft, annotations = [], referenceName) => {
2876
3564
  const actx = sessions.scope(sessionId);
2877
3565
  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");
2878
3566
  const response = await fetch(src);
2879
3567
  if (!response.ok) throw new Error("Could not read generated image");
2880
3568
  const blob = await response.blob();
2881
- const created = conversation.createDraftImages([new File([blob], filename, { type: blob.type || "image/png" })]);
3569
+ const files = [new File([blob], filename, { type: blob.type || "image/png" })];
3570
+ if (annotations.length > 0) {
3571
+ const reference = await createAnnotatedImageReference(blob, annotations);
3572
+ files.push(new File([reference], referenceName, { type: "image/png" }));
3573
+ }
3574
+ const created = conversation.createDraftImages(files);
2882
3575
  const input = conversation.input.for(actx);
2883
3576
  if (!input.addImages(created.map((item) => item.id))) {
2884
3577
  conversation.releaseDraftImages(created);