dsh-codex-subscription 1.14.3 → 1.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/client.js +2147 -2004
  2. package/lib/index.js +93 -24
  3. package/package.json +3 -3
package/lib/client.js CHANGED
@@ -28,26 +28,8 @@ window.__ModuleLoader__.load({
28
28
  //#endregion
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
32
31
  let react_jsx_runtime = require("react/jsx-runtime");
33
- //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/16/solid/esm/BoltIcon.js
34
- function BoltIcon({ title, titleId, ...props }, svgRef) {
35
- return /*#__PURE__*/ react.createElement("svg", Object.assign({
36
- xmlns: "http://www.w3.org/2000/svg",
37
- viewBox: "0 0 16 16",
38
- fill: "currentColor",
39
- "aria-hidden": "true",
40
- "data-slot": "icon",
41
- ref: svgRef,
42
- "aria-labelledby": titleId
43
- }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
44
- fillRule: "evenodd",
45
- d: "M9.58 1.077a.75.75 0 0 1 .405.82L9.165 6h4.085a.75.75 0 0 1 .567 1.241l-6.5 7.5a.75.75 0 0 1-1.302-.638L6.835 10H2.75a.75.75 0 0 1-.567-1.241l6.5-7.5a.75.75 0 0 1 .897-.182Z",
46
- clipRule: "evenodd"
47
- }));
48
- }
49
- const ForwardRef = /*#__PURE__*/ react.forwardRef(BoltIcon);
50
- //#endregion
32
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
51
33
  //#region src/image-edit.js
52
34
  const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
53
35
  const validCoordinate = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
@@ -115,128 +97,6 @@ window.__ModuleLoader__.load({
115
97
  ...notes
116
98
  ].join("\n");
117
99
  }
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
- }
239
- }
240
100
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
241
101
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
242
102
  function decodeOriginalImageRef(value) {
@@ -256,1700 +116,2193 @@ window.__ModuleLoader__.load({
256
116
  const original = decodeOriginalImageRef(value.original);
257
117
  return original === void 0 ? void 0 : { original };
258
118
  }
119
+ function originalImageRefsEqual(left, right) {
120
+ const a = decodeOriginalImageRef(left);
121
+ const b = decodeOriginalImageRef(right);
122
+ return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
123
+ }
259
124
  //#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
- `;
125
+ //#region src/original-image-download.js
126
+ function decodeBase64Chunk(value) {
127
+ if (typeof value !== "string" || value.length === 0 || value.length > Math.ceil(4194304 / 3) * 4 + 8) throw new Error("Invalid original image chunk");
128
+ let decoded;
129
+ try {
130
+ decoded = atob(value);
131
+ } catch {
132
+ throw new Error("Invalid original image chunk");
133
+ }
134
+ const bytes = new Uint8Array(decoded.length);
135
+ for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index);
136
+ return bytes;
137
+ }
138
+ /** Keep only the destination and current chunk, while verifying every reply. */
139
+ async function readOriginalImage(rpc, sessionId, original) {
140
+ original = decodeOriginalImageRef(original);
141
+ if (original === void 0) throw new Error("Invalid original image reference");
142
+ const data = new Uint8Array(original.bytes);
143
+ let total = 0;
144
+ let done = false;
145
+ while (!done) {
146
+ const response = await rpc.call("/codex-subscription", "image/original/chunk", {
147
+ sessionId,
148
+ assetId: original.assetId,
149
+ offset: total
150
+ });
151
+ if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
152
+ const chunk = response.value;
153
+ if (!originalImageRefsEqual(chunk?.ref, original) || chunk.offset !== total || typeof chunk.done !== "boolean") throw new Error("Original image metadata changed");
154
+ const bytes = decodeBase64Chunk(chunk.encoded);
155
+ if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
156
+ data.set(bytes, total);
157
+ total += bytes.byteLength;
158
+ done = chunk.done;
159
+ }
160
+ if (total !== original.bytes) throw new Error("Original image download is incomplete");
161
+ const digest = await crypto.subtle.digest("SHA-256", data);
162
+ if ([...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("") !== original.sha256) throw new Error("Original image integrity check failed");
163
+ return data;
164
+ }
278
165
  //#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;
166
+ //#region src/client-images.jsx
167
+ const imageDownloadName = (attachment) => {
168
+ const fallback = "codex-generated-image.png";
169
+ if (typeof attachment?.name !== "string") return fallback;
170
+ const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
171
+ if (cleaned === "") return fallback;
172
+ return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
286
173
  };
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
- });
174
+ function triggerBlobDownload(data, mediaType, filename) {
175
+ const url = URL.createObjectURL(new Blob([data], { type: mediaType }));
176
+ const anchor = document.createElement("a");
177
+ anchor.href = url;
178
+ anchor.download = filename;
179
+ anchor.rel = "noopener";
180
+ document.body.append(anchor);
181
+ try {
182
+ anchor.click();
183
+ } finally {
184
+ anchor.remove();
185
+ URL.revokeObjectURL(url);
186
+ }
320
187
  }
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;
188
+ function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
189
+ const [attempt, setAttempt] = (0, react.useState)(0);
190
+ const [error, setError] = (0, react.useState)(false);
191
+ const [src, setSrc] = (0, react.useState)();
192
+ const triggerRef = (0, react.useRef)(null);
374
193
  (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
194
+ let live = true;
195
+ setError(false);
196
+ setSrc(void 0);
197
+ Promise.resolve().then(() => loadImage(attachment)).then((value) => {
198
+ if (live) setSrc(value);
199
+ }).catch(() => {
200
+ if (live) setError(true);
446
201
  });
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
202
  return () => {
496
- document.body.style.overflow = previousOverflow;
497
- document.removeEventListener("keydown", onKeyDown);
203
+ live = false;
498
204
  };
499
205
  }, [
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
206
+ attachment,
207
+ loadImage,
208
+ attempt
513
209
  ]);
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
- }
210
+ const label = attachment.name ?? t("imageLabel");
211
+ const downloadName = imageDownloadName(attachment);
212
+ const downloadOriginal = async () => {
213
+ if (original === void 0) return;
214
+ triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
559
215
  };
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: ""
216
+ const openImage = () => {
217
+ if (src === void 0) return;
218
+ const request = {
219
+ items: [{
220
+ id: attachment.attachmentId ?? downloadName,
221
+ src,
222
+ name: label,
223
+ width: attachment.width,
224
+ height: attachment.height,
225
+ bytes: attachment.bytes,
226
+ download: original === void 0 ? void 0 : {
227
+ pendingLabel: t("imageDownloadPreparing"),
228
+ errorLabel: t("imageDownloadFailed"),
229
+ onInvoke: downloadOriginal
230
+ },
231
+ actions: [{
232
+ id: "continue-editing",
233
+ label: t("imageEdit"),
234
+ pendingLabel: t("imageEditPreparing"),
235
+ errorLabel: t("imageEditFailed"),
236
+ closeOnSuccess: true,
237
+ onInvoke: ({ annotations = [] }) => {
238
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
239
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
240
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
241
+ return attachForEdit(src, sourceName, buildImageEditDraft({
242
+ annotations,
243
+ translate: t,
244
+ width: attachment.width,
245
+ height: attachment.height,
246
+ sourceName,
247
+ referenceName
248
+ }), annotations, referenceName);
249
+ }
250
+ }]
251
+ }],
252
+ opener: triggerRef.current,
253
+ source: "codex-generated",
254
+ annotations: true
595
255
  };
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
- }
256
+ if (getInternalImageViewer?.()?.open?.(request) === true) return;
257
+ (getImageViewer?.())?.open?.(request);
613
258
  };
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;
259
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
260
+ type: "button",
261
+ className: "codexGeneratedImageRetry",
262
+ onClick: () => setAttempt((value) => value + 1),
263
+ children: t("imageLoadFailed")
264
+ });
265
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
266
+ ref: triggerRef,
267
+ type: "button",
268
+ className: "codexGeneratedImageFrame",
269
+ title: t("imageOpen"),
270
+ "aria-label": t("imageOpenNamed").replace("{value}", String(label)),
271
+ onClick: openImage,
272
+ children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
273
+ src,
274
+ alt: label
275
+ })
276
+ });
277
+ }
278
+ function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
279
+ const settled = block?.kind === "tool-result";
280
+ const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
281
+ const failed = settled && block.isError === true;
282
+ const state = !settled ? "running" : failed ? "error" : "done";
283
+ const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
284
+ const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
285
+ const original = decodeImagePresentation(block?.meta)?.original;
617
286
  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,
287
+ className: "codexImageTool",
288
+ "data-state": state,
624
289
  children: [
625
290
  /* @__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]
291
+ className: "codexImageToolRow",
292
+ children: [
293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
294
+ className: "codexImageToolIcon",
295
+ "aria-hidden": "true"
296
+ }),
297
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
298
+ className: "codexImageToolTitle",
299
+ children: t("imageGenerate")
300
+ }),
301
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
302
+ className: "codexImageBeta",
303
+ children: t("imageBeta")
304
+ }),
305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
306
+ className: "codexImageToolState",
307
+ children: status
308
+ })
309
+ ]
628
310
  }),
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
- ]
311
+ image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
312
+ className: "codexImageToolGallery",
313
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
314
+ attachment: image.attachment,
315
+ original,
316
+ rpc,
317
+ sessionId,
318
+ loadImage,
319
+ attachForEdit,
320
+ getImageViewer,
321
+ getInternalImageViewer,
322
+ t
707
323
  })
708
324
  }),
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]
325
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
326
+ className: "codexImageToolError",
327
+ children: error
854
328
  })
855
329
  ]
856
330
  });
857
331
  }
858
332
  //#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
958
- //#region src/settings-contract.js
959
- const SETTINGS_NAMESPACE = "codex-subscription";
960
- const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
961
- const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
962
- const QUICK_QUOTA_MODE_PERCENT = "percent";
963
- const QUICK_QUOTA_MODE_FORECAST = "forecast";
964
- const SEARCH_PROVIDER_FIELD = "searchProvider";
965
- const SEARCH_PROVIDER_AUTO = "auto";
966
- const SEARCH_PROVIDER_CODEX = "codex";
967
- const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
968
- const SPEED_MODE_FIELD = "speedMode";
969
- const SPEED_MODE_STANDARD = "standard";
970
- const SPEED_MODE_FAST = "fast";
971
- const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
972
- const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
973
- const OUTPUT_VERBOSITY_DEFAULT = "default";
974
- const OUTPUT_VERBOSITY_MEDIUM = "medium";
975
- const OUTPUT_VERBOSITY_HIGH = "high";
976
- const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
977
- const CONTEXT_MODE_FIELD = "contextMode";
978
- const CONTEXT_MODE_STANDARD = "standard";
979
- const CONTEXT_MODE_EXTENDED = "extended";
980
- const CONTEXT_MODE_CUSTOM = "custom";
981
- const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
982
- const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
983
- const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
984
- const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
985
- const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
986
- const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
987
- "gpt-5.4": "customContextGpt54",
988
- "gpt-5.4-mini": "customContextGpt54Mini",
989
- "gpt-5.5": "customContextGpt55",
990
- "gpt-5.6": "customContextGpt56",
991
- "gpt-6-astra": "customContextGpt6Astra"
992
- });
993
- const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
994
- "gpt-5.4": 1e6,
995
- "gpt-5.4-mini": 4e5,
996
- "gpt-5.5": 1e6,
997
- "gpt-5.6": 1e6,
998
- "gpt-6-astra": 872e3
999
- });
1000
- const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
1001
- "gpt-5.4": 272e3,
1002
- "gpt-5.4-mini": 272e3,
1003
- "gpt-5.5": 272e3,
1004
- "gpt-5.6": 272e3,
1005
- "gpt-6-astra": 272e3
1006
- });
1007
- const normalizeSearchProvider = (value) => [
1008
- "auto",
1009
- "dsh",
1010
- "codex"
1011
- ].includes(value) ? value : DEFAULT_SEARCH_PROVIDER;
1012
- const normalizeOutputVerbosity = (value) => [
1013
- "default",
1014
- "low",
1015
- "medium",
1016
- "high"
1017
- ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
1018
- const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
1019
- const normalizeContextMode = (value) => [
1020
- "standard",
1021
- "extended",
1022
- "custom"
1023
- ].includes(value) ? value : DEFAULT_CONTEXT_MODE;
1024
- const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
1025
- if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
1026
- return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
1027
- };
1028
- const formatContextWindow = (value) => value === 1e6 ? "1M" : `${Math.round(value / 1e3)}K`;
1029
- const parseContextWindow = (value) => {
1030
- const match = /^\s*(\d+)\s*$/u.exec(String(value));
1031
- if (match === null) return NaN;
1032
- return Number(match[1]);
1033
- };
1034
- const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
1035
- "off",
1036
- "percent",
1037
- "bar",
1038
- "forecast"
1039
- ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
1040
- const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
1041
- //#endregion
1042
- //#region src/sidebar-quota.js
1043
- const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100 && Number.isFinite(window?.windowSeconds) && window.windowSeconds > 0;
1044
- const normalized = (value) => String(value ?? "").toLocaleLowerCase("en-US").replaceAll(/[^a-z0-9]+/gu, "-");
1045
- const limitMatchesModel = (limit, model) => {
1046
- if (/\bspark\b/u.test(normalized(model))) return /\bspark\b/u.test(normalized(`${limit?.id ?? ""} ${limit?.name ?? ""}`));
1047
- return limit?.id === "codex";
333
+ //#region src/client-locales.js
334
+ const zh = {
335
+ imageEditLocation: "位置",
336
+ imageEditReferenceGuide: "本次编辑的干净源图为「{sourceName}」,编号定位参考图为「{referenceName}」。坐标以图片左上角为原点,x 向右、y 向下,百分比相对于整张图片。请查看这两张图片,将它们同时作为编辑工具的参考图,并在工具提示词中完整保留下方编号、位置和修改要求。只修改源图中对应位置的内容;定位参考图上的编号、圆点和引线仅用于定位,不得绘入最终结果。若无法读取两张图片或确定位置,请说明问题,不要猜测或忽略标注。",
337
+ nav: "Codex 订阅",
338
+ title: "Codex 订阅",
339
+ connected: "已登录",
340
+ disconnected: "未登录",
341
+ accountLoading: "正在读取账户状态…",
342
+ browserLogin: "浏览器登录",
343
+ deviceLogin: "设备代码登录",
344
+ logout: "退出登录",
345
+ addAccount: "添加账号",
346
+ switchAccount: "切换",
347
+ removeAccount: "移除",
348
+ removeConfirm: "确认移除",
349
+ removeCancel: "保留",
350
+ signOutAll: "退出全部账号",
351
+ cancel: "取消",
352
+ submit: "提交授权码",
353
+ openLogin: "打开登录页",
354
+ manualCode: "若浏览器回调没有自动完成,请粘贴授权码或完整重定向地址。",
355
+ deviceHint: "在登录页输入此设备代码:",
356
+ waiting: "正在等待登录完成…",
357
+ failed: "登录失败,请重试。",
358
+ accountRetry: "重试",
359
+ accountRetrying: "正在重试账户状态…",
360
+ accountCredentialUnavailable: "登录凭据暂时不可用。请重试;不会删除已保存的登录信息。",
361
+ accountCredentialMalformed: "登录凭据格式异常,无法读取账户状态。重试不会删除已保存的登录信息。",
362
+ accountStatusTimeout: "读取账户状态超时,请重试。",
363
+ accountStatusTransport: "无法连接账户服务,请检查连接后重试。",
364
+ accountStatusUnknown: "无法读取账户状态,请重试。",
365
+ diagnostics: "支持诊断",
366
+ diagnosticsLoad: "生成诊断",
367
+ diagnosticsLoading: "生成中…",
368
+ diagnosticsCopy: "复制诊断",
369
+ diagnosticsCopied: "已复制",
370
+ diagnosticsFailed: "无法生成诊断信息。",
371
+ feedbackOpen: "反馈问题",
372
+ showEmail: "显示完整邮箱",
373
+ hideEmail: "隐藏邮箱",
374
+ emailUnavailable: "邮箱不可用",
375
+ searchTitle: "搜索来源",
376
+ searchScope: "自动按当前会话模型分流;手动选择会覆盖所有模型和会话。",
377
+ searchAuto: "自动",
378
+ searchAutoHint: "Codex 模型用订阅搜索,其他模型用 DSH",
379
+ searchDsh: "DSH 默认",
380
+ searchDshHint: "所有模型使用 DSH 当前搜索服务",
381
+ searchCodex: "Codex 订阅",
382
+ searchCodexHint: "所有模型通过已登录的 ChatGPT 订阅搜索",
383
+ preferenceFailed: "设置未保存。",
384
+ preferenceRetry: "重试",
385
+ usage: "订阅额度",
386
+ refresh: "刷新",
387
+ refreshing: "刷新中…",
388
+ noUsage: "登录后可读取 ChatGPT 返回的额度窗口。",
389
+ usageLoading: "正在读取额度…",
390
+ usageEmpty: "当前账户没有返回可显示的额度窗口。请稍后刷新;这不代表额度为零。",
391
+ usageUpdated: "更新于 {value}",
392
+ remaining: "剩余 {value}%",
393
+ windowFiveHours: "5 小时额度",
394
+ windowDaily: "每日额度",
395
+ windowWeekly: "每周额度",
396
+ windowMonthly: "每月额度",
397
+ windowAnnual: "年度额度",
398
+ windowHours: "{value} 小时额度",
399
+ windowDays: "{value} 天额度",
400
+ resets: "重置于 {value}",
401
+ resetUnknown: "重置时间未提供",
402
+ creditsBalance: "额外 Credits 余额",
403
+ creditsUnit: "credits",
404
+ unlimited: "不限额",
405
+ monthlyCreditLimit: "Credits 月度消费上限",
406
+ resetCredits: "额度重置",
407
+ resetCreditDefaultName: "额度重置",
408
+ resetUse: "使用",
409
+ resetPreparing: "准备中…",
410
+ resetConfirmTitle: "确认使用额度重置",
411
+ resetWarning: "执行后会消耗 1 次,且无法撤销。",
412
+ resetEarlyWarning: "当前额度未用尽,服务可能不执行重置。",
413
+ resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
414
+ resetCreditExpires: "到期:{value}",
415
+ resetCreditExpiryUnknown: "到期时间未提供",
416
+ resetCreditExpiryLoading: "正在读取到期时间…",
417
+ resetCreditExpiryFailed: "无法读取到期时间",
418
+ resetWait: "请等待 {count} ",
419
+ resetFinal: "确认使用",
420
+ resetUsing: "使用中…",
421
+ resetSuccess: "额度重置已完成。",
422
+ resetNothing: "当前没有可重置的额度,未消耗新的重置次数。",
423
+ resetNoCredit: "没有可用的额度重置。",
424
+ resetAlready: "这次重置请求已处理。",
425
+ resetFailed: "无法使用额度重置。",
426
+ resetRenewLogin: "登录状态已失效,请重新登录。",
427
+ resetExpired: "本次确认已失效,请重新开始。",
428
+ resetInProgress: "额度重置正在处理中。",
429
+ resetTooEarly: "请等待冷静期结束后再确认。",
430
+ resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
431
+ resetAccountChanged: "登录账号已变更,请重新开始。",
432
+ resetUncertain: "服务端返回结果不确定。请再次确认,插件会复用同一个请求,不会另外发起一次重置。",
433
+ creditsNote: "额外 Credits、消费上限、重置次数分别显示。",
434
+ creditsUsed: "已用 {used} / {limit} credits",
435
+ spendReached: "Credits 月度消费上限已用尽。",
436
+ unavailable: "暂无数据",
437
+ quickQuotaSetting: "输入框额度",
438
+ quickQuotaOff: "关闭",
439
+ quickQuotaPercent: "百分比",
440
+ quickQuotaBar: "进度条",
441
+ quickQuotaForecast: "续航预测",
442
+ quickQuotaBeta: "Beta",
443
+ quickQuotaForecastHint: "按消耗速度自适应校准;高消耗通常 5–10 分钟可估算,低消耗会显示用量稳定。进度会在本机保留。",
444
+ contextTitle: "上下文窗口",
445
+ contextStandard: "标准",
446
+ contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
447
+ contextExtended: "扩展",
448
+ contextExtendedHint: "按模型使用已审核的扩展预算(Astra:872K);实际可用性由服务端决定。",
449
+ contextCustom: "自定义",
450
+ contextCustomHint: "输入完整 Token 数值;较低数值会让官方 Agent 预设更早压缩上下文。",
451
+ contextTokens: "Token 上限",
452
+ contextFixed: "固定 {value}",
453
+ contextMaximum: "范围 128000–{value}",
454
+ quickQuotaStatus: "Codex 剩余额度 {value}%",
455
+ quickQuotaForecastStatus: "Codex 剩余额度 {value}%,按当前速度预计可用 {duration}",
456
+ quickQuotaForecastCalibrating: "校准中",
457
+ quickQuotaForecastCalibratingStatus: "Codex 剩余额度 {value}%,续航预测正在校准",
458
+ quickQuotaForecastIdle: "用量稳定",
459
+ quickQuotaForecastIdleStatus: "Codex 剩余额度 {value}%,当前没有可测量的消耗速度",
460
+ quickQuotaForecastUntilReset: "够用到重置",
461
+ quickQuotaForecastUntilResetStatus: "Codex 剩余额度 {value}%,按当前速度足够用到重置",
462
+ quotaForecast: "按当前速度 {symbol}{duration}",
463
+ quotaForecastCalibrating: "续航正在校准",
464
+ quotaForecastIdle: "当前用量稳定",
465
+ quotaForecastUntilReset: "按当前速度足够用到重置",
466
+ runwayDaysHours: "{days} 天 {hours} 小时",
467
+ runwayDays: "{days} 天",
468
+ runwayHours: "{hours} 小时",
469
+ runwayMinutes: "{minutes} 分钟",
470
+ speedTitle: "速度",
471
+ speedStandard: "标准",
472
+ speedStandardHint: "标准速度",
473
+ speedFast: "高速",
474
+ speedFastHint: "1.5 倍,消耗更多 Credits",
475
+ verbosityTitle: "输出详略",
476
+ verbosityDefault: "模型默认",
477
+ verbosityDefaultHint: "使用官方模型目录推荐值",
478
+ verbosityLow: "简洁",
479
+ verbosityLowHint: "更短、更直接",
480
+ verbosityMedium: "均衡",
481
+ verbosityMediumHint: "兼顾完整性与长度",
482
+ verbosityHigh: "详细",
483
+ verbosityHighHint: "更充分的说明与结构",
484
+ modelMenuAria: "模型、推理等级、速度与输出详略",
485
+ modelLabel: "模型",
486
+ effortLabel: "推理等级",
487
+ providerDefault: "Default",
488
+ selectModel: "选择模型",
489
+ modelsLoading: "正在读取模型…",
490
+ modelsEmpty: "没有可用模型。",
491
+ effortsEmpty: "当前模型未提供推理等级。",
492
+ modelRetry: "重试",
493
+ modelDirectoryFailed: "模型目录加载失败,请重试。",
494
+ modelFailed: "模型目录加载失败:{value}",
495
+ groupFailed: "{name}:{value}",
496
+ imageGenerate: "生成图片",
497
+ imageBeta: "Beta",
498
+ imageGenerating: "正在生成…",
499
+ imageGenerated: "已生成",
500
+ imageFailed: "生成失败",
501
+ imageLabel: "生成的图片",
502
+ imageOpen: "查看图片",
503
+ imageOpenNamed: "查看 {value}",
504
+ imageLoading: "正在加载图片…",
505
+ imageLoadFailed: "图片加载失败,点击重试",
506
+ imagePreview: "图片预览",
507
+ imageClosePreview: "关闭预览",
508
+ imageDownload: "下载",
509
+ imageDownloadPreparing: "正在准备原图…",
510
+ imageDownloadFailed: "下载失败,重试",
511
+ imageFit: "适合窗口",
512
+ imageAnnotate: "标注部位",
513
+ imageAnnotateCancel: "取消标注",
514
+ imageAnnotateHint: "点击图片添加编号标注",
515
+ imageAnnotation: "标注 {value}",
516
+ imageAnnotationPlaceholder: "描述这个部位要修改什么",
517
+ imageRegions: "区域备注",
518
+ imageCopyNotes: "复制备注",
519
+ imageCopied: "已复制",
520
+ imagePrevious: "上一张图片",
521
+ imageNext: "下一张图片",
522
+ imageZoomHint: "滚轮缩放 · 拖动查看 · 双击切换原始大小",
523
+ imageActual: "原始大小",
524
+ imageEditDefault: "编辑这张图片。",
525
+ imageRegionNotes: "部位修改:",
526
+ imageEdit: "在输入框中继续编辑",
527
+ imageEditPreparing: "正在添加到输入框…",
528
+ imageEditFailed: "回填失败:请填写每个标记的备注,并确认输入框可接收图片后重试。",
529
+ imageRemoveAnnotation: "删除标注"
530
+ };
531
+ const en = {
532
+ imageEditLocation: "Location",
533
+ 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.",
534
+ nav: "Codex",
535
+ title: "Codex subscription",
536
+ connected: "Signed in",
537
+ disconnected: "Not signed in",
538
+ accountLoading: "Reading account status…",
539
+ browserLogin: "Browser sign-in",
540
+ deviceLogin: "Device-code sign-in",
541
+ logout: "Sign out",
542
+ addAccount: "Add account",
543
+ switchAccount: "Switch",
544
+ removeAccount: "Remove",
545
+ removeConfirm: "Confirm remove",
546
+ removeCancel: "Keep",
547
+ signOutAll: "Sign out all",
548
+ cancel: "Cancel",
549
+ submit: "Submit authorization code",
550
+ openLogin: "Open sign-in page",
551
+ manualCode: "If the browser callback did not finish automatically, paste the code or full redirect URL.",
552
+ deviceHint: "Enter this device code on the sign-in page:",
553
+ waiting: "Waiting for sign-in to finish…",
554
+ failed: "Sign-in failed. Try again.",
555
+ accountRetry: "Retry",
556
+ accountRetrying: "Retrying account status…",
557
+ accountCredentialUnavailable: "The saved sign-in credentials are temporarily unavailable. Retry; saved sign-in information will not be deleted.",
558
+ accountCredentialMalformed: "The saved sign-in credentials are malformed, so account status cannot be read. Retrying will not delete saved sign-in information.",
559
+ accountStatusTimeout: "Reading account status timed out. Retry.",
560
+ accountStatusTransport: "The account service is unavailable. Check the connection and retry.",
561
+ accountStatusUnknown: "Could not read account status. Retry.",
562
+ diagnostics: "Support diagnostics",
563
+ diagnosticsLoad: "Create report",
564
+ diagnosticsLoading: "Creating…",
565
+ diagnosticsCopy: "Copy report",
566
+ diagnosticsCopied: "Copied",
567
+ diagnosticsFailed: "Could not create diagnostics.",
568
+ feedbackOpen: "Report a problem",
569
+ showEmail: "Show full email",
570
+ hideEmail: "Hide email",
571
+ emailUnavailable: "Email unavailable",
572
+ searchTitle: "Search source",
573
+ searchScope: "Auto follows the current session model; an explicit choice overrides every model and session.",
574
+ searchAuto: "Auto",
575
+ searchAutoHint: "Codex models use subscription search; other models use DSH",
576
+ searchDsh: "DSH default",
577
+ searchDshHint: "Use DSH's current search service for every model",
578
+ searchCodex: "Codex subscription",
579
+ searchCodexHint: "Search through the signed-in ChatGPT subscription for every model",
580
+ preferenceFailed: "The setting was not saved.",
581
+ preferenceRetry: "Retry",
582
+ usage: "Subscription quota",
583
+ refresh: "Refresh",
584
+ refreshing: "Refreshing…",
585
+ noUsage: "Sign in to read quota windows reported by ChatGPT.",
586
+ usageLoading: "Reading quota…",
587
+ usageEmpty: "This account returned no displayable quota windows. Refresh later; this does not mean zero quota.",
588
+ usageUpdated: "Updated {value}",
589
+ remaining: "{value}% remaining",
590
+ windowFiveHours: "5-hour quota",
591
+ windowDaily: "Daily quota",
592
+ windowWeekly: "Weekly quota",
593
+ windowMonthly: "Monthly quota",
594
+ windowAnnual: "Annual quota",
595
+ windowHours: "{value}-hour quota",
596
+ windowDays: "{value}-day quota",
597
+ resets: "Resets {value}",
598
+ resetUnknown: "Reset time not provided",
599
+ creditsBalance: "Extra Credits balance",
600
+ creditsUnit: "credits",
601
+ unlimited: "Unlimited",
602
+ monthlyCreditLimit: "Monthly Credits spending cap",
603
+ resetCredits: "Quota resets",
604
+ resetCreditDefaultName: "Quota reset",
605
+ resetUse: "Use",
606
+ resetPreparing: "Preparing…",
607
+ resetConfirmTitle: "Confirm quota reset",
608
+ resetWarning: "This consumes one reset and cannot be undone.",
609
+ resetEarlyWarning: "Quota remains. The service may decline the reset.",
610
+ resetAcknowledge: "I understand this may consume one reset now",
611
+ resetCreditExpires: "Expires {value}",
612
+ resetCreditExpiryUnknown: "Expiration time not provided",
613
+ resetCreditExpiryLoading: "Reading expiration…",
614
+ resetCreditExpiryFailed: "Could not read expiration",
615
+ resetWait: "Wait {count} seconds",
616
+ resetFinal: "Confirm use",
617
+ resetUsing: "Using…",
618
+ resetSuccess: "Quota reset completed.",
619
+ resetNothing: "There is currently nothing to reset; no new reset was consumed.",
620
+ resetNoCredit: "No quota reset is available.",
621
+ resetAlready: "This reset request was already processed.",
622
+ resetFailed: "Could not use the quota reset.",
623
+ resetRenewLogin: "Your sign-in expired. Sign in again.",
624
+ resetExpired: "This confirmation expired. Start again.",
625
+ resetInProgress: "A quota reset is already in progress.",
626
+ resetTooEarly: "Wait for the cooldown before confirming.",
627
+ resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
628
+ resetAccountChanged: "The signed-in account changed. Start again.",
629
+ resetUncertain: "The server result is uncertain. Confirm again to check the same request; the plugin will not start a separate reset.",
630
+ creditsNote: "Extra Credits, spending caps, and resets are separate items.",
631
+ creditsUsed: "{used} / {limit} credits used",
632
+ spendReached: "The monthly Credits spending cap has been reached.",
633
+ unavailable: "No data yet",
634
+ quickQuotaSetting: "Composer quota",
635
+ quickQuotaOff: "Off",
636
+ quickQuotaPercent: "Percent",
637
+ quickQuotaBar: "Progress bar",
638
+ quickQuotaForecast: "Runway",
639
+ quickQuotaBeta: "Beta",
640
+ quickQuotaForecastHint: "Calibrates to actual consumption: high use is usually estimated in 5–10 minutes, while low use is shown as stable. Progress is kept locally.",
641
+ contextTitle: "Context window",
642
+ contextStandard: "Standard",
643
+ contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
644
+ contextExtended: "Extended",
645
+ contextExtendedHint: "Uses each model's audited extended budget (Astra: 872K); availability depends on the service.",
646
+ contextCustom: "Custom",
647
+ contextCustomHint: "Enter the full token count; lower values make official agent presets compact sooner.",
648
+ contextTokens: "Token limit",
649
+ contextFixed: "Fixed {value}",
650
+ contextMaximum: "128000–{value}",
651
+ quickQuotaStatus: "Codex quota: {value}% remaining",
652
+ quickQuotaForecastStatus: "Codex quota: {value}% remaining; about {duration} at the current pace",
653
+ quickQuotaForecastCalibrating: "Calibrating",
654
+ quickQuotaForecastCalibratingStatus: "Codex quota: {value}% remaining; runway is calibrating",
655
+ quickQuotaForecastIdle: "Usage stable",
656
+ quickQuotaForecastIdleStatus: "Codex quota: {value}% remaining; no measurable consumption pace",
657
+ quickQuotaForecastUntilReset: "Enough until reset",
658
+ quickQuotaForecastUntilResetStatus: "Codex quota: {value}% remaining; enough until reset at the current pace",
659
+ quotaForecast: "At current pace {symbol}{duration}",
660
+ quotaForecastCalibrating: "Runway calibrating",
661
+ quotaForecastIdle: "Usage currently stable",
662
+ quotaForecastUntilReset: "Enough until reset at current pace",
663
+ runwayDaysHours: "{days}d {hours}h",
664
+ runwayDays: "{days}d",
665
+ runwayHours: "{hours}h",
666
+ runwayMinutes: "{minutes}m",
667
+ speedTitle: "Speed",
668
+ speedStandard: "Standard",
669
+ speedStandardHint: "Standard speed",
670
+ speedFast: "Fast",
671
+ speedFastHint: "1.5x; higher Credits use",
672
+ verbosityTitle: "Output detail",
673
+ verbosityDefault: "Model default",
674
+ verbosityDefaultHint: "Use the official model catalog recommendation",
675
+ verbosityLow: "Concise",
676
+ verbosityLowHint: "Shorter and more direct",
677
+ verbosityMedium: "Balanced",
678
+ verbosityMediumHint: "Balance completeness and length",
679
+ verbosityHigh: "Detailed",
680
+ verbosityHighHint: "More explanation and structure",
681
+ modelMenuAria: "Model, effort, speed, and output detail",
682
+ modelLabel: "Model",
683
+ effortLabel: "Effort",
684
+ providerDefault: "Default",
685
+ selectModel: "Select model",
686
+ modelsLoading: "Loading models…",
687
+ modelsEmpty: "No models available.",
688
+ effortsEmpty: "This model provides no reasoning effort levels.",
689
+ modelRetry: "Retry",
690
+ modelDirectoryFailed: "Could not load the model directory. Try again.",
691
+ modelFailed: "Could not load models: {value}",
692
+ groupFailed: "{name}: {value}",
693
+ imageGenerate: "Generate image",
694
+ imageBeta: "Beta",
695
+ imageGenerating: "Generating…",
696
+ imageGenerated: "Generated",
697
+ imageFailed: "Generation failed",
698
+ imageLabel: "Generated image",
699
+ imageOpen: "View image",
700
+ imageOpenNamed: "View {value}",
701
+ imageLoading: "Loading image…",
702
+ imageLoadFailed: "Image failed to load. Click to retry",
703
+ imagePreview: "Image preview",
704
+ imageClosePreview: "Close preview",
705
+ imageDownload: "Download",
706
+ imageDownloadPreparing: "Preparing original…",
707
+ imageDownloadFailed: "Download failed. Retry",
708
+ imageFit: "Fit to window",
709
+ imageAnnotate: "Annotate",
710
+ imageAnnotateCancel: "Cancel marking",
711
+ imageAnnotateHint: "Click the image to add a numbered note",
712
+ imageAnnotation: "Note {value}",
713
+ imageAnnotationPlaceholder: "Describe what should change in this area",
714
+ imageRegions: "Region notes",
715
+ imageCopyNotes: "Copy notes",
716
+ imageCopied: "Copied",
717
+ imagePrevious: "Previous image",
718
+ imageNext: "Next image",
719
+ imageZoomHint: "Wheel to zoom · drag to pan · double-click for 100%",
720
+ imageActual: "100%",
721
+ imageEditDefault: "Edit this image.",
722
+ imageRegionNotes: "Region changes:",
723
+ imageEdit: "Continue editing in composer",
724
+ imageEditPreparing: "Adding to composer…",
725
+ imageEditFailed: "Handoff failed. Add a note to every marker and ensure the composer accepts images, then retry.",
726
+ imageRemoveAnnotation: "Remove note"
1048
727
  };
1049
- function selectModelQuotaWindows(usage, model) {
1050
- return (Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : []).map((selected) => ({
1051
- remainingPercent: selected.remainingPercent,
1052
- windowSeconds: selected.windowSeconds,
1053
- ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
1054
- ...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
1055
- })).sort((a, b) => a.windowSeconds - b.windowSeconds);
1056
- }
1057
728
  //#endregion
1058
- //#region src/login-progress.js
1059
- /** Reconcile a login flow with the credential store without exposing credentials. */
1060
- async function readLoginProgress({ flow, readFlow, readAccount }) {
1061
- try {
1062
- const nextFlow = await readFlow();
1063
- if (nextFlow.phase === "failed") try {
1064
- const account = await readAccount();
1065
- if (account?.authenticated === true) return {
1066
- flow: {
1067
- id: flow.id,
1068
- method: flow.method,
1069
- phase: "authenticated",
1070
- authenticated: true
1071
- },
1072
- account,
1073
- recovered: true
1074
- };
1075
- } catch {}
1076
- if (nextFlow.phase !== "authenticated") return { flow: nextFlow };
1077
- return {
1078
- flow: nextFlow,
1079
- account: await readAccount()
1080
- };
1081
- } catch (flowError) {
1082
- try {
1083
- const account = await readAccount();
1084
- if (account?.authenticated === true) return {
1085
- flow: {
1086
- id: flow.id,
1087
- method: flow.method,
1088
- phase: "authenticated",
1089
- authenticated: true
1090
- },
1091
- account,
1092
- recovered: true
1093
- };
1094
- } catch {}
1095
- throw flowError;
1096
- }
1097
- }
729
+ //#region src/client-styles.js
730
+ const STYLE = `
731
+ .codexSubscriptionSearchHead{display:flex;flex-direction:column;gap:1px}
732
+ .codexSubscriptionSearchScope{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
733
+ .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
734
+ .codexSubscription h2,.codexSubscription h3,.codexSubscription p{margin:0}
735
+ .codexSubscriptionHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
736
+ .codexSubscription h2{font-size:16px;line-height:24px;font-weight:500}
737
+ .codexSubscription h3{font-size:14px;line-height:22px;font-weight:500}
738
+ .codexSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
739
+ .codexSubscriptionUsageCard{padding:12px 14px;gap:9px}
740
+ .codexSubscriptionPreferencesCard{padding:12px 14px;gap:10px}
741
+ .codexSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}
742
+ .codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}
743
+ .codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}
744
+ .codexSubscriptionPreferenceHint{max-width:300px;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
745
+ .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}
746
+ .codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer;white-space:nowrap}
747
+ .codexSubscriptionQuotaMode small{margin-left:3px;font-size:9px;line-height:1;color:var(--dsw-alias-label-tertiary)}
748
+ .codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}
749
+ .codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}
750
+ .codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}
751
+ .codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
752
+ .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}
753
+ .codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
754
+ .codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}
755
+ .codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
756
+ .codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}
757
+ .codexSubscriptionContextTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
758
+ .codexSubscriptionContextTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
759
+ .codexSubscriptionContextTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}
760
+ .codexSubscriptionContextTrigger svg{color:var(--dsw-alias-label-tertiary);transition:transform 120ms var(--ds-ease-in-out)}
761
+ .codexSubscriptionContextTrigger[aria-expanded=true] svg{transform:rotate(180deg)}
762
+ .codexSubscriptionContextModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}
763
+ .codexSubscriptionContextModel{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}
764
+ .codexSubscriptionContextModel:last-child{border-bottom:0}
765
+ .codexSubscriptionContextModelCopy{display:flex;min-width:0;flex-direction:column}
766
+ .codexSubscriptionContextModelCopy strong{font-size:12px;line-height:18px;font-weight:500}
767
+ .codexSubscriptionContextModelCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
768
+ .codexSubscriptionContextInput{width:116px}
769
+ .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}
770
+ .codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px}
771
+ .codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}
772
+ .codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}
773
+ .codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}
774
+ .codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
775
+ .codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}
776
+ .codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}
777
+ .codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}
778
+ .codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}
779
+ .codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}
780
+ .codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
781
+ .codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
782
+ .codexSubscriptionQuotaModes[data-saving=true] .codexSubscriptionQuotaMode:has(input:disabled){cursor:wait;opacity:1}
783
+ .codexSubscriptionSearchChoices[data-saving=true] .codexSubscriptionSearchChoice:has(input:disabled){cursor:wait;opacity:1}
784
+ .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}
785
+ .codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
786
+ .codexSubscriptionAccounts{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}
787
+ .codexSubscriptionAccount{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:42px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px}
788
+ .codexSubscriptionAccount:last-child{border-bottom:0}
789
+ .codexSubscriptionAccount[data-active=true] .codexSubscriptionEmail,.codexSubscriptionAccount[data-active=true]>span{font-weight:600}
790
+ .codexSubscriptionEmail{max-width:100%;overflow:hidden;padding:2px 4px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
791
+ .codexSubscriptionEmail:hover{background:var(--dsw-alias-interactive-bg-hover)}
792
+ .codexSubscriptionEmail:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}
793
+ .codexSubscriptionFlow label{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary)}
794
+ .codexSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}
795
+ .codexSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}
796
+ .codexSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
797
+ .codexSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
798
+ .codexSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
799
+ .codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
800
+ .codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
801
+ .codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}
802
+ .codexSubscriptionInput{width:100%;box-sizing:border-box}
803
+ .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}
804
+ .codexSubscriptionRecover .codexSubscriptionError{flex:1}
805
+ .codexSubscriptionRecover button{flex:0 0 auto}
806
+ .codexSubscriptionDiagnostics{padding:8px 12px;gap:8px;background:transparent;color:var(--dsw-alias-label-secondary)}
807
+ .codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}
808
+ .codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}
809
+ .codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}
810
+ .codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
811
+ .codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}
812
+ .codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
813
+ .codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}
814
+ .codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
815
+ .codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
816
+ .codexSubscriptionLimits{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:6px}
817
+ .codexSubscriptionLimit{min-width:0;border-radius:10px;padding:9px 12px;background:var(--dsw-alias-bg-module-platform);display:flex;flex-direction:column;gap:6px}
818
+ .codexSubscriptionLimitTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
819
+ .codexSubscriptionLimitLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
820
+ .codexSubscriptionLimit strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
821
+ .codexSubscriptionLimit progress{width:100%;height:4px;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}
822
+ .codexSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}
823
+ .codexSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
824
+ .codexSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
825
+ .codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
826
+ .codexSubscriptionCreditSection{display:flex;flex-direction:column;gap:7px}
827
+ .codexSubscriptionCreditNote{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
828
+ .codexSubscriptionCreditRows{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}
829
+ .codexSubscriptionCreditBalance,.codexSubscriptionSpendLimit{min-width:0;border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-module-platform)}
830
+ .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}
831
+ .codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
832
+ .codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
833
+ .codexSubscriptionCreditRows{display:flex;flex-direction:column;gap:6px}
834
+ .codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}
835
+ .codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}
836
+ .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)}
837
+ .codexSubscriptionResetCard .codexSubscriptionResetMeta{flex:1}
838
+ .codexSubscriptionResetCard strong{overflow:hidden;font-size:12px;line-height:18px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}
839
+ .codexSubscriptionResetCard .codexSubscriptionActions{flex:0 0 auto}
840
+ .codexSubscriptionResetCard .codexSubscriptionResetUse{min-height:28px;padding:0 10px}
841
+ .codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}
842
+ .codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}
843
+ .codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}
844
+ .codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
845
+ .codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
846
+ .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}
847
+ .codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}
848
+ .codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}
849
+ .codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
850
+ .codexSubscriptionResetUse:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
851
+ .codexSubscriptionResetUse:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}
852
+ .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}
853
+ .codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
854
+ .codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
855
+ .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}
856
+ .codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}
857
+ .codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
858
+ .codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
859
+ .codexComposerQuotaWindows{display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap}
860
+ .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}
861
+ .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}
862
+ .codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}
863
+ .codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}
864
+ .codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
865
+ .codexModelSelect{position:relative;min-width:0}
866
+ .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}
867
+ .codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}
868
+ .codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
869
+ .codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}
870
+ .codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}
871
+ .codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
872
+ .codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}
873
+ .codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}
874
+ .codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
875
+ .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}
876
+ .codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}
877
+ .codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}
878
+ .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}
879
+ .codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}
880
+ .codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}
881
+ .codexModelSelectCellLabel{flex:none;white-space:nowrap}
882
+ .codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}
883
+ .codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}
884
+ .codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}
885
+ .codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}
886
+ .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}
887
+ .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}
888
+ .codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}
889
+ .codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}
890
+ .codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}
891
+ .codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}
892
+ .codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}
893
+ .codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}
894
+ .codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}
895
+ .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}
896
+ .codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}
897
+ .codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
898
+ .codexModelSelectMenu{overflow:visible}
899
+ .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}
900
+ .codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}
901
+ .codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}
902
+ .codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}
903
+ .codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}
904
+ .codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}
905
+ .codexImageToolTitle{font-weight:500}
906
+ .codexImageToolState{color:var(--dsw-alias-label-tertiary)}
907
+ .codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
908
+ .codexImageToolGallery{margin-left:24px}
909
+ .codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);height:240px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}
910
+ .codexGeneratedImageFrame img{display:block;width:100%;height:100%;object-fit:cover}
911
+ .codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}
912
+ @keyframes codexImageSpin{to{transform:rotate(360deg)}}
913
+ .codexImageBeta{padding:0 5px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px}
914
+ .codexImageToolGallery{display:flex;align-items:flex-start;flex-direction:column;gap:8px}
915
+ @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
916
+ @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}
917
+ .codexSubscriptionActions{width:100%}
918
+ .codexSubscriptionSearchChoices{grid-template-columns:1fr}}
919
+ @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
920
+ `;
1098
921
  //#endregion
1099
- //#region src/preference-controller.js
1100
- const CHANNEL$2 = "/codex-subscription";
1101
- const unwrap$1 = (response) => {
1102
- if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
1103
- return response.value;
922
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/16/solid/esm/BoltIcon.js
923
+ function BoltIcon({ title, titleId, ...props }, svgRef) {
924
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
925
+ xmlns: "http://www.w3.org/2000/svg",
926
+ viewBox: "0 0 16 16",
927
+ fill: "currentColor",
928
+ "aria-hidden": "true",
929
+ "data-slot": "icon",
930
+ ref: svgRef,
931
+ "aria-labelledby": titleId
932
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
933
+ fillRule: "evenodd",
934
+ d: "M9.58 1.077a.75.75 0 0 1 .405.82L9.165 6h4.085a.75.75 0 0 1 .567 1.241l-6.5 7.5a.75.75 0 0 1-1.302-.638L6.835 10H2.75a.75.75 0 0 1-.567-1.241l6.5-7.5a.75.75 0 0 1 .897-.182Z",
935
+ clipRule: "evenodd"
936
+ }));
937
+ }
938
+ const ForwardRef = /*#__PURE__*/ react.forwardRef(BoltIcon);
939
+ //#endregion
940
+ //#region src/image-edit-reference.js
941
+ const TWO_PI = Math.PI * 2;
942
+ const PIN_FILL = "#e11d48";
943
+ const PIN_OUTER_STROKE = "#111827";
944
+ const PIN_INNER_STROKE = "#ffffff";
945
+ const finiteDimension = (value) => Number.isSafeInteger(value) && value > 0;
946
+ const clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
947
+ const clampCenter = (value, size, edge) => {
948
+ const margin = Math.min(edge, size / 2);
949
+ return clamp$1(value, margin, size - margin);
1104
950
  };
1105
- function createPreferenceController(scope, rpc) {
1106
- let updating = false;
1107
- let error = false;
1108
- let fallbackStatus = "loading";
1109
- let fallback;
1110
- let pendingPatch;
1111
- let failedPatch;
1112
- let generation = 0;
1113
- let contextModels = [];
1114
- let verbosityModels = [];
1115
- let modelError = false;
1116
- let modelRefreshGeneration = 0;
1117
- let modelRefreshStarted = false;
1118
- let disposed = false;
1119
- const sameModels = (left, right) => left.length === right.length && left.every((model, index) => JSON.stringify(model) === JSON.stringify(right[index]));
1120
- const nativeSnapshot = () => scope.getSnapshot();
1121
- const read = () => {
1122
- const native = nativeSnapshot();
1123
- const current = native.status === "ready" ? native : fallbackStatus === "ready" ? fallback : native;
1124
- const value = pendingPatch === void 0 ? current.value : {
1125
- ...current.value,
1126
- ...pendingPatch
1127
- };
1128
- return Object.freeze({
1129
- status: current.status,
1130
- quickQuotaMode: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
1131
- searchProvider: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
1132
- speedMode: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1133
- outputVerbosity: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
1134
- contextMode: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
1135
- customContextWindow: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
1136
- customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1137
- contextModels,
1138
- verbosityModels,
1139
- modelError,
1140
- writable: !updating && current.status === "ready" && current.writable === true,
1141
- saving: updating,
1142
- error
1143
- });
1144
- };
1145
- let snapshot = read();
1146
- const listeners = /* @__PURE__ */ new Set();
1147
- const publish = () => {
1148
- snapshot = read();
1149
- for (const listener of listeners) listener();
951
+ function getBitmapFactory(options) {
952
+ const factory = options?.createImageBitmap ?? options?.bitmapFactory ?? globalThis.createImageBitmap;
953
+ if (typeof factory !== "function") throw new Error("createImageBitmap is not available");
954
+ return factory;
955
+ }
956
+ function getCanvasFactory(options) {
957
+ const injected = options?.createCanvas ?? options?.canvasFactory;
958
+ if (typeof injected === "function") return injected;
959
+ const document = globalThis.document;
960
+ if (document !== void 0 && typeof document.createElement === "function") return (width, height) => {
961
+ const canvas = document.createElement("canvas");
962
+ canvas.width = width;
963
+ canvas.height = height;
964
+ return canvas;
1150
965
  };
1151
- const disposeScope = scope.subscribe(() => {
1152
- error = false;
1153
- if (!updating) failedPatch = void 0;
1154
- publish();
1155
- });
1156
- const acceptFallback = (value) => {
1157
- if (!modelRefreshStarted) {
1158
- contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1159
- verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1160
- }
1161
- fallbackStatus = "ready";
1162
- fallback = {
1163
- status: "ready",
1164
- value: {
1165
- [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
1166
- [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
1167
- [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1168
- [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
1169
- [CONTEXT_MODE_FIELD]: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
1170
- [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
1171
- ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])]))
1172
- },
1173
- writable: value?.writable === true
966
+ throw new Error("A canvas factory is not available");
967
+ }
968
+ function drawPin(context, number, x, y, width, height) {
969
+ const label = String(number);
970
+ const fontSize = Math.max(12, Math.min(40, Math.round(Math.min(width, height) * .03)));
971
+ const radius = Math.max(12, fontSize * .8, fontSize * (.3 * label.length + .35));
972
+ const outerStroke = Math.max(2, Math.min(4, radius * .25));
973
+ const innerStroke = Math.max(1, Math.min(2, radius * .13));
974
+ const edge = radius + outerStroke + 2;
975
+ const targetX = x * Math.max(0, width - 1);
976
+ const targetY = y * Math.max(0, height - 1);
977
+ const centerX = clampCenter(targetX, width, edge);
978
+ const centerY = clampCenter(targetY, height, edge);
979
+ context.save?.();
980
+ if ((centerX !== targetX || centerY !== targetY) && typeof context.moveTo === "function" && typeof context.lineTo === "function") {
981
+ context.beginPath();
982
+ context.moveTo(targetX, targetY);
983
+ context.lineTo(centerX, centerY);
984
+ context.lineWidth = outerStroke * 2;
985
+ context.strokeStyle = PIN_OUTER_STROKE;
986
+ context.stroke();
987
+ context.beginPath();
988
+ context.moveTo(targetX, targetY);
989
+ context.lineTo(centerX, centerY);
990
+ context.lineWidth = innerStroke;
991
+ context.strokeStyle = PIN_INNER_STROKE;
992
+ context.stroke();
993
+ }
994
+ context.beginPath();
995
+ context.arc(centerX, centerY, radius, 0, TWO_PI);
996
+ context.fillStyle = PIN_FILL;
997
+ context.fill();
998
+ context.lineWidth = outerStroke;
999
+ context.strokeStyle = PIN_OUTER_STROKE;
1000
+ context.stroke();
1001
+ context.lineWidth = innerStroke;
1002
+ context.strokeStyle = PIN_INNER_STROKE;
1003
+ context.stroke();
1004
+ context.font = `700 ${fontSize}px sans-serif`;
1005
+ context.fillStyle = PIN_INNER_STROKE;
1006
+ context.textAlign = "center";
1007
+ context.textBaseline = "middle";
1008
+ context.fillText(label, centerX, centerY);
1009
+ context.restore?.();
1010
+ }
1011
+ function encodePng(canvas) {
1012
+ if (typeof canvas?.toBlob !== "function") throw new Error("Canvas PNG encoding is not available");
1013
+ return new Promise((resolve, reject) => {
1014
+ let settled = false;
1015
+ const finish = (callback, value) => {
1016
+ if (settled) return;
1017
+ settled = true;
1018
+ callback(value);
1174
1019
  };
1175
- };
1176
- const load = async () => {
1177
- const current = ++generation;
1178
- updating = false;
1179
- pendingPatch = void 0;
1180
- fallbackStatus = "loading";
1181
- fallback = void 0;
1182
- error = false;
1183
- publish();
1184
- try {
1185
- const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/status", {}));
1186
- if (current !== generation || disposed) return;
1187
- if (nativeSnapshot().status === "ready") {
1188
- if (!modelRefreshStarted) {
1189
- contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1190
- verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1191
- }
1192
- } else acceptFallback(value);
1193
- publish();
1194
- } catch {
1195
- if (current !== generation || disposed || nativeSnapshot().status === "ready") return;
1196
- fallbackStatus = "unavailable";
1197
- publish();
1198
- }
1199
- };
1200
- const refreshModels = async () => {
1201
- const current = ++modelRefreshGeneration;
1202
- modelRefreshStarted = true;
1203
- const hadError = modelError;
1204
- modelError = false;
1205
- if (hadError) publish();
1206
- try {
1207
- const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/models", {}));
1208
- if (disposed || current !== modelRefreshGeneration) return false;
1209
- const nextContextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1210
- const nextVerbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1211
- const changed = !sameModels(contextModels, nextContextModels) || !sameModels(verbosityModels, nextVerbosityModels);
1212
- if (changed) {
1213
- contextModels = nextContextModels;
1214
- verbosityModels = nextVerbosityModels;
1215
- publish();
1216
- }
1217
- return changed;
1218
- } catch {
1219
- if (disposed || current !== modelRefreshGeneration) return false;
1220
- if (!modelError) {
1221
- modelError = true;
1222
- publish();
1223
- }
1224
- return false;
1225
- }
1226
- };
1227
- const set = async (patch) => {
1228
- if (disposed || snapshot.status !== "ready" || snapshot.writable !== true) return;
1229
- const current = ++generation;
1230
- const entries = Object.entries(patch);
1231
- updating = true;
1232
- pendingPatch = patch;
1233
- error = false;
1234
- failedPatch = void 0;
1235
- publish();
1236
1020
  try {
1237
- if (nativeSnapshot().status === "ready") {
1238
- for (const [field, value] of entries) {
1239
- if (current !== generation) return;
1240
- await scope.set(field, value);
1241
- }
1242
- if (current !== generation) return;
1243
- const accepted = nativeSnapshot().value;
1244
- error = entries.some(([field, value]) => accepted?.[field] !== value);
1245
- pendingPatch = void 0;
1246
- } else {
1247
- const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/update", patch));
1248
- if (current !== generation) return;
1249
- acceptFallback(value);
1250
- pendingPatch = void 0;
1251
- }
1252
- } catch {
1253
- if (current === generation) {
1254
- pendingPatch = void 0;
1255
- error = true;
1256
- failedPatch = patch;
1257
- }
1258
- } finally {
1259
- if (current === generation) {
1260
- updating = false;
1261
- publish();
1262
- }
1021
+ canvas.toBlob((value) => {
1022
+ if (value === null || value === void 0) finish(reject, /* @__PURE__ */ new Error("Canvas failed to encode the annotation reference as PNG"));
1023
+ else finish(resolve, value);
1024
+ }, "image/png");
1025
+ } catch (error) {
1026
+ finish(reject, error);
1263
1027
  }
1264
- };
1265
- return {
1266
- getSnapshot: () => snapshot,
1267
- subscribe: (listener) => {
1268
- listeners.add(listener);
1269
- return () => listeners.delete(listener);
1270
- },
1271
- load,
1272
- set,
1273
- retry: () => failedPatch === void 0 ? load() : set(failedPatch),
1274
- refreshModels,
1275
- dispose: () => {
1276
- disposed = true;
1277
- generation += 1;
1278
- modelRefreshGeneration += 1;
1279
- disposeScope();
1280
- }
1281
- };
1282
- }
1283
- //#endregion
1284
- //#region src/account-status-controller.js
1285
- const CHANNEL$1 = "/codex-subscription";
1286
- const DEFAULT_TIMEOUT_MS = 1e4;
1287
- const STATUS_ERROR_CODES = /* @__PURE__ */ new Set([
1288
- "credential-unavailable",
1289
- "credential-malformed",
1290
- "transport",
1291
- "timeout",
1292
- "unknown"
1293
- ]);
1294
- const STATUS_ERROR_MESSAGES = /* @__PURE__ */ new Map([
1295
- ["Codex account credentials are unavailable", "credential-unavailable"],
1296
- ["Codex account credentials are malformed", "credential-malformed"],
1297
- ["Codex account status service is unavailable", "transport"],
1298
- ["Could not read Codex account status", "unknown"]
1299
- ]);
1300
- const TIMEOUT_CODES = /* @__PURE__ */ new Set([
1301
- "TIMEOUT",
1302
- "ETIMEDOUT",
1303
- "ERR_TIMEOUT",
1304
- "UND_ERR_CONNECT_TIMEOUT"
1305
- ]);
1306
- const TRANSPORT_CODES = /* @__PURE__ */ new Set([
1307
- "ECONNRESET",
1308
- "ECONNREFUSED",
1309
- "ENOTFOUND",
1310
- "EAI_AGAIN",
1311
- "NETWORK",
1312
- "NETWORK_ERROR",
1313
- "TRANSPORT",
1314
- "CONNECTION_CLOSED",
1315
- "DISCONNECTED"
1316
- ]);
1317
- const asCode = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
1318
- function rpcError(response) {
1319
- const code = asCode(response?.error?.code);
1320
- const message = typeof response?.error?.message === "string" ? response.error.message : "";
1321
- const error = /* @__PURE__ */ new Error("Codex account status request failed");
1322
- error.code = code === "internal" && STATUS_ERROR_MESSAGES.has(message) ? STATUS_ERROR_MESSAGES.get(message) : "unknown";
1323
- return error;
1324
- }
1325
- function timeoutError() {
1326
- const error = /* @__PURE__ */ new Error("Codex account status request timed out");
1327
- error.code = "timeout";
1328
- error.name = "TimeoutError";
1329
- return error;
1330
- }
1331
- function classifyAccountStatusError(error) {
1332
- const code = typeof error?.code === "string" ? error.code.trim().toUpperCase() : "";
1333
- if (code === "TIMEOUT" || TIMEOUT_CODES.has(code) || error?.name === "TimeoutError") return "timeout";
1334
- if (STATUS_ERROR_CODES.has(asCode(error?.code))) return asCode(error.code);
1335
- if (TRANSPORT_CODES.has(code) || error?.name === "NetworkError") return "transport";
1336
- return "unknown";
1337
- }
1338
- function publicAccountStatusError(error) {
1339
- return Object.freeze({ code: classifyAccountStatusError(error) });
1340
- }
1341
- /** Own the account-status request lifecycle independently from account actions. */
1342
- function createAccountStatusController(rpc, options = {}) {
1343
- const request = options.request ?? (() => rpc.call(CHANNEL$1, "status", {}));
1344
- const scheduleTimeout = options.setTimeout ?? setTimeout;
1345
- const cancelTimeout = options.clearTimeout ?? clearTimeout;
1346
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1347
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Account status timeout must be positive");
1348
- let snapshot = Object.freeze({
1349
- status: "loading",
1350
- account: void 0,
1351
- error: void 0,
1352
- retrying: false
1353
- });
1354
- let generation = 0;
1355
- let active;
1356
- let disposed = false;
1357
- const listeners = /* @__PURE__ */ new Set();
1358
- const publish = (next) => {
1359
- snapshot = Object.freeze(next);
1360
- for (const listener of [...listeners]) listener();
1361
- };
1362
- const load = () => {
1363
- if (disposed) return Promise.resolve(void 0);
1364
- if (active !== void 0) return active.promise;
1365
- const id = ++generation;
1366
- const retrying = snapshot.error !== void 0;
1367
- publish({
1368
- status: retrying ? "error" : "loading",
1369
- account: retrying ? snapshot.account : void 0,
1370
- error: retrying ? snapshot.error : void 0,
1371
- retrying: true
1372
- });
1373
- const controller = new AbortController();
1374
- let timer;
1375
- let onAbort;
1376
- const cancelled = new Promise((resolve, reject) => {
1377
- onAbort = () => reject(controller.signal.reason);
1378
- controller.signal.addEventListener("abort", onAbort, { once: true });
1379
- if (controller.signal.aborted) onAbort();
1380
- });
1381
- const timeout = new Promise((resolve, reject) => {
1382
- timer = scheduleTimeout(() => {
1383
- const error = timeoutError();
1384
- controller.abort(error);
1385
- reject(error);
1386
- }, timeoutMs);
1387
- });
1388
- const work = Promise.resolve().then(() => request(controller.signal)).then((response) => {
1389
- if (!response?.ok) throw rpcError(response);
1390
- return response.value;
1391
- });
1392
- const promise = Promise.race([
1393
- work,
1394
- timeout,
1395
- cancelled
1396
- ]).then((account) => {
1397
- if (disposed || id !== generation || controller.signal.aborted) return void 0;
1398
- if (account === null || typeof account !== "object" || Array.isArray(account) || typeof account.authenticated !== "boolean") throw new Error("Invalid account status");
1399
- publish({
1400
- status: "ready",
1401
- account,
1402
- error: void 0,
1403
- retrying: false
1404
- });
1405
- return account;
1406
- }).catch((error) => {
1407
- if (disposed || id !== generation || controller.signal.aborted && error?.code !== "timeout") return void 0;
1408
- publish({
1409
- status: "error",
1410
- account: void 0,
1411
- error: publicAccountStatusError(error),
1412
- retrying: false
1413
- });
1414
- }).finally(() => {
1415
- cancelTimeout(timer);
1416
- controller.signal.removeEventListener("abort", onAbort);
1417
- if (active?.id === id) active = void 0;
1418
- });
1419
- active = {
1420
- id,
1421
- controller,
1422
- promise
1423
- };
1424
- return promise;
1425
- };
1426
- const acceptAccount = (account) => {
1427
- if (disposed) return false;
1428
- generation += 1;
1429
- active?.controller.abort(/* @__PURE__ */ new Error("Account status superseded by an account action"));
1430
- active = void 0;
1431
- publish({
1432
- status: "ready",
1433
- account,
1434
- error: void 0,
1435
- retrying: false
1436
- });
1437
- return true;
1438
- };
1439
- const reload = () => {
1440
- if (disposed) return Promise.resolve(void 0);
1441
- if (active !== void 0) {
1442
- generation += 1;
1443
- active.controller.abort(/* @__PURE__ */ new Error("Account status reload superseded the previous request"));
1444
- active = void 0;
1445
- }
1446
- return load();
1447
- };
1448
- const dispose = () => {
1449
- if (disposed) return;
1450
- disposed = true;
1451
- generation += 1;
1452
- active?.controller.abort(/* @__PURE__ */ new Error("Account status controller disposed"));
1453
- active = void 0;
1454
- listeners.clear();
1455
- };
1456
- return Object.freeze({
1457
- getSnapshot: () => snapshot,
1458
- subscribe(listener) {
1459
- listeners.add(listener);
1460
- return () => listeners.delete(listener);
1461
- },
1462
- load,
1463
- retry: load,
1464
- reload,
1465
- acceptAccount,
1466
- dispose
1467
1028
  });
1468
1029
  }
1469
- //#endregion
1470
- //#region src/context-draft-state.js
1471
- const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value ?? {}, key);
1472
1030
  /**
1473
- * Reconcile saved context values with the inputs currently shown in Settings.
1474
- * A draft survives a catalog refresh while its saved value is unchanged. New
1475
- * rows and rows whose saved value changed start from the new saved value.
1031
+ * Create the second image sent with an annotated edit. It contains the clean
1032
+ * source plus numbered pins, with no note text. `options` is intentionally
1033
+ * injectable so the rendering path can be exercised without a browser:
1034
+ * `{ createImageBitmap, createCanvas }`.
1476
1035
  */
1477
- function reconcileContextDrafts({ modelRows, drafts, previousSavedValues, savedValues }) {
1478
- const next = {};
1479
- for (const model of modelRows) {
1480
- const key = model.key;
1481
- const saved = String(savedValues?.[key] ?? "");
1482
- next[key] = hasOwn(previousSavedValues, key) && previousSavedValues[key] === saved && hasOwn(drafts, key) ? drafts[key] : saved;
1036
+ async function createAnnotatedImageReference(blob, annotations, options = {}) {
1037
+ const normalized = normalizeImageEditAnnotations(annotations);
1038
+ const createImageBitmap = getBitmapFactory(options);
1039
+ const createCanvas = getCanvasFactory(options);
1040
+ const bitmap = await createImageBitmap(blob);
1041
+ try {
1042
+ const width = bitmap?.width;
1043
+ const height = bitmap?.height;
1044
+ if (!finiteDimension(width) || !finiteDimension(height)) throw new Error("The source image has invalid dimensions");
1045
+ if (Math.min(width, height) < 64) throw new Error("The source image is too small for readable annotation pins");
1046
+ const canvas = await createCanvas(width, height);
1047
+ if (canvas === null || canvas === void 0) throw new Error("Canvas factory returned no canvas");
1048
+ canvas.width = width;
1049
+ canvas.height = height;
1050
+ const context = canvas.getContext?.("2d");
1051
+ if (context === null || context === void 0) throw new Error("Canvas 2D context is not available");
1052
+ context.clearRect?.(0, 0, width, height);
1053
+ context.drawImage?.(bitmap, 0, 0, width, height);
1054
+ if (typeof context.drawImage !== "function") throw new Error("Canvas 2D context cannot draw the source image");
1055
+ for (const annotation of normalized) drawPin(context, annotation.number, annotation.x, annotation.y, width, height);
1056
+ return await encodePng(canvas);
1057
+ } finally {
1058
+ if (typeof bitmap?.close === "function") bitmap.close();
1483
1059
  }
1484
- return next;
1485
1060
  }
1486
1061
  //#endregion
1487
- //#region src/client.jsx
1488
- const inject = [
1489
- "slots",
1490
- "locale",
1491
- "connection",
1492
- "remote",
1493
- "settingsScope",
1494
- "modelDirectories",
1495
- "conversation",
1496
- "uiConversation",
1497
- "sessions"
1498
- ];
1499
- const NS = "settings.codexSubscription";
1500
- const CHANNEL = "/codex-subscription";
1501
- const SUPPORT_ISSUE_URL = "https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml";
1502
- const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
1503
- const QUICK_QUOTA_REFRESH_MS = 6e4;
1504
- const zh = {
1505
- imageEditLocation: "位置",
1506
- imageEditReferenceGuide: "本次编辑的干净源图为「{sourceName}」,编号定位参考图为「{referenceName}」。坐标以图片左上角为原点,x 向右、y 向下,百分比相对于整张图片。请查看这两张图片,将它们同时作为编辑工具的参考图,并在工具提示词中完整保留下方编号、位置和修改要求。只修改源图中对应位置的内容;定位参考图上的编号、圆点和引线仅用于定位,不得绘入最终结果。若无法读取两张图片或确定位置,请说明问题,不要猜测或忽略标注。",
1507
- nav: "Codex 订阅",
1508
- title: "Codex 订阅",
1509
- connected: "已登录",
1510
- disconnected: "未登录",
1511
- accountLoading: "正在读取账户状态…",
1512
- browserLogin: "浏览器登录",
1513
- deviceLogin: "设备代码登录",
1514
- logout: "退出登录",
1515
- addAccount: "添加账号",
1516
- switchAccount: "切换",
1517
- removeAccount: "移除",
1518
- removeConfirm: "确认移除",
1519
- removeCancel: "保留",
1520
- signOutAll: "退出全部账号",
1521
- cancel: "取消",
1522
- submit: "提交授权码",
1523
- openLogin: "打开登录页",
1524
- manualCode: "若浏览器回调没有自动完成,请粘贴授权码或完整重定向地址。",
1525
- deviceHint: "在登录页输入此设备代码:",
1526
- waiting: "正在等待登录完成…",
1527
- failed: "登录失败,请重试。",
1528
- loadFailed: "无法读取账户状态。",
1529
- accountRetry: "重试",
1530
- accountRetrying: "正在重试账户状态…",
1531
- accountCredentialUnavailable: "登录凭据暂时不可用。请重试;不会删除已保存的登录信息。",
1532
- accountCredentialMalformed: "登录凭据格式异常,无法读取账户状态。重试不会删除已保存的登录信息。",
1533
- accountStatusTimeout: "读取账户状态超时,请重试。",
1534
- accountStatusTransport: "无法连接账户服务,请检查连接后重试。",
1535
- accountStatusUnknown: "无法读取账户状态,请重试。",
1536
- diagnostics: "支持诊断",
1537
- diagnosticsLoad: "生成诊断",
1538
- diagnosticsLoading: "生成中…",
1539
- diagnosticsCopy: "复制诊断",
1540
- diagnosticsCopied: "已复制",
1541
- diagnosticsFailed: "无法生成诊断信息。",
1542
- feedbackOpen: "反馈问题",
1543
- showEmail: "显示完整邮箱",
1544
- hideEmail: "隐藏邮箱",
1545
- emailUnavailable: "邮箱不可用",
1546
- searchTitle: "搜索来源",
1547
- searchScope: "自动按当前会话模型分流;手动选择会覆盖所有模型和会话。",
1548
- searchAuto: "自动",
1549
- searchAutoHint: "Codex 模型用订阅搜索,其他模型用 DSH",
1550
- searchDsh: "DSH 默认",
1551
- searchDshHint: "所有模型使用 DSH 当前搜索服务",
1552
- searchCodex: "Codex 订阅",
1553
- searchCodexHint: "所有模型通过已登录的 ChatGPT 订阅搜索",
1554
- preferenceFailed: "设置未保存。",
1555
- preferenceRetry: "重试",
1556
- usage: "订阅额度",
1557
- refresh: "刷新",
1558
- refreshing: "刷新中…",
1559
- noUsage: "登录后可读取 ChatGPT 返回的额度窗口。",
1560
- usageLoading: "正在读取额度…",
1561
- usageEmpty: "当前账户没有返回可显示的额度窗口。请稍后刷新;这不代表额度为零。",
1562
- usageUpdated: "更新于 {value}",
1563
- remaining: "剩余 {value}%",
1564
- windowFiveHours: "5 小时额度",
1565
- windowDaily: "每日额度",
1566
- windowWeekly: "每周额度",
1567
- windowMonthly: "每月额度",
1568
- windowAnnual: "年度额度",
1569
- windowHours: "{value} 小时额度",
1570
- windowDays: "{value} 天额度",
1571
- resets: "重置于 {value}",
1572
- resetUnknown: "重置时间未提供",
1573
- creditsBalance: "额外 Credits 余额",
1574
- creditsUnit: "credits",
1575
- unlimited: "不限额",
1576
- monthlyCreditLimit: "Credits 月度消费上限",
1577
- resetCredits: "额度重置",
1578
- resetCreditDefaultName: "额度重置",
1579
- resetUse: "使用",
1580
- resetPreparing: "准备中…",
1581
- resetConfirmTitle: "确认使用额度重置",
1582
- resetWarning: "执行后会消耗 1 次,且无法撤销。",
1583
- resetEarlyWarning: "当前额度未用尽,服务可能不执行重置。",
1584
- resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
1585
- resetCreditExpires: "到期:{value}",
1586
- resetCreditExpiryUnknown: "到期时间未提供",
1587
- resetCreditExpiryLoading: "正在读取到期时间…",
1588
- resetCreditExpiryFailed: "无法读取到期时间",
1589
- resetWait: "请等待 {count} 秒",
1590
- resetFinal: "确认使用",
1591
- resetUsing: "使用中…",
1592
- resetSuccess: "额度重置已完成。",
1593
- resetNothing: "当前没有可重置的额度,未消耗新的重置次数。",
1594
- resetNoCredit: "没有可用的额度重置。",
1595
- resetAlready: "这次重置请求已处理。",
1596
- resetFailed: "无法使用额度重置。",
1597
- resetRenewLogin: "登录状态已失效,请重新登录。",
1598
- resetExpired: "本次确认已失效,请重新开始。",
1599
- resetInProgress: "额度重置正在处理中。",
1600
- resetTooEarly: "请等待冷静期结束后再确认。",
1601
- resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
1602
- resetAccountChanged: "登录账号已变更,请重新开始。",
1603
- resetUncertain: "服务端返回结果不确定。请再次确认,插件会复用同一个请求,不会另外发起一次重置。",
1604
- creditsNote: "额外 Credits、消费上限、重置次数分别显示。",
1605
- creditsUsed: "已用 {used} / {limit} credits",
1606
- spendReached: "Credits 月度消费上限已用尽。",
1607
- unavailable: "暂无数据",
1608
- quickQuotaSetting: "输入框额度",
1609
- quickQuotaOff: "关闭",
1610
- quickQuotaPercent: "百分比",
1611
- quickQuotaBar: "进度条",
1612
- quickQuotaForecast: "续航预测",
1613
- quickQuotaBeta: "Beta",
1614
- quickQuotaForecastHint: "按消耗速度自适应校准;高消耗通常 5–10 分钟可估算,低消耗会显示用量稳定。进度会在本机保留。",
1615
- contextTitle: "上下文窗口",
1616
- contextStandard: "标准",
1617
- contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
1618
- contextExtended: "扩展",
1619
- contextExtendedHint: "按模型使用已审核的扩展预算(Astra:872K);实际可用性由服务端决定。",
1620
- contextCustom: "自定义",
1621
- contextCustomHint: "输入完整 Token 数值;较低数值会让官方 Agent 预设更早压缩上下文。",
1622
- contextTokens: "Token 上限",
1623
- contextFixed: "固定 {value}",
1624
- contextMaximum: "范围 128000–{value}",
1625
- quickQuotaStatus: "Codex 剩余额度 {value}%",
1626
- quickQuotaForecastStatus: "Codex 剩余额度 {value}%,按当前速度预计可用 {duration}",
1627
- quickQuotaForecastCalibrating: "校准中",
1628
- quickQuotaForecastCalibratingStatus: "Codex 剩余额度 {value}%,续航预测正在校准",
1629
- quickQuotaForecastIdle: "用量稳定",
1630
- quickQuotaForecastIdleStatus: "Codex 剩余额度 {value}%,当前没有可测量的消耗速度",
1631
- quickQuotaForecastUntilReset: "够用到重置",
1632
- quickQuotaForecastUntilResetStatus: "Codex 剩余额度 {value}%,按当前速度足够用到重置",
1633
- quotaForecast: "按当前速度 {symbol}{duration}",
1634
- quotaForecastCalibrating: "续航正在校准",
1635
- quotaForecastIdle: "当前用量稳定",
1636
- quotaForecastUntilReset: "按当前速度足够用到重置",
1637
- runwayDaysHours: "{days} 天 {hours} 小时",
1638
- runwayDays: "{days} 天",
1639
- runwayHours: "{hours} 小时",
1640
- runwayMinutes: "{minutes} 分钟",
1641
- speedTitle: "速度",
1642
- speedStandard: "标准",
1643
- speedStandardHint: "标准速度",
1644
- speedFast: "高速",
1645
- speedFastHint: "1.5 倍,消耗更多 Credits",
1646
- verbosityTitle: "输出详略",
1647
- verbosityDefault: "模型默认",
1648
- verbosityDefaultHint: "使用官方模型目录推荐值",
1649
- verbosityLow: "简洁",
1650
- verbosityLowHint: "更短、更直接",
1651
- verbosityMedium: "均衡",
1652
- verbosityMediumHint: "兼顾完整性与长度",
1653
- verbosityHigh: "详细",
1654
- verbosityHighHint: "更充分的说明与结构",
1655
- modelMenuAria: "模型、推理等级、速度与输出详略",
1656
- modelLabel: "模型",
1657
- effortLabel: "推理等级",
1658
- providerDefault: "Default",
1659
- selectModel: "选择模型",
1660
- modelsLoading: "正在读取模型…",
1661
- modelsEmpty: "没有可用模型。",
1662
- effortsEmpty: "当前模型未提供推理等级。",
1663
- modelRetry: "重试",
1664
- modelDirectoryFailed: "模型目录加载失败,请重试。",
1665
- modelFailed: "模型目录加载失败:{value}",
1666
- groupFailed: "{name}:{value}",
1667
- imageGenerate: "生成图片",
1668
- imageBeta: "Beta",
1669
- imageGenerating: "正在生成…",
1670
- imageGenerated: "已生成",
1671
- imageFailed: "生成失败",
1672
- imageLabel: "生成的图片",
1673
- imageOpen: "查看图片",
1674
- imageOpenNamed: "查看 {value}",
1675
- imageLoading: "正在加载图片…",
1676
- imageLoadFailed: "图片加载失败,点击重试",
1677
- imagePreview: "图片预览",
1678
- imagePreviewShort: "预览图",
1679
- imageClosePreview: "关闭预览",
1680
- imageDownload: "下载",
1681
- imageDownloadPreparing: "正在准备原图…",
1682
- imageDownloadFailed: "下载失败,重试",
1683
- imageZoomOut: "缩小",
1684
- imageZoomIn: "放大",
1685
- imageFit: "适合窗口",
1686
- imageAnnotate: "标注部位",
1687
- imageAnnotateCancel: "取消标注",
1688
- imageAnnotateHint: "点击图片添加编号标注",
1689
- imageAnnotation: "标注 {value}",
1690
- imageAnnotationPlaceholder: "描述这个部位要修改什么",
1691
- imageRegions: "区域备注",
1692
- imageCopyNotes: "复制备注",
1693
- imageCopied: "已复制",
1694
- imagePrevious: "上一张图片",
1695
- imageNext: "下一张图片",
1696
- imageZoomHint: "滚轮缩放 · 拖动查看 · 双击切换原始大小",
1697
- imageActual: "原始大小",
1698
- imageEditPrompt: "描述你想怎样修改这张图",
1699
- imageEditDefault: "编辑这张图片。",
1700
- imageRegionNotes: "部位修改:",
1701
- imageEdit: "在输入框中继续编辑",
1702
- imageEditPreparing: "正在添加到输入框…",
1703
- imageEditFailed: "回填失败:请填写每个标记的备注,并确认输入框可接收图片后重试。",
1704
- imageRemoveAnnotation: "删除标注"
1062
+ //#region src/subscription-image-viewer-styles.js
1063
+ const SUBSCRIPTION_IMAGE_VIEWER_CSS = String.raw`
1064
+ .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)}
1065
+ .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}
1066
+ .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)}
1067
+ .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}
1068
+ .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)}
1069
+ .dcsiv-workspace{position:absolute;inset:0;display:grid;min-width:0;min-height:0;padding:68px 24px 72px;box-sizing:border-box}
1070
+ .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}
1071
+ .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}
1072
+ .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)}
1073
+ .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}
1074
+ .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)}
1075
+ .dcsiv-hint{display:none}
1076
+ .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)}
1077
+ @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}}
1078
+ @media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}}
1079
+ `;
1080
+ //#endregion
1081
+ //#region src/subscription-image-viewer.jsx
1082
+ const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
1083
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
1084
+ 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`;
1085
+ const downloadName = (name) => {
1086
+ const cleaned = String(name || "image.png").replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
1087
+ return cleaned === "" ? "image.png" : cleaned;
1705
1088
  };
1706
- const en = {
1707
- imageEditLocation: "Location",
1708
- 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.",
1709
- nav: "Codex",
1710
- title: "Codex subscription",
1711
- connected: "Signed in",
1712
- disconnected: "Not signed in",
1713
- accountLoading: "Reading account status…",
1714
- browserLogin: "Browser sign-in",
1715
- deviceLogin: "Device-code sign-in",
1716
- logout: "Sign out",
1717
- addAccount: "Add account",
1718
- switchAccount: "Switch",
1719
- removeAccount: "Remove",
1720
- removeConfirm: "Confirm remove",
1721
- removeCancel: "Keep",
1722
- signOutAll: "Sign out all",
1723
- cancel: "Cancel",
1724
- submit: "Submit authorization code",
1725
- openLogin: "Open sign-in page",
1726
- manualCode: "If the browser callback did not finish automatically, paste the code or full redirect URL.",
1727
- deviceHint: "Enter this device code on the sign-in page:",
1728
- waiting: "Waiting for sign-in to finish…",
1729
- failed: "Sign-in failed. Try again.",
1730
- loadFailed: "Could not read account status.",
1731
- accountRetry: "Retry",
1732
- accountRetrying: "Retrying account status…",
1733
- accountCredentialUnavailable: "The saved sign-in credentials are temporarily unavailable. Retry; saved sign-in information will not be deleted.",
1734
- accountCredentialMalformed: "The saved sign-in credentials are malformed, so account status cannot be read. Retrying will not delete saved sign-in information.",
1735
- accountStatusTimeout: "Reading account status timed out. Retry.",
1736
- accountStatusTransport: "The account service is unavailable. Check the connection and retry.",
1737
- accountStatusUnknown: "Could not read account status. Retry.",
1738
- diagnostics: "Support diagnostics",
1739
- diagnosticsLoad: "Create report",
1740
- diagnosticsLoading: "Creating…",
1741
- diagnosticsCopy: "Copy report",
1742
- diagnosticsCopied: "Copied",
1743
- diagnosticsFailed: "Could not create diagnostics.",
1744
- feedbackOpen: "Report a problem",
1745
- showEmail: "Show full email",
1746
- hideEmail: "Hide email",
1747
- emailUnavailable: "Email unavailable",
1748
- searchTitle: "Search source",
1749
- searchScope: "Auto follows the current session model; an explicit choice overrides every model and session.",
1750
- searchAuto: "Auto",
1751
- searchAutoHint: "Codex models use subscription search; other models use DSH",
1752
- searchDsh: "DSH default",
1753
- searchDshHint: "Use DSH's current search service for every model",
1754
- searchCodex: "Codex subscription",
1755
- searchCodexHint: "Search through the signed-in ChatGPT subscription for every model",
1756
- preferenceFailed: "The setting was not saved.",
1757
- preferenceRetry: "Retry",
1758
- usage: "Subscription quota",
1759
- refresh: "Refresh",
1760
- refreshing: "Refreshing…",
1761
- noUsage: "Sign in to read quota windows reported by ChatGPT.",
1762
- usageLoading: "Reading quota…",
1763
- usageEmpty: "This account returned no displayable quota windows. Refresh later; this does not mean zero quota.",
1764
- usageUpdated: "Updated {value}",
1765
- remaining: "{value}% remaining",
1766
- windowFiveHours: "5-hour quota",
1767
- windowDaily: "Daily quota",
1768
- windowWeekly: "Weekly quota",
1769
- windowMonthly: "Monthly quota",
1770
- windowAnnual: "Annual quota",
1771
- windowHours: "{value}-hour quota",
1772
- windowDays: "{value}-day quota",
1773
- resets: "Resets {value}",
1774
- resetUnknown: "Reset time not provided",
1775
- creditsBalance: "Extra Credits balance",
1776
- creditsUnit: "credits",
1777
- unlimited: "Unlimited",
1778
- monthlyCreditLimit: "Monthly Credits spending cap",
1779
- resetCredits: "Quota resets",
1780
- resetCreditDefaultName: "Quota reset",
1781
- resetUse: "Use",
1782
- resetPreparing: "Preparing…",
1783
- resetConfirmTitle: "Confirm quota reset",
1784
- resetWarning: "This consumes one reset and cannot be undone.",
1785
- resetEarlyWarning: "Quota remains. The service may decline the reset.",
1786
- resetAcknowledge: "I understand this may consume one reset now",
1787
- resetCreditExpires: "Expires {value}",
1788
- resetCreditExpiryUnknown: "Expiration time not provided",
1789
- resetCreditExpiryLoading: "Reading expiration…",
1790
- resetCreditExpiryFailed: "Could not read expiration",
1791
- resetWait: "Wait {count} seconds",
1792
- resetFinal: "Confirm use",
1793
- resetUsing: "Using…",
1794
- resetSuccess: "Quota reset completed.",
1795
- resetNothing: "There is currently nothing to reset; no new reset was consumed.",
1796
- resetNoCredit: "No quota reset is available.",
1797
- resetAlready: "This reset request was already processed.",
1798
- resetFailed: "Could not use the quota reset.",
1799
- resetRenewLogin: "Your sign-in expired. Sign in again.",
1800
- resetExpired: "This confirmation expired. Start again.",
1801
- resetInProgress: "A quota reset is already in progress.",
1802
- resetTooEarly: "Wait for the cooldown before confirming.",
1803
- resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
1804
- resetAccountChanged: "The signed-in account changed. Start again.",
1805
- resetUncertain: "The server result is uncertain. Confirm again to check the same request; the plugin will not start a separate reset.",
1806
- creditsNote: "Extra Credits, spending caps, and resets are separate items.",
1807
- creditsUsed: "{used} / {limit} credits used",
1808
- spendReached: "The monthly Credits spending cap has been reached.",
1809
- unavailable: "No data yet",
1810
- quickQuotaSetting: "Composer quota",
1811
- quickQuotaOff: "Off",
1812
- quickQuotaPercent: "Percent",
1813
- quickQuotaBar: "Progress bar",
1814
- quickQuotaForecast: "Runway",
1815
- quickQuotaBeta: "Beta",
1816
- quickQuotaForecastHint: "Calibrates to actual consumption: high use is usually estimated in 5–10 minutes, while low use is shown as stable. Progress is kept locally.",
1817
- contextTitle: "Context window",
1818
- contextStandard: "Standard",
1819
- contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
1820
- contextExtended: "Extended",
1821
- contextExtendedHint: "Uses each model's audited extended budget (Astra: 872K); availability depends on the service.",
1822
- contextCustom: "Custom",
1823
- contextCustomHint: "Enter the full token count; lower values make official agent presets compact sooner.",
1824
- contextTokens: "Token limit",
1825
- contextFixed: "Fixed {value}",
1826
- contextMaximum: "128000–{value}",
1827
- quickQuotaStatus: "Codex quota: {value}% remaining",
1828
- quickQuotaForecastStatus: "Codex quota: {value}% remaining; about {duration} at the current pace",
1829
- quickQuotaForecastCalibrating: "Calibrating",
1830
- quickQuotaForecastCalibratingStatus: "Codex quota: {value}% remaining; runway is calibrating",
1831
- quickQuotaForecastIdle: "Usage stable",
1832
- quickQuotaForecastIdleStatus: "Codex quota: {value}% remaining; no measurable consumption pace",
1833
- quickQuotaForecastUntilReset: "Enough until reset",
1834
- quickQuotaForecastUntilResetStatus: "Codex quota: {value}% remaining; enough until reset at the current pace",
1835
- quotaForecast: "At current pace {symbol}{duration}",
1836
- quotaForecastCalibrating: "Runway calibrating",
1837
- quotaForecastIdle: "Usage currently stable",
1838
- quotaForecastUntilReset: "Enough until reset at current pace",
1839
- runwayDaysHours: "{days}d {hours}h",
1840
- runwayDays: "{days}d",
1841
- runwayHours: "{hours}h",
1842
- runwayMinutes: "{minutes}m",
1843
- speedTitle: "Speed",
1844
- speedStandard: "Standard",
1845
- speedStandardHint: "Standard speed",
1846
- speedFast: "Fast",
1847
- speedFastHint: "1.5x; higher Credits use",
1848
- verbosityTitle: "Output detail",
1849
- verbosityDefault: "Model default",
1850
- verbosityDefaultHint: "Use the official model catalog recommendation",
1851
- verbosityLow: "Concise",
1852
- verbosityLowHint: "Shorter and more direct",
1853
- verbosityMedium: "Balanced",
1854
- verbosityMediumHint: "Balance completeness and length",
1855
- verbosityHigh: "Detailed",
1856
- verbosityHighHint: "More explanation and structure",
1857
- modelMenuAria: "Model, effort, speed, and output detail",
1858
- modelLabel: "Model",
1859
- effortLabel: "Effort",
1860
- providerDefault: "Default",
1861
- selectModel: "Select model",
1862
- modelsLoading: "Loading models…",
1863
- modelsEmpty: "No models available.",
1864
- effortsEmpty: "This model provides no reasoning effort levels.",
1865
- modelRetry: "Retry",
1866
- modelDirectoryFailed: "Could not load the model directory. Try again.",
1867
- modelFailed: "Could not load models: {value}",
1868
- groupFailed: "{name}: {value}",
1869
- imageGenerate: "Generate image",
1870
- imageBeta: "Beta",
1871
- imageGenerating: "Generating…",
1872
- imageGenerated: "Generated",
1873
- imageFailed: "Generation failed",
1874
- imageLabel: "Generated image",
1875
- imageOpen: "View image",
1876
- imageOpenNamed: "View {value}",
1877
- imageLoading: "Loading image…",
1878
- imageLoadFailed: "Image failed to load. Click to retry",
1879
- imagePreview: "Image preview",
1880
- imagePreviewShort: "Preview",
1881
- imageClosePreview: "Close preview",
1882
- imageDownload: "Download",
1883
- imageDownloadPreparing: "Preparing original…",
1884
- imageDownloadFailed: "Download failed. Retry",
1885
- imageZoomOut: "Zoom out",
1886
- imageZoomIn: "Zoom in",
1887
- imageFit: "Fit to window",
1888
- imageAnnotate: "Annotate",
1889
- imageAnnotateCancel: "Cancel marking",
1890
- imageAnnotateHint: "Click the image to add a numbered note",
1891
- imageAnnotation: "Note {value}",
1892
- imageAnnotationPlaceholder: "Describe what should change in this area",
1893
- imageRegions: "Region notes",
1894
- imageCopyNotes: "Copy notes",
1895
- imageCopied: "Copied",
1896
- imagePrevious: "Previous image",
1897
- imageNext: "Next image",
1898
- imageZoomHint: "Wheel to zoom · drag to pan · double-click for 100%",
1899
- imageActual: "100%",
1900
- imageEditPrompt: "Describe how you want to change this image",
1901
- imageEditDefault: "Edit this image.",
1902
- imageRegionNotes: "Region changes:",
1903
- imageEdit: "Continue editing in composer",
1904
- imageEditPreparing: "Adding to composer…",
1905
- imageEditFailed: "Handoff failed. Add a note to every marker and ensure the composer accepts images, then retry.",
1906
- imageRemoveAnnotation: "Remove note"
1089
+ const noteText = (annotations, t) => annotations.map((annotation, index) => {
1090
+ return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
1091
+ }).filter((line) => !line.endsWith(": ")).join("\n");
1092
+ function ViewerAction({ action, annotations, item, service, t }) {
1093
+ const [state, setState] = (0, react.useState)("idle");
1094
+ const invoke = async () => {
1095
+ if (state === "pending") return;
1096
+ setState("pending");
1097
+ try {
1098
+ await action.onInvoke({
1099
+ annotations,
1100
+ item,
1101
+ src: item.src
1102
+ });
1103
+ setState("idle");
1104
+ if (action.closeOnSuccess) service.close();
1105
+ } catch {
1106
+ setState("failed");
1107
+ }
1108
+ };
1109
+ const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
1110
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1111
+ type: "button",
1112
+ className: "dcsiv-button",
1113
+ disabled: state === "pending",
1114
+ onClick: () => {
1115
+ invoke();
1116
+ },
1117
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1118
+ className: "dcsiv-label",
1119
+ children: label ?? t("imageEdit")
1120
+ })
1121
+ });
1122
+ }
1123
+ function ViewerDownload({ download, item, t }) {
1124
+ const [state, setState] = (0, react.useState)("idle");
1125
+ const invoke = async () => {
1126
+ if (state === "pending") return;
1127
+ setState("pending");
1128
+ try {
1129
+ await download.onInvoke({
1130
+ item,
1131
+ src: item.src
1132
+ });
1133
+ setState("idle");
1134
+ } catch {
1135
+ setState("failed");
1136
+ }
1137
+ };
1138
+ const label = state === "pending" ? download.pendingLabel ?? t("imageDownloadPreparing") : state === "failed" ? download.errorLabel ?? t("imageDownloadFailed") : t("imageDownload");
1139
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1140
+ type: "button",
1141
+ className: "dcsiv-download",
1142
+ disabled: state === "pending",
1143
+ onClick: () => {
1144
+ invoke();
1145
+ },
1146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1147
+ className: "dcsiv-label",
1148
+ children: label
1149
+ })]
1150
+ });
1151
+ }
1152
+ function SubscriptionImageViewerOverlay({ service, t }) {
1153
+ const request = (0, react.useSyncExternalStore)(service.subscribe, service.getSnapshot);
1154
+ const [index, setIndex] = (0, react.useState)(0);
1155
+ const [transform, setTransform] = (0, react.useState)({
1156
+ zoom: 1,
1157
+ x: 0,
1158
+ y: 0
1159
+ });
1160
+ const [dragging, setDragging] = (0, react.useState)(false);
1161
+ const [annotating, setAnnotating] = (0, react.useState)(false);
1162
+ const [annotationsByImage, setAnnotationsByImage] = (0, react.useState)(service.getAnnotationsSnapshot);
1163
+ const annotationsByImageRef = (0, react.useRef)(annotationsByImage);
1164
+ const [selected, setSelected] = (0, react.useState)();
1165
+ const [focusNote, setFocusNote] = (0, react.useState)();
1166
+ const [copied, setCopied] = (0, react.useState)(false);
1167
+ const rootRef = (0, react.useRef)(null);
1168
+ const stageRef = (0, react.useRef)(null);
1169
+ const surfaceRef = (0, react.useRef)(null);
1170
+ const imageRef = (0, react.useRef)(null);
1171
+ const pointersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
1172
+ const gestureRef = (0, react.useRef)();
1173
+ const transformRef = (0, react.useRef)(transform);
1174
+ transformRef.current = transform;
1175
+ annotationsByImageRef.current = annotationsByImage;
1176
+ (0, react.useEffect)(() => {
1177
+ if (request === void 0) return;
1178
+ setIndex(request.index);
1179
+ setTransform({
1180
+ zoom: 1,
1181
+ x: 0,
1182
+ y: 0
1183
+ });
1184
+ setDragging(false);
1185
+ setAnnotating(false);
1186
+ setSelected(void 0);
1187
+ setCopied(false);
1188
+ }, [request?.revision]);
1189
+ const item = request?.items[index];
1190
+ const annotations = item === void 0 ? [] : annotationsByImage[item.id] ?? [];
1191
+ const setAnnotations = (0, react.useCallback)((update) => {
1192
+ if (item === void 0) return;
1193
+ const previous = annotationsByImageRef.current[item.id] ?? [];
1194
+ const next = typeof update === "function" ? update(previous) : update;
1195
+ const snapshot = {
1196
+ ...annotationsByImageRef.current,
1197
+ [item.id]: next
1198
+ };
1199
+ annotationsByImageRef.current = snapshot;
1200
+ service.setAnnotations(item.id, next);
1201
+ setAnnotationsByImage(snapshot);
1202
+ }, [item?.id, service]);
1203
+ const boundedPan = (0, react.useCallback)((zoom, x, y) => {
1204
+ const stage = stageRef.current;
1205
+ const surface = surfaceRef.current;
1206
+ if (stage === null || surface === null || zoom <= 1) return {
1207
+ x: 0,
1208
+ y: 0
1209
+ };
1210
+ const limitX = Math.max(0, (surface.offsetWidth * zoom - stage.clientWidth) / 2) + 28;
1211
+ const limitY = Math.max(0, (surface.offsetHeight * zoom - stage.clientHeight) / 2) + 28;
1212
+ return {
1213
+ x: clamp(x, -limitX, limitX),
1214
+ y: clamp(y, -limitY, limitY)
1215
+ };
1216
+ }, []);
1217
+ const setZoomAt = (0, react.useCallback)((nextZoom, clientX, clientY) => {
1218
+ const stage = stageRef.current;
1219
+ if (stage === null) return;
1220
+ setTransform((current) => {
1221
+ const next = clamp(nextZoom, .5, 8);
1222
+ const box = stage.getBoundingClientRect();
1223
+ const px = clientX - box.left - box.width / 2;
1224
+ const py = clientY - box.top - box.height / 2;
1225
+ const ratio = next / current.zoom;
1226
+ return {
1227
+ zoom: next,
1228
+ ...boundedPan(next, px - (px - current.x) * ratio, py - (py - current.y) * ratio)
1229
+ };
1230
+ });
1231
+ }, [boundedPan]);
1232
+ const fit = (0, react.useCallback)(() => {
1233
+ setTransform({
1234
+ zoom: 1,
1235
+ x: 0,
1236
+ y: 0
1237
+ });
1238
+ }, []);
1239
+ const actual = (0, react.useCallback)(() => {
1240
+ const image = imageRef.current;
1241
+ const surface = surfaceRef.current;
1242
+ if (image === null || surface === null || image.naturalWidth === 0) return;
1243
+ const zoom = clamp(image.naturalWidth / Math.max(1, surface.offsetWidth), 1, 8);
1244
+ setTransform({
1245
+ zoom,
1246
+ x: 0,
1247
+ y: 0
1248
+ });
1249
+ }, []);
1250
+ (0, react.useEffect)(() => {
1251
+ if (request === void 0) return void 0;
1252
+ const previousOverflow = document.body.style.overflow;
1253
+ document.body.style.overflow = "hidden";
1254
+ rootRef.current?.focus();
1255
+ const onKeyDown = (event) => {
1256
+ if (event.key === "Escape") {
1257
+ event.preventDefault();
1258
+ if (event.target instanceof Element && event.target.closest(".dcsiv-inline-note") !== null) {
1259
+ setSelected(void 0);
1260
+ return;
1261
+ }
1262
+ service.close();
1263
+ return;
1264
+ }
1265
+ const editing = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement;
1266
+ if (!editing && event.key === "ArrowLeft" && request.items.length > 1) {
1267
+ event.preventDefault();
1268
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
1269
+ } else if (!editing && event.key === "ArrowRight" && request.items.length > 1) {
1270
+ event.preventDefault();
1271
+ setIndex((value) => (value + 1) % request.items.length);
1272
+ } else if (!editing && (event.key === "+" || event.key === "=")) {
1273
+ event.preventDefault();
1274
+ const box = stageRef.current?.getBoundingClientRect();
1275
+ if (box) setZoomAt(transformRef.current.zoom * 1.2, box.left + box.width / 2, box.top + box.height / 2);
1276
+ } else if (!editing && event.key === "-") {
1277
+ event.preventDefault();
1278
+ const box = stageRef.current?.getBoundingClientRect();
1279
+ if (box) setZoomAt(transformRef.current.zoom / 1.2, box.left + box.width / 2, box.top + box.height / 2);
1280
+ } else if (!editing && event.key.toLowerCase() === "f") {
1281
+ event.preventDefault();
1282
+ fit();
1283
+ } else if (event.key === "Tab") {
1284
+ const controls = [...rootRef.current.querySelectorAll("button:not(:disabled),a[href],input:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])")];
1285
+ const first = controls[0];
1286
+ const last = controls.at(-1);
1287
+ if (event.shiftKey && document.activeElement === first) {
1288
+ event.preventDefault();
1289
+ last?.focus();
1290
+ } else if (!event.shiftKey && document.activeElement === last) {
1291
+ event.preventDefault();
1292
+ first?.focus();
1293
+ }
1294
+ }
1295
+ };
1296
+ document.addEventListener("keydown", onKeyDown);
1297
+ return () => {
1298
+ document.body.style.overflow = previousOverflow;
1299
+ document.removeEventListener("keydown", onKeyDown);
1300
+ };
1301
+ }, [
1302
+ request,
1303
+ service,
1304
+ fit,
1305
+ setZoomAt
1306
+ ]);
1307
+ (0, react.useEffect)(() => {
1308
+ if (focusNote === void 0) return;
1309
+ (rootRef.current?.querySelector(`[data-note-id="${CSS.escape(focusNote)}"] textarea`))?.focus();
1310
+ setFocusNote(void 0);
1311
+ }, [
1312
+ focusNote,
1313
+ selected,
1314
+ annotations.length
1315
+ ]);
1316
+ (0, react.useEffect)(() => {
1317
+ setTransform({
1318
+ zoom: 1,
1319
+ x: 0,
1320
+ y: 0
1321
+ });
1322
+ setDragging(false);
1323
+ setAnnotating(false);
1324
+ setSelected(void 0);
1325
+ }, [item?.id]);
1326
+ const onWheel = (0, react.useCallback)((event) => {
1327
+ event.preventDefault();
1328
+ setZoomAt(transformRef.current.zoom * Math.exp(-event.deltaY * .0015), event.clientX, event.clientY);
1329
+ }, [setZoomAt]);
1330
+ (0, react.useEffect)(() => {
1331
+ const stage = stageRef.current;
1332
+ if (stage === null || request === void 0) return void 0;
1333
+ stage.addEventListener("wheel", onWheel, { passive: false });
1334
+ return () => stage.removeEventListener("wheel", onWheel);
1335
+ }, [onWheel, request]);
1336
+ const onPointerDown = (event) => {
1337
+ const target = event.target;
1338
+ if (event.button !== 0 || annotating || target instanceof Element && target.closest("button,textarea,input,a,select,[contenteditable=true]") !== null) return;
1339
+ pointersRef.current.set(event.pointerId, {
1340
+ x: event.clientX,
1341
+ y: event.clientY
1342
+ });
1343
+ if (pointersRef.current.size === 2) {
1344
+ event.currentTarget.setPointerCapture(event.pointerId);
1345
+ const [a, b] = [...pointersRef.current.values()];
1346
+ gestureRef.current = {
1347
+ kind: "pinch",
1348
+ distance: Math.hypot(a.x - b.x, a.y - b.y),
1349
+ transform
1350
+ };
1351
+ } else if (!annotating && transform.zoom > 1) {
1352
+ event.currentTarget.setPointerCapture(event.pointerId);
1353
+ gestureRef.current = {
1354
+ kind: "pan",
1355
+ x: event.clientX,
1356
+ y: event.clientY,
1357
+ transform
1358
+ };
1359
+ setDragging(true);
1360
+ }
1361
+ };
1362
+ const onPointerMove = (event) => {
1363
+ if (!pointersRef.current.has(event.pointerId)) return;
1364
+ pointersRef.current.set(event.pointerId, {
1365
+ x: event.clientX,
1366
+ y: event.clientY
1367
+ });
1368
+ const gesture = gestureRef.current;
1369
+ if (gesture?.kind === "pinch" && pointersRef.current.size >= 2) {
1370
+ const [a, b] = [...pointersRef.current.values()];
1371
+ const distance = Math.max(1, Math.hypot(a.x - b.x, a.y - b.y));
1372
+ setZoomAt(gesture.transform.zoom * distance / Math.max(1, gesture.distance), (a.x + b.x) / 2, (a.y + b.y) / 2);
1373
+ } else if (gesture?.kind === "pan") {
1374
+ const pan = boundedPan(gesture.transform.zoom, gesture.transform.x + event.clientX - gesture.x, gesture.transform.y + event.clientY - gesture.y);
1375
+ setTransform({
1376
+ zoom: gesture.transform.zoom,
1377
+ ...pan
1378
+ });
1379
+ }
1380
+ };
1381
+ const endPointer = (event) => {
1382
+ pointersRef.current.delete(event.pointerId);
1383
+ if (pointersRef.current.size === 0) {
1384
+ gestureRef.current = void 0;
1385
+ setDragging(false);
1386
+ }
1387
+ };
1388
+ const addAnnotation = (event) => {
1389
+ if (!annotating || event.target.closest(".dcsiv-annotation")) return;
1390
+ const bounds = surfaceRef.current?.getBoundingClientRect();
1391
+ if (bounds === void 0) return;
1392
+ const annotation = {
1393
+ id: crypto.randomUUID(),
1394
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
1395
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1),
1396
+ note: ""
1397
+ };
1398
+ setAnnotations((current) => [...current, annotation]);
1399
+ setAnnotating(false);
1400
+ setSelected(annotation.id);
1401
+ setFocusNote(annotation.id);
1402
+ };
1403
+ const copyNotes = async () => {
1404
+ const text = noteText(annotations, t);
1405
+ if (text === "" || typeof navigator?.clipboard?.writeText !== "function") return;
1406
+ try {
1407
+ await navigator.clipboard.writeText(text);
1408
+ setCopied(true);
1409
+ window.setTimeout(() => {
1410
+ setCopied(false);
1411
+ }, 1200);
1412
+ } catch {
1413
+ setCopied(false);
1414
+ }
1415
+ };
1416
+ if (request === void 0 || item === void 0) return null;
1417
+ const meta = [item.width && item.height ? `${item.width} × ${item.height}` : void 0, bytesLabel(item.bytes)].filter(Boolean).join(" · ");
1418
+ const showCounter = request.items.length > 1;
1419
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1420
+ ref: rootRef,
1421
+ className: "dcsiv-root",
1422
+ role: "dialog",
1423
+ "aria-modal": "true",
1424
+ "aria-label": t("imagePreview"),
1425
+ tabIndex: -1,
1426
+ children: [
1427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1428
+ className: "dcsiv-title dcsiv-sr-only",
1429
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.name }), meta !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: meta }) : null]
1430
+ }),
1431
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
1432
+ className: "dcsiv-topbar",
1433
+ role: "toolbar",
1434
+ "aria-label": t("imagePreview"),
1435
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1436
+ className: "dcsiv-actions",
1437
+ children: [
1438
+ request.annotations ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1439
+ type: "button",
1440
+ className: "dcsiv-button",
1441
+ "data-active": annotating,
1442
+ "aria-label": annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
1443
+ "aria-pressed": annotating,
1444
+ onClick: () => setAnnotating((value) => !value),
1445
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1446
+ className: "dcsiv-label",
1447
+ children: annotating ? t("imageAnnotateCancel") : t("imageAnnotate")
1448
+ })]
1449
+ }) : null,
1450
+ annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1451
+ type: "button",
1452
+ className: "dcsiv-button",
1453
+ "data-active": selected !== void 0,
1454
+ onClick: () => {
1455
+ const first = annotations[0];
1456
+ setSelected((current) => current === void 0 ? first.id : void 0);
1457
+ if (selected === void 0) setFocusNote(first.id);
1458
+ },
1459
+ children: [
1460
+ annotations.length,
1461
+ " ",
1462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1463
+ className: "dcsiv-label",
1464
+ children: t("imageRegions")
1465
+ })
1466
+ ]
1467
+ }) : null,
1468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1469
+ type: "button",
1470
+ className: "dcsiv-button",
1471
+ "aria-label": t("imageFit"),
1472
+ onClick: fit,
1473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1474
+ className: "dcsiv-label",
1475
+ children: t("imageFit")
1476
+ })]
1477
+ }),
1478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1479
+ type: "button",
1480
+ className: "dcsiv-button",
1481
+ onClick: actual,
1482
+ children: t("imageActual")
1483
+ }),
1484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1485
+ className: "dcsiv-zoom",
1486
+ children: [Math.round(transform.zoom * 100), "%"]
1487
+ }),
1488
+ item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
1489
+ className: "dcsiv-download",
1490
+ href: item.src,
1491
+ download: downloadName(item.name),
1492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1493
+ className: "dcsiv-label",
1494
+ children: t("imageDownload")
1495
+ })]
1496
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerDownload, {
1497
+ download: item.download,
1498
+ item,
1499
+ t
1500
+ }),
1501
+ item.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerAction, {
1502
+ action,
1503
+ annotations,
1504
+ item,
1505
+ service,
1506
+ t
1507
+ }, action.id))
1508
+ ]
1509
+ })
1510
+ }),
1511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1512
+ type: "button",
1513
+ className: "dcsiv-close-floating",
1514
+ "aria-label": t("imageClosePreview"),
1515
+ onClick: () => service.close(),
1516
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
1517
+ }),
1518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1519
+ className: "dcsiv-workspace",
1520
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1521
+ ref: stageRef,
1522
+ className: "dcsiv-stage",
1523
+ "data-dragging": dragging,
1524
+ "data-annotating": annotating,
1525
+ onClick: (event) => {
1526
+ if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
1527
+ },
1528
+ onPointerDown,
1529
+ onPointerMove,
1530
+ onPointerUp: endPointer,
1531
+ onPointerCancel: endPointer,
1532
+ onDoubleClick: () => {
1533
+ if (transform.zoom === 1) actual();
1534
+ else fit();
1535
+ },
1536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1537
+ ref: surfaceRef,
1538
+ className: "dcsiv-surface",
1539
+ onClick: addAnnotation,
1540
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
1541
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1542
+ ref: imageRef,
1543
+ className: "dcsiv-image",
1544
+ src: item.src,
1545
+ alt: item.name,
1546
+ draggable: "false"
1547
+ }), annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1548
+ className: "dcsiv-annotation",
1549
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
1550
+ "data-y": annotation.y < .28 ? "down" : "up",
1551
+ style: {
1552
+ left: `${annotation.x * 100}%`,
1553
+ top: `${annotation.y * 100}%`,
1554
+ transform: `translate(-50%,-50%) scale(${1 / transform.zoom})`
1555
+ },
1556
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1557
+ type: "button",
1558
+ className: "dcsiv-pin",
1559
+ "data-active": selected === annotation.id,
1560
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
1561
+ onClick: (event) => {
1562
+ event.stopPropagation();
1563
+ const opening = selected !== annotation.id;
1564
+ setSelected(opening ? annotation.id : void 0);
1565
+ if (opening) setFocusNote(annotation.id);
1566
+ },
1567
+ children: position + 1
1568
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1569
+ className: "dcsiv-inline-note",
1570
+ "data-note-id": annotation.id,
1571
+ onClick: (event) => event.stopPropagation(),
1572
+ children: [
1573
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1574
+ className: "dcsiv-inline-index",
1575
+ children: position + 1
1576
+ }),
1577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1578
+ value: annotation.note,
1579
+ rows: 1,
1580
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
1581
+ placeholder: t("imageAnnotationPlaceholder"),
1582
+ onChange: (event) => {
1583
+ const note = event.target.value;
1584
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
1585
+ ...entry,
1586
+ note
1587
+ } : entry));
1588
+ },
1589
+ onKeyDown: (event) => {
1590
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
1591
+ event.preventDefault();
1592
+ event.stopPropagation();
1593
+ event.nativeEvent?.stopImmediatePropagation?.();
1594
+ setSelected(void 0);
1595
+ }
1596
+ }
1597
+ }),
1598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1599
+ type: "button",
1600
+ className: "dcsiv-note-remove",
1601
+ "aria-label": t("imageRemoveAnnotation"),
1602
+ onClick: (event) => {
1603
+ event.stopPropagation();
1604
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
1605
+ setSelected(void 0);
1606
+ },
1607
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
1608
+ })
1609
+ ]
1610
+ }) : null]
1611
+ }, annotation.id))]
1612
+ }), showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1614
+ type: "button",
1615
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
1616
+ "aria-label": t("imagePrevious"),
1617
+ onClick: (event) => {
1618
+ event.stopPropagation();
1619
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
1620
+ },
1621
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
1622
+ }),
1623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1624
+ type: "button",
1625
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
1626
+ "aria-label": t("imageNext"),
1627
+ onClick: (event) => {
1628
+ event.stopPropagation();
1629
+ setIndex((value) => (value + 1) % request.items.length);
1630
+ },
1631
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
1632
+ }),
1633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1634
+ className: "dcsiv-counter",
1635
+ children: [
1636
+ index + 1,
1637
+ " / ",
1638
+ request.items.length
1639
+ ]
1640
+ })
1641
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1642
+ className: "dcsiv-hint",
1643
+ children: t("imageAnnotateHint")
1644
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1645
+ className: "dcsiv-hint",
1646
+ children: t("imageZoomHint")
1647
+ }) : null]
1648
+ }), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1649
+ type: "button",
1650
+ className: "dcsiv-copy-notes",
1651
+ onClick: () => {
1652
+ copyNotes();
1653
+ },
1654
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
1655
+ }) : null]
1656
+ })
1657
+ ]
1658
+ });
1659
+ }
1660
+ //#endregion
1661
+ //#region src/subscription-image-viewer.js
1662
+ const boundedNumber = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
1663
+ const downloadOf = (value) => typeof value?.onInvoke === "function" ? {
1664
+ pendingLabel: typeof value.pendingLabel === "string" && value.pendingLabel !== "" ? value.pendingLabel : void 0,
1665
+ errorLabel: typeof value.errorLabel === "string" && value.errorLabel !== "" ? value.errorLabel : void 0,
1666
+ onInvoke: value.onInvoke
1667
+ } : void 0;
1668
+ const actionsOf = (value) => Array.isArray(value) ? value.flatMap((action, position) => {
1669
+ if (typeof action?.onInvoke !== "function" || typeof action?.label !== "string" || action.label.trim() === "") return [];
1670
+ return [{
1671
+ id: typeof action.id === "string" && action.id !== "" ? action.id : `action-${position + 1}`,
1672
+ label: action.label,
1673
+ pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
1674
+ errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
1675
+ closeOnSuccess: action.closeOnSuccess === true,
1676
+ onInvoke: action.onInvoke
1677
+ }];
1678
+ }) : [];
1679
+ function normalizeSubscriptionViewerRequest(request) {
1680
+ const items = (Array.isArray(request?.items) ? request.items : []).flatMap((item, position) => {
1681
+ if (typeof item?.src !== "string" || item.src === "") return [];
1682
+ return [{
1683
+ id: typeof item.id === "string" && item.id !== "" ? item.id : `image-${position + 1}`,
1684
+ src: item.src,
1685
+ name: typeof item.name === "string" && item.name !== "" ? item.name : `Image ${position + 1}`,
1686
+ width: boundedNumber(item.width, void 0),
1687
+ height: boundedNumber(item.height, void 0),
1688
+ bytes: boundedNumber(item.bytes, void 0),
1689
+ download: downloadOf(item.download),
1690
+ actions: actionsOf(item.actions)
1691
+ }];
1692
+ });
1693
+ if (items.length === 0) return void 0;
1694
+ const requestedIndex = Number.isInteger(request?.index) ? request.index : 0;
1695
+ return {
1696
+ items,
1697
+ index: Math.max(0, Math.min(items.length - 1, requestedIndex)),
1698
+ opener: typeof HTMLElement !== "undefined" && request?.opener instanceof HTMLElement ? request.opener : void 0,
1699
+ source: typeof request?.source === "string" ? request.source : "dsh-codex-subscription",
1700
+ annotations: request?.annotations !== false
1701
+ };
1702
+ }
1703
+ const copyAnnotations = (annotations) => annotations.map((annotation) => ({ ...annotation }));
1704
+ /**
1705
+ * Local image viewer state for subscription-generated images.
1706
+ *
1707
+ * This stays private to subscription image cards, which need annotation and
1708
+ * edit actions that a host's generic native viewer may not implement.
1709
+ */
1710
+ var SubscriptionImageViewerService = class {
1711
+ #listeners = /* @__PURE__ */ new Set();
1712
+ #revision = 0;
1713
+ #snapshot;
1714
+ #annotationsByImage = /* @__PURE__ */ new Map();
1715
+ constructor() {
1716
+ this.subscribe = (listener) => {
1717
+ this.#listeners.add(listener);
1718
+ return () => {
1719
+ this.#listeners.delete(listener);
1720
+ };
1721
+ };
1722
+ this.getSnapshot = () => this.#snapshot;
1723
+ this.getAnnotationsSnapshot = () => Object.fromEntries([...this.#annotationsByImage].map(([id, annotations]) => [id, copyAnnotations(annotations)]));
1724
+ }
1725
+ setAnnotations(imageId, annotations) {
1726
+ if (typeof imageId !== "string" || imageId === "" || !Array.isArray(annotations)) return;
1727
+ if (annotations.length === 0) this.#annotationsByImage.delete(imageId);
1728
+ else this.#annotationsByImage.set(imageId, copyAnnotations(annotations));
1729
+ }
1730
+ open(request) {
1731
+ const normalized = normalizeSubscriptionViewerRequest(request);
1732
+ if (normalized === void 0) return false;
1733
+ this.#revision += 1;
1734
+ this.#snapshot = {
1735
+ ...normalized,
1736
+ revision: this.#revision
1737
+ };
1738
+ this.#emit();
1739
+ return true;
1740
+ }
1741
+ close() {
1742
+ if (this.#snapshot === void 0) return;
1743
+ const opener = this.#snapshot.opener;
1744
+ this.#snapshot = void 0;
1745
+ this.#emit();
1746
+ if (typeof window === "undefined") opener?.focus();
1747
+ else {
1748
+ const focus = () => {
1749
+ opener?.focus();
1750
+ };
1751
+ if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(focus);
1752
+ else focus();
1753
+ }
1754
+ }
1755
+ #emit() {
1756
+ for (const listener of this.#listeners) listener();
1757
+ }
1907
1758
  };
1908
- const STYLE = `
1909
- .codexSubscriptionSearchHead{display:flex;flex-direction:column;gap:1px}.codexSubscriptionSearchScope{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
1910
- .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
1911
- .codexSubscription h2,.codexSubscription h3,.codexSubscription p{margin:0}.codexSubscriptionHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
1912
- .codexSubscription h2{font-size:16px;line-height:24px;font-weight:500}.codexSubscription h3{font-size:14px;line-height:22px;font-weight:500}
1913
- .codexSubscriptionTag{border:1px solid var(--dsw-alias-border-l3);border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}
1914
- .codexSubscriptionNote{font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
1915
- .codexSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
1916
- .codexSubscriptionUsageCard{padding:12px 14px;gap:9px}.codexSubscriptionPreferencesCard{padding:12px 14px;gap:10px}.codexSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}.codexSubscriptionPreferenceHint{max-width:300px;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
1917
- .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer;white-space:nowrap}.codexSubscriptionQuotaMode small{margin-left:3px;font-size:9px;line-height:1;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
1918
- .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}.codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.codexSubscriptionContextTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionContextTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexSubscriptionContextTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}.codexSubscriptionContextTrigger svg{color:var(--dsw-alias-label-tertiary);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionContextTrigger[aria-expanded=true] svg{transform:rotate(180deg)}.codexSubscriptionContextModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel:last-child{border-bottom:0}.codexSubscriptionContextModelCopy{display:flex;min-width:0;flex-direction:column}.codexSubscriptionContextModelCopy strong{font-size:12px;line-height:18px;font-weight:500}.codexSubscriptionContextModelCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextInput{width:116px}
1919
- .codexSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}.codexSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(14px)}
1920
- .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
1921
- .codexSubscriptionQuotaModes[data-saving=true] .codexSubscriptionQuotaMode:has(input:disabled){cursor:wait;opacity:1}.codexSubscriptionSearchChoices[data-saving=true] .codexSubscriptionSearchChoice:has(input:disabled){cursor:wait;opacity:1}
1922
- .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
1923
- .codexSubscriptionAccounts{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionAccount{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:42px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px}.codexSubscriptionAccount:last-child{border-bottom:0}.codexSubscriptionAccount[data-active=true] .codexSubscriptionEmail,.codexSubscriptionAccount[data-active=true]>span{font-weight:600}.codexSubscriptionEmail{max-width:100%;overflow:hidden;padding:2px 4px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.codexSubscriptionEmail:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionEmail:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionFlow label{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary)}
1924
- .codexSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}.codexSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}.codexSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
1925
- .codexSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.codexSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
1926
- .codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
1927
- .codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}.codexSubscriptionInput{width:100%;box-sizing:border-box}
1928
- .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionRecover .codexSubscriptionError{flex:1}.codexSubscriptionRecover button{flex:0 0 auto}
1929
- .codexSubscriptionDiagnostics{padding:8px 12px;gap:8px;background:transparent;color:var(--dsw-alias-label-secondary)}.codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}.codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}.codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
1930
- .codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
1931
- .codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}.codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
1932
- .codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
1933
- .codexSubscriptionLimits{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:6px}.codexSubscriptionLimit{min-width:0;border-radius:10px;padding:9px 12px;background:var(--dsw-alias-bg-module-platform);display:flex;flex-direction:column;gap:6px}
1934
- .codexSubscriptionLimitTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionLimitLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionLimit strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
1935
- .codexSubscriptionLimit progress{width:100%;height:4px;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}
1936
- .codexSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
1937
- .codexSubscriptionCreditSection{display:flex;flex-direction:column;gap:7px}.codexSubscriptionCreditNote{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionCreditRows{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.codexSubscriptionCreditBalance,.codexSubscriptionSpendLimit{min-width:0;border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-module-platform)}
1938
- .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
1939
- .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)}
1940
- .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}
1941
- .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}
1942
- .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}
1943
- .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)}
1944
- .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}
1945
- .codexModelSelectMenu{overflow:visible}
1946
- .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);height:240px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;height:100%;object-fit:cover}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageModal{width:min(920px,calc(100vw - 32px));max-height:calc(100vh - 32px)}.codexGeneratedImageModalContent{min-height:0;overflow:hidden}.codexGeneratedImageViewer{display:flex;min-width:0;flex-direction:column;gap:12px}.codexGeneratedImageStage{display:grid;place-items:center;min-height:280px;max-height:calc(100vh - 260px);overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-module-platform)}.codexGeneratedImageStage img{display:block;max-width:100%;max-height:calc(100vh - 280px);object-fit:contain;transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.codexGeneratedImageToolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.codexGeneratedImageZoom{display:flex;align-items:center;gap:6px}.codexGeneratedImageZoomValue{min-width:44px;color:var(--dsw-alias-label-secondary);font-size:12px;text-align:center}.codexGeneratedImageDownload{display:inline-flex;align-items:center;gap:6px;min-height:32px;box-sizing:border-box;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;text-decoration:none}.codexGeneratedImageDownload:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageGuidance{display:flex;flex-direction:column;gap:2px;padding-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
1947
- .codexImageBeta{padding:0 5px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px}
1948
- .codexImageToolGallery{display:flex;align-items:flex-start;flex-direction:column;gap:8px}
1949
- @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
1950
- @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
1951
- @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
1952
- `;
1759
+ //#endregion
1760
+ //#region src/settings-contract.js
1761
+ const SETTINGS_NAMESPACE = "codex-subscription";
1762
+ const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
1763
+ const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
1764
+ const QUICK_QUOTA_MODE_PERCENT = "percent";
1765
+ const QUICK_QUOTA_MODE_FORECAST = "forecast";
1766
+ const SEARCH_PROVIDER_FIELD = "searchProvider";
1767
+ const SEARCH_PROVIDER_AUTO = "auto";
1768
+ const SEARCH_PROVIDER_CODEX = "codex";
1769
+ const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
1770
+ const SPEED_MODE_FIELD = "speedMode";
1771
+ const SPEED_MODE_STANDARD = "standard";
1772
+ const SPEED_MODE_FAST = "fast";
1773
+ const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
1774
+ const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
1775
+ const OUTPUT_VERBOSITY_DEFAULT = "default";
1776
+ const OUTPUT_VERBOSITY_MEDIUM = "medium";
1777
+ const OUTPUT_VERBOSITY_HIGH = "high";
1778
+ const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
1779
+ const CONTEXT_MODE_FIELD = "contextMode";
1780
+ const CONTEXT_MODE_STANDARD = "standard";
1781
+ const CONTEXT_MODE_EXTENDED = "extended";
1782
+ const CONTEXT_MODE_CUSTOM = "custom";
1783
+ const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
1784
+ const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
1785
+ const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
1786
+ const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
1787
+ const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
1788
+ const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
1789
+ "gpt-5.4": "customContextGpt54",
1790
+ "gpt-5.4-mini": "customContextGpt54Mini",
1791
+ "gpt-5.5": "customContextGpt55",
1792
+ "gpt-5.6": "customContextGpt56",
1793
+ "gpt-6-astra": "customContextGpt6Astra"
1794
+ });
1795
+ const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
1796
+ "gpt-5.4": 1e6,
1797
+ "gpt-5.4-mini": 4e5,
1798
+ "gpt-5.5": 1e6,
1799
+ "gpt-5.6": 1e6,
1800
+ "gpt-6-astra": 872e3
1801
+ });
1802
+ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
1803
+ "gpt-5.4": 272e3,
1804
+ "gpt-5.4-mini": 272e3,
1805
+ "gpt-5.5": 272e3,
1806
+ "gpt-5.6": 272e3,
1807
+ "gpt-6-astra": 272e3
1808
+ });
1809
+ const normalizeSearchProvider = (value) => [
1810
+ "auto",
1811
+ "dsh",
1812
+ "codex"
1813
+ ].includes(value) ? value : DEFAULT_SEARCH_PROVIDER;
1814
+ const normalizeOutputVerbosity = (value) => [
1815
+ "default",
1816
+ "low",
1817
+ "medium",
1818
+ "high"
1819
+ ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
1820
+ const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
1821
+ const normalizeContextMode = (value) => [
1822
+ "standard",
1823
+ "extended",
1824
+ "custom"
1825
+ ].includes(value) ? value : DEFAULT_CONTEXT_MODE;
1826
+ const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
1827
+ if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
1828
+ return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
1829
+ };
1830
+ const formatContextWindow = (value) => value === 1e6 ? "1M" : `${Math.round(value / 1e3)}K`;
1831
+ const parseContextWindow = (value) => {
1832
+ const match = /^\s*(\d+)\s*$/u.exec(String(value));
1833
+ if (match === null) return NaN;
1834
+ return Number(match[1]);
1835
+ };
1836
+ const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
1837
+ "off",
1838
+ "percent",
1839
+ "bar",
1840
+ "forecast"
1841
+ ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
1842
+ const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
1843
+ //#endregion
1844
+ //#region src/sidebar-quota.js
1845
+ const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100 && Number.isFinite(window?.windowSeconds) && window.windowSeconds > 0;
1846
+ const normalized = (value) => String(value ?? "").toLocaleLowerCase("en-US").replaceAll(/[^a-z0-9]+/gu, "-");
1847
+ const limitMatchesModel = (limit, model) => {
1848
+ if (/\bspark\b/u.test(normalized(model))) return /\bspark\b/u.test(normalized(`${limit?.id ?? ""} ${limit?.name ?? ""}`));
1849
+ return limit?.id === "codex";
1850
+ };
1851
+ function selectModelQuotaWindows(usage, model) {
1852
+ return (Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : []).map((selected) => ({
1853
+ remainingPercent: selected.remainingPercent,
1854
+ windowSeconds: selected.windowSeconds,
1855
+ ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
1856
+ ...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
1857
+ })).sort((a, b) => a.windowSeconds - b.windowSeconds);
1858
+ }
1859
+ //#endregion
1860
+ //#region src/login-progress.js
1861
+ /** Reconcile a login flow with the credential store without exposing credentials. */
1862
+ async function readLoginProgress({ flow, readFlow, readAccount }) {
1863
+ try {
1864
+ const nextFlow = await readFlow();
1865
+ if (nextFlow.phase === "failed") try {
1866
+ const account = await readAccount();
1867
+ if (account?.authenticated === true) return {
1868
+ flow: {
1869
+ id: flow.id,
1870
+ method: flow.method,
1871
+ phase: "authenticated",
1872
+ authenticated: true
1873
+ },
1874
+ account,
1875
+ recovered: true
1876
+ };
1877
+ } catch {}
1878
+ if (nextFlow.phase !== "authenticated") return { flow: nextFlow };
1879
+ return {
1880
+ flow: nextFlow,
1881
+ account: await readAccount()
1882
+ };
1883
+ } catch (flowError) {
1884
+ try {
1885
+ const account = await readAccount();
1886
+ if (account?.authenticated === true) return {
1887
+ flow: {
1888
+ id: flow.id,
1889
+ method: flow.method,
1890
+ phase: "authenticated",
1891
+ authenticated: true
1892
+ },
1893
+ account,
1894
+ recovered: true
1895
+ };
1896
+ } catch {}
1897
+ throw flowError;
1898
+ }
1899
+ }
1900
+ //#endregion
1901
+ //#region src/preference-controller.js
1902
+ const CHANNEL$2 = "/codex-subscription";
1903
+ const unwrap$1 = (response) => {
1904
+ if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
1905
+ return response.value;
1906
+ };
1907
+ function createPreferenceController(scope, rpc) {
1908
+ let updating = false;
1909
+ let error = false;
1910
+ let fallbackStatus = "loading";
1911
+ let fallback;
1912
+ let pendingPatch;
1913
+ let failedPatch;
1914
+ let generation = 0;
1915
+ let contextModels = [];
1916
+ let verbosityModels = [];
1917
+ let modelError = false;
1918
+ let modelRefreshGeneration = 0;
1919
+ let modelRefreshStarted = false;
1920
+ let disposed = false;
1921
+ const sameModels = (left, right) => left.length === right.length && left.every((model, index) => JSON.stringify(model) === JSON.stringify(right[index]));
1922
+ const nativeSnapshot = () => scope.getSnapshot();
1923
+ const read = () => {
1924
+ const native = nativeSnapshot();
1925
+ const current = native.status === "ready" ? native : fallbackStatus === "ready" ? fallback : native;
1926
+ const value = pendingPatch === void 0 ? current.value : {
1927
+ ...current.value,
1928
+ ...pendingPatch
1929
+ };
1930
+ return Object.freeze({
1931
+ status: current.status,
1932
+ quickQuotaMode: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
1933
+ searchProvider: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
1934
+ speedMode: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1935
+ outputVerbosity: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
1936
+ contextMode: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
1937
+ customContextWindow: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
1938
+ customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1939
+ contextModels,
1940
+ verbosityModels,
1941
+ modelError,
1942
+ writable: !updating && current.status === "ready" && current.writable === true,
1943
+ saving: updating,
1944
+ error
1945
+ });
1946
+ };
1947
+ let snapshot = read();
1948
+ const listeners = /* @__PURE__ */ new Set();
1949
+ const publish = () => {
1950
+ snapshot = read();
1951
+ for (const listener of listeners) listener();
1952
+ };
1953
+ const disposeScope = scope.subscribe(() => {
1954
+ error = false;
1955
+ if (!updating) failedPatch = void 0;
1956
+ publish();
1957
+ });
1958
+ const acceptFallback = (value) => {
1959
+ if (!modelRefreshStarted) {
1960
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1961
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1962
+ }
1963
+ fallbackStatus = "ready";
1964
+ fallback = {
1965
+ status: "ready",
1966
+ value: {
1967
+ [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
1968
+ [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
1969
+ [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1970
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
1971
+ [CONTEXT_MODE_FIELD]: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
1972
+ [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
1973
+ ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])]))
1974
+ },
1975
+ writable: value?.writable === true
1976
+ };
1977
+ };
1978
+ const load = async () => {
1979
+ const current = ++generation;
1980
+ updating = false;
1981
+ pendingPatch = void 0;
1982
+ fallbackStatus = "loading";
1983
+ fallback = void 0;
1984
+ error = false;
1985
+ publish();
1986
+ try {
1987
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/status", {}));
1988
+ if (current !== generation || disposed) return;
1989
+ if (nativeSnapshot().status === "ready") {
1990
+ if (!modelRefreshStarted) {
1991
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1992
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1993
+ }
1994
+ } else acceptFallback(value);
1995
+ publish();
1996
+ } catch {
1997
+ if (current !== generation || disposed || nativeSnapshot().status === "ready") return;
1998
+ fallbackStatus = "unavailable";
1999
+ publish();
2000
+ }
2001
+ };
2002
+ const refreshModels = async () => {
2003
+ const current = ++modelRefreshGeneration;
2004
+ modelRefreshStarted = true;
2005
+ const hadError = modelError;
2006
+ modelError = false;
2007
+ if (hadError) publish();
2008
+ try {
2009
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/models", {}));
2010
+ if (disposed || current !== modelRefreshGeneration) return false;
2011
+ const nextContextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
2012
+ const nextVerbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
2013
+ const changed = !sameModels(contextModels, nextContextModels) || !sameModels(verbosityModels, nextVerbosityModels);
2014
+ if (changed) {
2015
+ contextModels = nextContextModels;
2016
+ verbosityModels = nextVerbosityModels;
2017
+ publish();
2018
+ }
2019
+ return changed;
2020
+ } catch {
2021
+ if (disposed || current !== modelRefreshGeneration) return false;
2022
+ if (!modelError) {
2023
+ modelError = true;
2024
+ publish();
2025
+ }
2026
+ return false;
2027
+ }
2028
+ };
2029
+ const set = async (patch) => {
2030
+ if (disposed || snapshot.status !== "ready" || snapshot.writable !== true) return;
2031
+ const current = ++generation;
2032
+ const entries = Object.entries(patch);
2033
+ updating = true;
2034
+ pendingPatch = patch;
2035
+ error = false;
2036
+ failedPatch = void 0;
2037
+ publish();
2038
+ try {
2039
+ if (nativeSnapshot().status === "ready") {
2040
+ for (const [field, value] of entries) {
2041
+ if (current !== generation) return;
2042
+ await scope.set(field, value);
2043
+ }
2044
+ if (current !== generation) return;
2045
+ const accepted = nativeSnapshot().value;
2046
+ error = entries.some(([field, value]) => accepted?.[field] !== value);
2047
+ pendingPatch = void 0;
2048
+ } else {
2049
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/update", patch));
2050
+ if (current !== generation) return;
2051
+ acceptFallback(value);
2052
+ pendingPatch = void 0;
2053
+ }
2054
+ } catch {
2055
+ if (current === generation) {
2056
+ pendingPatch = void 0;
2057
+ error = true;
2058
+ failedPatch = patch;
2059
+ }
2060
+ } finally {
2061
+ if (current === generation) {
2062
+ updating = false;
2063
+ publish();
2064
+ }
2065
+ }
2066
+ };
2067
+ return {
2068
+ getSnapshot: () => snapshot,
2069
+ subscribe: (listener) => {
2070
+ listeners.add(listener);
2071
+ return () => listeners.delete(listener);
2072
+ },
2073
+ load,
2074
+ set,
2075
+ retry: () => failedPatch === void 0 ? load() : set(failedPatch),
2076
+ refreshModels,
2077
+ dispose: () => {
2078
+ disposed = true;
2079
+ generation += 1;
2080
+ modelRefreshGeneration += 1;
2081
+ disposeScope();
2082
+ }
2083
+ };
2084
+ }
2085
+ //#endregion
2086
+ //#region src/account-status-controller.js
2087
+ const CHANNEL$1 = "/codex-subscription";
2088
+ const DEFAULT_TIMEOUT_MS = 1e4;
2089
+ const STATUS_ERROR_CODES = /* @__PURE__ */ new Set([
2090
+ "credential-unavailable",
2091
+ "credential-malformed",
2092
+ "transport",
2093
+ "timeout",
2094
+ "unknown"
2095
+ ]);
2096
+ const STATUS_ERROR_MESSAGES = /* @__PURE__ */ new Map([
2097
+ ["Codex account credentials are unavailable", "credential-unavailable"],
2098
+ ["Codex account credentials are malformed", "credential-malformed"],
2099
+ ["Codex account status service is unavailable", "transport"],
2100
+ ["Could not read Codex account status", "unknown"]
2101
+ ]);
2102
+ const TIMEOUT_CODES = /* @__PURE__ */ new Set([
2103
+ "TIMEOUT",
2104
+ "ETIMEDOUT",
2105
+ "ERR_TIMEOUT",
2106
+ "UND_ERR_CONNECT_TIMEOUT"
2107
+ ]);
2108
+ const TRANSPORT_CODES = /* @__PURE__ */ new Set([
2109
+ "ECONNRESET",
2110
+ "ECONNREFUSED",
2111
+ "ENOTFOUND",
2112
+ "EAI_AGAIN",
2113
+ "NETWORK",
2114
+ "NETWORK_ERROR",
2115
+ "TRANSPORT",
2116
+ "CONNECTION_CLOSED",
2117
+ "DISCONNECTED"
2118
+ ]);
2119
+ const asCode = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
2120
+ function rpcError(response) {
2121
+ const code = asCode(response?.error?.code);
2122
+ const message = typeof response?.error?.message === "string" ? response.error.message : "";
2123
+ const error = /* @__PURE__ */ new Error("Codex account status request failed");
2124
+ error.code = code === "internal" && STATUS_ERROR_MESSAGES.has(message) ? STATUS_ERROR_MESSAGES.get(message) : "unknown";
2125
+ return error;
2126
+ }
2127
+ function timeoutError() {
2128
+ const error = /* @__PURE__ */ new Error("Codex account status request timed out");
2129
+ error.code = "timeout";
2130
+ error.name = "TimeoutError";
2131
+ return error;
2132
+ }
2133
+ function classifyAccountStatusError(error) {
2134
+ const code = typeof error?.code === "string" ? error.code.trim().toUpperCase() : "";
2135
+ if (code === "TIMEOUT" || TIMEOUT_CODES.has(code) || error?.name === "TimeoutError") return "timeout";
2136
+ if (STATUS_ERROR_CODES.has(asCode(error?.code))) return asCode(error.code);
2137
+ if (TRANSPORT_CODES.has(code) || error?.name === "NetworkError") return "transport";
2138
+ return "unknown";
2139
+ }
2140
+ function publicAccountStatusError(error) {
2141
+ return Object.freeze({ code: classifyAccountStatusError(error) });
2142
+ }
2143
+ /** Own the account-status request lifecycle independently from account actions. */
2144
+ function createAccountStatusController(rpc, options = {}) {
2145
+ const request = options.request ?? (() => rpc.call(CHANNEL$1, "status", {}));
2146
+ const scheduleTimeout = options.setTimeout ?? setTimeout;
2147
+ const cancelTimeout = options.clearTimeout ?? clearTimeout;
2148
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2149
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Account status timeout must be positive");
2150
+ let snapshot = Object.freeze({
2151
+ status: "loading",
2152
+ account: void 0,
2153
+ error: void 0,
2154
+ retrying: false
2155
+ });
2156
+ let generation = 0;
2157
+ let active;
2158
+ let disposed = false;
2159
+ const listeners = /* @__PURE__ */ new Set();
2160
+ const publish = (next) => {
2161
+ snapshot = Object.freeze(next);
2162
+ for (const listener of [...listeners]) listener();
2163
+ };
2164
+ const load = () => {
2165
+ if (disposed) return Promise.resolve(void 0);
2166
+ if (active !== void 0) return active.promise;
2167
+ const id = ++generation;
2168
+ const retrying = snapshot.error !== void 0;
2169
+ publish({
2170
+ status: retrying ? "error" : "loading",
2171
+ account: retrying ? snapshot.account : void 0,
2172
+ error: retrying ? snapshot.error : void 0,
2173
+ retrying: true
2174
+ });
2175
+ const controller = new AbortController();
2176
+ let timer;
2177
+ let onAbort;
2178
+ const cancelled = new Promise((resolve, reject) => {
2179
+ onAbort = () => reject(controller.signal.reason);
2180
+ controller.signal.addEventListener("abort", onAbort, { once: true });
2181
+ if (controller.signal.aborted) onAbort();
2182
+ });
2183
+ const timeout = new Promise((resolve, reject) => {
2184
+ timer = scheduleTimeout(() => {
2185
+ const error = timeoutError();
2186
+ controller.abort(error);
2187
+ reject(error);
2188
+ }, timeoutMs);
2189
+ });
2190
+ const work = Promise.resolve().then(() => request(controller.signal)).then((response) => {
2191
+ if (!response?.ok) throw rpcError(response);
2192
+ return response.value;
2193
+ });
2194
+ const promise = Promise.race([
2195
+ work,
2196
+ timeout,
2197
+ cancelled
2198
+ ]).then((account) => {
2199
+ if (disposed || id !== generation || controller.signal.aborted) return void 0;
2200
+ if (account === null || typeof account !== "object" || Array.isArray(account) || typeof account.authenticated !== "boolean") throw new Error("Invalid account status");
2201
+ publish({
2202
+ status: "ready",
2203
+ account,
2204
+ error: void 0,
2205
+ retrying: false
2206
+ });
2207
+ return account;
2208
+ }).catch((error) => {
2209
+ if (disposed || id !== generation || controller.signal.aborted && error?.code !== "timeout") return void 0;
2210
+ publish({
2211
+ status: "error",
2212
+ account: void 0,
2213
+ error: publicAccountStatusError(error),
2214
+ retrying: false
2215
+ });
2216
+ }).finally(() => {
2217
+ cancelTimeout(timer);
2218
+ controller.signal.removeEventListener("abort", onAbort);
2219
+ if (active?.id === id) active = void 0;
2220
+ });
2221
+ active = {
2222
+ id,
2223
+ controller,
2224
+ promise
2225
+ };
2226
+ return promise;
2227
+ };
2228
+ const acceptAccount = (account) => {
2229
+ if (disposed) return false;
2230
+ generation += 1;
2231
+ active?.controller.abort(/* @__PURE__ */ new Error("Account status superseded by an account action"));
2232
+ active = void 0;
2233
+ publish({
2234
+ status: "ready",
2235
+ account,
2236
+ error: void 0,
2237
+ retrying: false
2238
+ });
2239
+ return true;
2240
+ };
2241
+ const reload = () => {
2242
+ if (disposed) return Promise.resolve(void 0);
2243
+ if (active !== void 0) {
2244
+ generation += 1;
2245
+ active.controller.abort(/* @__PURE__ */ new Error("Account status reload superseded the previous request"));
2246
+ active = void 0;
2247
+ }
2248
+ return load();
2249
+ };
2250
+ const dispose = () => {
2251
+ if (disposed) return;
2252
+ disposed = true;
2253
+ generation += 1;
2254
+ active?.controller.abort(/* @__PURE__ */ new Error("Account status controller disposed"));
2255
+ active = void 0;
2256
+ listeners.clear();
2257
+ };
2258
+ return Object.freeze({
2259
+ getSnapshot: () => snapshot,
2260
+ subscribe(listener) {
2261
+ listeners.add(listener);
2262
+ return () => listeners.delete(listener);
2263
+ },
2264
+ load,
2265
+ retry: load,
2266
+ reload,
2267
+ acceptAccount,
2268
+ dispose
2269
+ });
2270
+ }
2271
+ //#endregion
2272
+ //#region src/context-draft-state.js
2273
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value ?? {}, key);
2274
+ /**
2275
+ * Reconcile saved context values with the inputs currently shown in Settings.
2276
+ * A draft survives a catalog refresh while its saved value is unchanged. New
2277
+ * rows and rows whose saved value changed start from the new saved value.
2278
+ */
2279
+ function reconcileContextDrafts({ modelRows, drafts, previousSavedValues, savedValues }) {
2280
+ const next = {};
2281
+ for (const model of modelRows) {
2282
+ const key = model.key;
2283
+ const saved = String(savedValues?.[key] ?? "");
2284
+ next[key] = hasOwn(previousSavedValues, key) && previousSavedValues[key] === saved && hasOwn(drafts, key) ? drafts[key] : saved;
2285
+ }
2286
+ return next;
2287
+ }
2288
+ //#endregion
2289
+ //#region src/client.jsx
2290
+ const inject = [
2291
+ "slots",
2292
+ "locale",
2293
+ "connection",
2294
+ "remote",
2295
+ "settingsScope",
2296
+ "modelDirectories",
2297
+ "conversation",
2298
+ "uiConversation",
2299
+ "sessions"
2300
+ ];
2301
+ const NS = "settings.codexSubscription";
2302
+ const CHANNEL = "/codex-subscription";
2303
+ const SUPPORT_ISSUE_URL = "https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml";
2304
+ const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
2305
+ const QUICK_QUOTA_REFRESH_MS = 6e4;
1953
2306
  const unwrap = (response) => {
1954
2307
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
1955
2308
  return response.value;
@@ -1986,216 +2339,6 @@ window.__ModuleLoader__.load({
1986
2339
  const date = new Date(value);
1987
2340
  return Number.isFinite(date.getTime()) ? date : void 0;
1988
2341
  };
1989
- const imageDownloadName = (attachment) => {
1990
- const fallback = "codex-generated-image.png";
1991
- if (typeof attachment?.name !== "string") return fallback;
1992
- const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
1993
- if (cleaned === "") return fallback;
1994
- return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
1995
- };
1996
- 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;
1997
- async function sha256Hex(data) {
1998
- const value = await crypto.subtle.digest("SHA-256", data);
1999
- return [...new Uint8Array(value)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2000
- }
2001
- function decodeBase64Chunk(value) {
2002
- if (typeof value !== "string" || value.length === 0 || value.length > Math.ceil(4194304 / 3) * 4 + 8) throw new Error("Invalid original image chunk");
2003
- let decoded;
2004
- try {
2005
- decoded = atob(value);
2006
- } catch {
2007
- throw new Error("Invalid original image chunk");
2008
- }
2009
- const bytes = new Uint8Array(decoded.length);
2010
- for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index);
2011
- return bytes;
2012
- }
2013
- async function readOriginalImage(rpc, sessionId, original) {
2014
- const parts = [];
2015
- let total = 0;
2016
- let done = false;
2017
- while (!done) {
2018
- const chunk = unwrap(await rpc.call(CHANNEL, "image/original/chunk", {
2019
- sessionId,
2020
- assetId: original.assetId,
2021
- offset: total
2022
- }));
2023
- const ref = decodeOriginalImageRef(chunk?.ref);
2024
- if (ref === void 0 || !originalRefMatches(ref, original) || chunk.offset !== total || typeof chunk.done !== "boolean") throw new Error("Original image metadata changed");
2025
- const bytes = decodeBase64Chunk(chunk.encoded);
2026
- if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
2027
- parts.push(bytes);
2028
- total += bytes.byteLength;
2029
- done = chunk.done;
2030
- }
2031
- if (total !== original.bytes) throw new Error("Original image download is incomplete");
2032
- const data = new Uint8Array(total);
2033
- let offset = 0;
2034
- for (const part of parts) {
2035
- data.set(part, offset);
2036
- offset += part.byteLength;
2037
- }
2038
- if (await sha256Hex(data) !== original.sha256) throw new Error("Original image integrity check failed");
2039
- return data;
2040
- }
2041
- function triggerBlobDownload(data, mediaType, filename) {
2042
- const url = URL.createObjectURL(new Blob([data], { type: mediaType }));
2043
- const anchor = document.createElement("a");
2044
- anchor.href = url;
2045
- anchor.download = filename;
2046
- anchor.rel = "noopener";
2047
- document.body.append(anchor);
2048
- try {
2049
- anchor.click();
2050
- } finally {
2051
- anchor.remove();
2052
- URL.revokeObjectURL(url);
2053
- }
2054
- }
2055
- function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
2056
- const [attempt, setAttempt] = (0, react.useState)(0);
2057
- const [error, setError] = (0, react.useState)(false);
2058
- const [src, setSrc] = (0, react.useState)();
2059
- const triggerRef = (0, react.useRef)(null);
2060
- (0, react.useEffect)(() => {
2061
- let live = true;
2062
- setError(false);
2063
- setSrc(void 0);
2064
- Promise.resolve().then(() => loadImage(attachment)).then((value) => {
2065
- if (live) setSrc(value);
2066
- }).catch(() => {
2067
- if (live) setError(true);
2068
- });
2069
- return () => {
2070
- live = false;
2071
- };
2072
- }, [
2073
- attachment,
2074
- loadImage,
2075
- attempt
2076
- ]);
2077
- const label = attachment.name ?? t("imageLabel");
2078
- const downloadName = imageDownloadName(attachment);
2079
- const downloadOriginal = async () => {
2080
- if (original === void 0) return;
2081
- triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
2082
- };
2083
- const openImage = () => {
2084
- if (src === void 0) return;
2085
- const request = {
2086
- items: [{
2087
- id: attachment.attachmentId ?? downloadName,
2088
- src,
2089
- name: label,
2090
- width: attachment.width,
2091
- height: attachment.height,
2092
- bytes: attachment.bytes,
2093
- download: original === void 0 ? void 0 : {
2094
- pendingLabel: t("imageDownloadPreparing"),
2095
- errorLabel: t("imageDownloadFailed"),
2096
- onInvoke: downloadOriginal
2097
- },
2098
- actions: [{
2099
- id: "continue-editing",
2100
- label: t("imageEdit"),
2101
- pendingLabel: t("imageEditPreparing"),
2102
- errorLabel: t("imageEditFailed"),
2103
- closeOnSuccess: true,
2104
- onInvoke: ({ annotations = [] }) => {
2105
- const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
2106
- const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
2107
- const referenceName = `codex-edit-${imageKey}-annotations.png`;
2108
- return attachForEdit(src, sourceName, buildImageEditDraft({
2109
- annotations,
2110
- translate: t,
2111
- width: attachment.width,
2112
- height: attachment.height,
2113
- sourceName,
2114
- referenceName
2115
- }), annotations, referenceName);
2116
- }
2117
- }]
2118
- }],
2119
- opener: triggerRef.current,
2120
- source: "codex-generated",
2121
- annotations: true
2122
- };
2123
- if (getInternalImageViewer?.()?.open?.(request) === true) return;
2124
- (getImageViewer?.())?.open?.(request);
2125
- };
2126
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2127
- type: "button",
2128
- className: "codexGeneratedImageRetry",
2129
- onClick: () => setAttempt((value) => value + 1),
2130
- children: t("imageLoadFailed")
2131
- });
2132
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2133
- ref: triggerRef,
2134
- type: "button",
2135
- className: "codexGeneratedImageFrame",
2136
- title: t("imageOpen"),
2137
- "aria-label": fill(t("imageOpenNamed"), { value: label }),
2138
- onClick: openImage,
2139
- children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
2140
- src,
2141
- alt: label
2142
- })
2143
- });
2144
- }
2145
- function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, getInternalImageViewer, t }) {
2146
- const settled = block?.kind === "tool-result";
2147
- const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
2148
- const failed = settled && block.isError === true;
2149
- const state = !settled ? "running" : failed ? "error" : "done";
2150
- const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
2151
- const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
2152
- const original = decodeImagePresentation(block?.meta)?.original;
2153
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2154
- className: "codexImageTool",
2155
- "data-state": state,
2156
- children: [
2157
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2158
- className: "codexImageToolRow",
2159
- children: [
2160
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2161
- className: "codexImageToolIcon",
2162
- "aria-hidden": "true"
2163
- }),
2164
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2165
- className: "codexImageToolTitle",
2166
- children: t("imageGenerate")
2167
- }),
2168
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2169
- className: "codexImageBeta",
2170
- children: t("imageBeta")
2171
- }),
2172
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2173
- className: "codexImageToolState",
2174
- children: status
2175
- })
2176
- ]
2177
- }),
2178
- image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2179
- className: "codexImageToolGallery",
2180
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
2181
- attachment: image.attachment,
2182
- original,
2183
- rpc,
2184
- sessionId,
2185
- loadImage,
2186
- attachForEdit,
2187
- getImageViewer,
2188
- getInternalImageViewer,
2189
- t
2190
- })
2191
- }),
2192
- error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2193
- className: "codexImageToolError",
2194
- children: error
2195
- })
2196
- ]
2197
- });
2198
- }
2199
2342
  const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
2200
2343
  const useAccountStatusSnapshot = (accountStatus) => (0, react.useSyncExternalStore)(accountStatus.subscribe, accountStatus.getSnapshot);
2201
2344
  const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));