dsh-codex-subscription 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -4
- package/README.md +33 -42
- package/README.zh-CN.md +32 -41
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/lib/client.js +350 -200
- package/lib/index.js +401 -18
- package/package.json +5 -1
package/lib/client.js
CHANGED
|
@@ -59,12 +59,32 @@ window.__ModuleLoader__.load({
|
|
|
59
59
|
const base = prompt.trim() === "" ? translate("imageEditDefault") : prompt.trim();
|
|
60
60
|
return notes.length === 0 ? base : base + "\n\n" + translate("imageRegionNotes") + "\n" + notes.join("\n");
|
|
61
61
|
}
|
|
62
|
+
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
63
|
+
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
64
|
+
function decodeOriginalImageRef(value) {
|
|
65
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
|
|
66
|
+
return {
|
|
67
|
+
assetId: value.assetId,
|
|
68
|
+
mediaType: value.mediaType,
|
|
69
|
+
bytes: value.bytes,
|
|
70
|
+
width: value.width,
|
|
71
|
+
height: value.height,
|
|
72
|
+
name: value.name,
|
|
73
|
+
sha256: value.sha256
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function decodeImagePresentation(value) {
|
|
77
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
78
|
+
const original = decodeOriginalImageRef(value.original);
|
|
79
|
+
return original === void 0 ? void 0 : { original };
|
|
80
|
+
}
|
|
62
81
|
//#endregion
|
|
63
82
|
//#region src/settings-contract.js
|
|
64
83
|
const SETTINGS_NAMESPACE = "codex-subscription";
|
|
65
84
|
const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
|
|
66
85
|
const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
|
|
67
86
|
const QUICK_QUOTA_MODE_PERCENT = "percent";
|
|
87
|
+
const QUICK_QUOTA_MODE_FORECAST = "forecast";
|
|
68
88
|
const SEARCH_PROVIDER_FIELD = "searchProvider";
|
|
69
89
|
const SEARCH_PROVIDER_AUTO = "auto";
|
|
70
90
|
const SEARCH_PROVIDER_CODEX = "codex";
|
|
@@ -135,7 +155,8 @@ window.__ModuleLoader__.load({
|
|
|
135
155
|
const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
|
|
136
156
|
"off",
|
|
137
157
|
"percent",
|
|
138
|
-
"bar"
|
|
158
|
+
"bar",
|
|
159
|
+
"forecast"
|
|
139
160
|
].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
|
|
140
161
|
const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
|
|
141
162
|
//#endregion
|
|
@@ -153,7 +174,8 @@ window.__ModuleLoader__.load({
|
|
|
153
174
|
return {
|
|
154
175
|
remainingPercent: selected.remainingPercent,
|
|
155
176
|
windowSeconds: selected.windowSeconds,
|
|
156
|
-
...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {}
|
|
177
|
+
...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
|
|
178
|
+
...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
|
|
157
179
|
};
|
|
158
180
|
}
|
|
159
181
|
//#endregion
|
|
@@ -304,6 +326,8 @@ window.__ModuleLoader__.load({
|
|
|
304
326
|
quickQuotaOff: "关闭",
|
|
305
327
|
quickQuotaPercent: "百分比",
|
|
306
328
|
quickQuotaBar: "进度条",
|
|
329
|
+
quickQuotaForecast: "续航预测",
|
|
330
|
+
quickQuotaBeta: "Beta",
|
|
307
331
|
contextTitle: "上下文窗口",
|
|
308
332
|
contextStandard: "标准",
|
|
309
333
|
contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
|
|
@@ -315,6 +339,12 @@ window.__ModuleLoader__.load({
|
|
|
315
339
|
contextFixed: "固定 {value}",
|
|
316
340
|
contextMaximum: "范围 128000–{value}",
|
|
317
341
|
quickQuotaStatus: "Codex 剩余额度 {value}%",
|
|
342
|
+
quickQuotaForecastStatus: "Codex 剩余额度 {value}%,按当前速度预计可用 {duration}",
|
|
343
|
+
quotaForecast: "按当前速度 {symbol}{duration}",
|
|
344
|
+
runwayDaysHours: "{days} 天 {hours} 小时",
|
|
345
|
+
runwayDays: "{days} 天",
|
|
346
|
+
runwayHours: "{hours} 小时",
|
|
347
|
+
runwayMinutes: "{minutes} 分钟",
|
|
318
348
|
speedTitle: "速度",
|
|
319
349
|
speedStandard: "标准",
|
|
320
350
|
speedStandardHint: "标准速度",
|
|
@@ -346,13 +376,16 @@ window.__ModuleLoader__.load({
|
|
|
346
376
|
imageGenerated: "已生成",
|
|
347
377
|
imageFailed: "生成失败",
|
|
348
378
|
imageLabel: "生成的图片",
|
|
349
|
-
imageOpen: "
|
|
379
|
+
imageOpen: "查看图片",
|
|
350
380
|
imageOpenNamed: "查看 {value}",
|
|
351
381
|
imageLoading: "正在加载图片…",
|
|
352
382
|
imageLoadFailed: "图片加载失败,点击重试",
|
|
353
383
|
imagePreview: "图片预览",
|
|
384
|
+
imagePreviewShort: "预览图",
|
|
354
385
|
imageClosePreview: "关闭预览",
|
|
355
|
-
imageDownload: "
|
|
386
|
+
imageDownload: "下载",
|
|
387
|
+
imageDownloadPreparing: "正在准备原图…",
|
|
388
|
+
imageDownloadFailed: "下载失败,重试",
|
|
356
389
|
imageZoomOut: "缩小",
|
|
357
390
|
imageZoomIn: "放大",
|
|
358
391
|
imageFit: "适合窗口",
|
|
@@ -458,6 +491,8 @@ window.__ModuleLoader__.load({
|
|
|
458
491
|
quickQuotaOff: "Off",
|
|
459
492
|
quickQuotaPercent: "Percent",
|
|
460
493
|
quickQuotaBar: "Progress bar",
|
|
494
|
+
quickQuotaForecast: "Runway",
|
|
495
|
+
quickQuotaBeta: "Beta",
|
|
461
496
|
contextTitle: "Context window",
|
|
462
497
|
contextStandard: "Standard",
|
|
463
498
|
contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
|
|
@@ -469,6 +504,12 @@ window.__ModuleLoader__.load({
|
|
|
469
504
|
contextFixed: "Fixed {value}",
|
|
470
505
|
contextMaximum: "128000–{value}",
|
|
471
506
|
quickQuotaStatus: "Codex quota: {value}% remaining",
|
|
507
|
+
quickQuotaForecastStatus: "Codex quota: {value}% remaining; about {duration} at the current pace",
|
|
508
|
+
quotaForecast: "At current pace {symbol}{duration}",
|
|
509
|
+
runwayDaysHours: "{days}d {hours}h",
|
|
510
|
+
runwayDays: "{days}d",
|
|
511
|
+
runwayHours: "{hours}h",
|
|
512
|
+
runwayMinutes: "{minutes}m",
|
|
472
513
|
speedTitle: "Speed",
|
|
473
514
|
speedStandard: "Standard",
|
|
474
515
|
speedStandardHint: "Standard speed",
|
|
@@ -500,13 +541,16 @@ window.__ModuleLoader__.load({
|
|
|
500
541
|
imageGenerated: "Generated",
|
|
501
542
|
imageFailed: "Generation failed",
|
|
502
543
|
imageLabel: "Generated image",
|
|
503
|
-
imageOpen: "View
|
|
544
|
+
imageOpen: "View image",
|
|
504
545
|
imageOpenNamed: "View {value}",
|
|
505
546
|
imageLoading: "Loading image…",
|
|
506
547
|
imageLoadFailed: "Image failed to load. Click to retry",
|
|
507
548
|
imagePreview: "Image preview",
|
|
549
|
+
imagePreviewShort: "Preview",
|
|
508
550
|
imageClosePreview: "Close preview",
|
|
509
|
-
imageDownload: "Download
|
|
551
|
+
imageDownload: "Download",
|
|
552
|
+
imageDownloadPreparing: "Preparing original…",
|
|
553
|
+
imageDownloadFailed: "Download failed. Retry",
|
|
510
554
|
imageZoomOut: "Zoom out",
|
|
511
555
|
imageZoomIn: "Zoom in",
|
|
512
556
|
imageFit: "Fit to window",
|
|
@@ -531,7 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
531
575
|
.codexSubscriptionNote{font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
|
|
532
576
|
.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}
|
|
533
577
|
.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}
|
|
534
|
-
.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}.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}
|
|
578
|
+
.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}
|
|
535
579
|
.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}
|
|
536
580
|
.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)}
|
|
537
581
|
.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)}
|
|
@@ -560,12 +604,13 @@ window.__ModuleLoader__.load({
|
|
|
560
604
|
.codexModelSelectMenu{overflow:visible}
|
|
561
605
|
.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)}}
|
|
562
606
|
.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}
|
|
607
|
+
.codexImageToolGallery{display:flex;align-items:flex-start;flex-direction:column;gap:8px}
|
|
563
608
|
@container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
|
|
564
609
|
@container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
|
|
565
610
|
@media(max-width:640px){.codexSubscriptionCard{padding:14px}}
|
|
566
611
|
`;
|
|
567
612
|
const IMAGE_STYLE = String.raw`
|
|
568
|
-
.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:grid;grid-template-rows:auto minmax(0,1fr)
|
|
613
|
+
.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:grid;grid-template-rows:auto minmax(0,1fr);background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary);outline:0}.codexGeneratedImageTopbar{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:58px;padding:8px 14px;border-bottom:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-base,#111) 92%,transparent);backdrop-filter:blur(18px)}.codexGeneratedImageTopbar>div:first-child{display:flex;min-width:0;flex-direction:column}.codexGeneratedImageTopbar strong{overflow:hidden;font-size:13px;font-weight:600;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexGeneratedImageTopbar small{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.codexGeneratedImageActions{display:flex;align-items:center;gap:6px}.codexGeneratedImageActions button,.codexGeneratedImageActions a{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:5px;height:32px;padding:0 10px;border:1px solid transparent;border-radius:16px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-decoration:none;cursor:pointer}.codexGeneratedImageActions button:hover,.codexGeneratedImageActions a:hover,.codexGeneratedImageActions button.is-active{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageActions button:focus-visible,.codexGeneratedImageActions a:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexGeneratedImageActions button:disabled{opacity:.4;cursor:default}.codexGeneratedImageActions>span{min-width:38px;color:var(--dsw-alias-label-tertiary);font-size:11px;text-align:center}.codexGeneratedImageClose{font-size:20px!important}.codexGeneratedImageCanvas{position:relative;display:grid;place-items:center;min-height:0;overflow:auto;padding:24px;background:var(--dsw-alias-bg-base,#111)}.codexGeneratedImageSurface{position:relative;display:inline-flex;max-width:calc(100vw - 48px);max-height:calc(100vh - 150px);transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageSurface img{display:block;max-width:100%;max-height:calc(100vh - 150px);border-radius:12px;object-fit:contain}.codexGeneratedImageCanvas.is-annotating .codexGeneratedImageSurface{cursor:crosshair}.codexGeneratedImagePin{position:absolute;display:grid;place-items:center;width:24px;height:24px;padding:0;border:2px solid white;border-radius:50%;background:var(--dsw-alias-label-primary);box-shadow:0 1px 4px rgba(0,0,0,.35);color:var(--dsw-alias-bg-base,#111);font:inherit;font-size:11px;font-weight:700;transform:translate(-50%,-50%);cursor:pointer}.codexGeneratedImagePin.is-active{background:var(--dsw-alias-state-business-primary,#3964fe);color:white}.codexGeneratedImageAnnotateHint{position:absolute;bottom:14px;left:50%;padding:6px 10px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 90%,transparent);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-secondary);font-size:11px;transform:translateX(-50%)}@media(max-width:760px){.codexGeneratedImageTopbar{align-items:flex-start;flex-direction:column}.codexGeneratedImageActions{width:100%;overflow-x:auto}.codexGeneratedImageActions>span{display:none}.codexGeneratedImageCanvas{padding:12px}.codexGeneratedImageSurface{max-width:calc(100vw - 24px);max-height:calc(100vh - 180px)}.codexGeneratedImageSurface img{max-height:calc(100vh - 180px)}}
|
|
569
614
|
`;
|
|
570
615
|
const IMAGE_LAYOUT_STYLE = String.raw`
|
|
571
616
|
.codexGeneratedImageWorkspace{display:grid;min-height:0;grid-template-columns:minmax(0,1fr)}
|
|
@@ -605,7 +650,66 @@ window.__ModuleLoader__.load({
|
|
|
605
650
|
return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
|
|
606
651
|
};
|
|
607
652
|
const imageByteSize = (bytes) => bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
|
|
608
|
-
|
|
653
|
+
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;
|
|
654
|
+
async function sha256Hex(data) {
|
|
655
|
+
const value = await crypto.subtle.digest("SHA-256", data);
|
|
656
|
+
return [...new Uint8Array(value)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
657
|
+
}
|
|
658
|
+
function decodeBase64Chunk(value) {
|
|
659
|
+
if (typeof value !== "string" || value.length === 0 || value.length > Math.ceil(4194304 / 3) * 4 + 8) throw new Error("Invalid original image chunk");
|
|
660
|
+
let decoded;
|
|
661
|
+
try {
|
|
662
|
+
decoded = atob(value);
|
|
663
|
+
} catch {
|
|
664
|
+
throw new Error("Invalid original image chunk");
|
|
665
|
+
}
|
|
666
|
+
const bytes = new Uint8Array(decoded.length);
|
|
667
|
+
for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index);
|
|
668
|
+
return bytes;
|
|
669
|
+
}
|
|
670
|
+
async function readOriginalImage(rpc, sessionId, original) {
|
|
671
|
+
const parts = [];
|
|
672
|
+
let total = 0;
|
|
673
|
+
let done = false;
|
|
674
|
+
while (!done) {
|
|
675
|
+
const chunk = unwrap(await rpc.call(CHANNEL, "image/original/chunk", {
|
|
676
|
+
sessionId,
|
|
677
|
+
assetId: original.assetId,
|
|
678
|
+
offset: total
|
|
679
|
+
}));
|
|
680
|
+
const ref = decodeOriginalImageRef(chunk?.ref);
|
|
681
|
+
if (ref === void 0 || !originalRefMatches(ref, original) || chunk.offset !== total || typeof chunk.done !== "boolean") throw new Error("Original image metadata changed");
|
|
682
|
+
const bytes = decodeBase64Chunk(chunk.encoded);
|
|
683
|
+
if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
|
|
684
|
+
parts.push(bytes);
|
|
685
|
+
total += bytes.byteLength;
|
|
686
|
+
done = chunk.done;
|
|
687
|
+
}
|
|
688
|
+
if (total !== original.bytes) throw new Error("Original image download is incomplete");
|
|
689
|
+
const data = new Uint8Array(total);
|
|
690
|
+
let offset = 0;
|
|
691
|
+
for (const part of parts) {
|
|
692
|
+
data.set(part, offset);
|
|
693
|
+
offset += part.byteLength;
|
|
694
|
+
}
|
|
695
|
+
if (await sha256Hex(data) !== original.sha256) throw new Error("Original image integrity check failed");
|
|
696
|
+
return data;
|
|
697
|
+
}
|
|
698
|
+
function triggerBlobDownload(data, mediaType, filename) {
|
|
699
|
+
const url = URL.createObjectURL(new Blob([data], { type: mediaType }));
|
|
700
|
+
const anchor = document.createElement("a");
|
|
701
|
+
anchor.href = url;
|
|
702
|
+
anchor.download = filename;
|
|
703
|
+
anchor.rel = "noopener";
|
|
704
|
+
document.body.append(anchor);
|
|
705
|
+
try {
|
|
706
|
+
anchor.click();
|
|
707
|
+
} finally {
|
|
708
|
+
anchor.remove();
|
|
709
|
+
URL.revokeObjectURL(url);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, t }) {
|
|
609
713
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
610
714
|
const [error, setError] = (0, react.useState)(false);
|
|
611
715
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -614,9 +718,9 @@ window.__ModuleLoader__.load({
|
|
|
614
718
|
const [annotationMode, setAnnotationMode] = (0, react.useState)(false);
|
|
615
719
|
const [annotations, setAnnotations] = (0, react.useState)([]);
|
|
616
720
|
const [selectedAnnotation, setSelectedAnnotation] = (0, react.useState)();
|
|
617
|
-
const [prompt, setPrompt] = (0, react.useState)("");
|
|
618
721
|
const [editBusy, setEditBusy] = (0, react.useState)(false);
|
|
619
722
|
const [editError, setEditError] = (0, react.useState)(false);
|
|
723
|
+
const [downloadState, setDownloadState] = (0, react.useState)("idle");
|
|
620
724
|
const triggerRef = (0, react.useRef)(null);
|
|
621
725
|
const dialogRef = (0, react.useRef)(null);
|
|
622
726
|
(0, react.useEffect)(() => {
|
|
@@ -673,7 +777,8 @@ window.__ModuleLoader__.load({
|
|
|
673
777
|
}, [open]);
|
|
674
778
|
const label = attachment.name ?? t("imageLabel");
|
|
675
779
|
const downloadName = imageDownloadName(attachment);
|
|
676
|
-
const
|
|
780
|
+
const previewMeta = attachment.width + " × " + attachment.height + " · " + imageByteSize(attachment.bytes);
|
|
781
|
+
const imageMeta = original === void 0 ? previewMeta : original.width + " × " + original.height + " · " + imageByteSize(original.bytes) + " · " + t("imagePreviewShort") + " " + previewMeta;
|
|
677
782
|
const addAnnotation = (event) => {
|
|
678
783
|
if (!annotationMode || event.target.closest(".codexGeneratedImagePin")) return;
|
|
679
784
|
const bounds = event.currentTarget.getBoundingClientRect();
|
|
@@ -692,7 +797,6 @@ window.__ModuleLoader__.load({
|
|
|
692
797
|
setEditError(false);
|
|
693
798
|
try {
|
|
694
799
|
await attachForEdit(src, downloadName, buildImageEditDraft({
|
|
695
|
-
prompt,
|
|
696
800
|
annotations,
|
|
697
801
|
translate: t
|
|
698
802
|
}));
|
|
@@ -703,31 +807,50 @@ window.__ModuleLoader__.load({
|
|
|
703
807
|
setEditBusy(false);
|
|
704
808
|
}
|
|
705
809
|
};
|
|
810
|
+
const downloadOriginal = async () => {
|
|
811
|
+
if (downloadState === "pending" || original === void 0) return;
|
|
812
|
+
setDownloadState("pending");
|
|
813
|
+
try {
|
|
814
|
+
triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
|
|
815
|
+
setDownloadState("idle");
|
|
816
|
+
} catch {
|
|
817
|
+
setDownloadState("failed");
|
|
818
|
+
throw new Error("original image download failed");
|
|
819
|
+
}
|
|
820
|
+
};
|
|
706
821
|
const openImage = () => {
|
|
707
822
|
if (src === void 0) return;
|
|
708
|
-
|
|
823
|
+
const viewer = getImageViewer?.();
|
|
824
|
+
const actions = [];
|
|
825
|
+
actions.push({
|
|
826
|
+
id: "continue-editing",
|
|
827
|
+
label: t("imageEdit"),
|
|
828
|
+
pendingLabel: t("imageEditPreparing"),
|
|
829
|
+
errorLabel: t("imageEditFailed"),
|
|
830
|
+
closeOnSuccess: true,
|
|
831
|
+
onInvoke: ({ annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
|
|
832
|
+
annotations: nextAnnotations,
|
|
833
|
+
translate: t
|
|
834
|
+
}))
|
|
835
|
+
});
|
|
836
|
+
if (viewer?.open?.({
|
|
709
837
|
items: [{
|
|
710
838
|
id: attachment.attachmentId ?? downloadName,
|
|
711
839
|
src,
|
|
712
840
|
name: label,
|
|
713
841
|
width: attachment.width,
|
|
714
842
|
height: attachment.height,
|
|
715
|
-
bytes: attachment.bytes
|
|
843
|
+
bytes: attachment.bytes,
|
|
844
|
+
download: original === void 0 ? void 0 : {
|
|
845
|
+
pendingLabel: t("imageDownloadPreparing"),
|
|
846
|
+
errorLabel: t("imageDownloadFailed"),
|
|
847
|
+
onInvoke: downloadOriginal
|
|
848
|
+
},
|
|
849
|
+
actions
|
|
716
850
|
}],
|
|
717
851
|
opener: triggerRef.current,
|
|
718
852
|
source: "codex-generated",
|
|
719
|
-
annotations: true
|
|
720
|
-
editor: {
|
|
721
|
-
label: t("imageEdit"),
|
|
722
|
-
busyLabel: t("imageEditPreparing"),
|
|
723
|
-
errorLabel: t("imageEditFailed"),
|
|
724
|
-
placeholder: t("imageEditPrompt"),
|
|
725
|
-
onSubmit: ({ prompt: nextPrompt, annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
|
|
726
|
-
prompt: nextPrompt,
|
|
727
|
-
annotations: nextAnnotations,
|
|
728
|
-
translate: t
|
|
729
|
-
}))
|
|
730
|
-
}
|
|
853
|
+
annotations: true
|
|
731
854
|
}) === true) return;
|
|
732
855
|
setZoom(1);
|
|
733
856
|
setOpen(true);
|
|
@@ -745,151 +868,142 @@ window.__ModuleLoader__.load({
|
|
|
745
868
|
"aria-modal": "true",
|
|
746
869
|
"aria-label": t("imagePreview"),
|
|
747
870
|
tabIndex: -1,
|
|
748
|
-
children: [
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
]
|
|
794
|
-
})]
|
|
795
|
-
}),
|
|
796
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
797
|
-
className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
|
|
798
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
799
|
-
className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
|
|
800
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
801
|
-
className: "codexGeneratedImageSurface",
|
|
802
|
-
onClick: addAnnotation,
|
|
803
|
-
style: { transform: "scale(" + zoom + ")" },
|
|
804
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
805
|
-
src,
|
|
806
|
-
alt: label
|
|
807
|
-
}), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
808
|
-
type: "button",
|
|
809
|
-
className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
|
|
810
|
-
"aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
|
|
811
|
-
style: {
|
|
812
|
-
left: annotation.x * 100 + "%",
|
|
813
|
-
top: annotation.y * 100 + "%"
|
|
814
|
-
},
|
|
815
|
-
onClick: (event) => {
|
|
816
|
-
event.stopPropagation();
|
|
817
|
-
setSelectedAnnotation(annotation.id);
|
|
818
|
-
},
|
|
819
|
-
children: index + 1
|
|
820
|
-
}, annotation.id))]
|
|
821
|
-
}), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
822
|
-
className: "codexGeneratedImageAnnotateHint",
|
|
823
|
-
children: t("imageAnnotateHint")
|
|
824
|
-
}) : null]
|
|
825
|
-
}), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
826
|
-
className: "codexGeneratedImageComments",
|
|
827
|
-
"aria-label": t("imageAnnotate"),
|
|
828
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("imageAnnotate") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("imageAnnotateHint") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
829
|
-
className: "codexGeneratedImageCommentList",
|
|
830
|
-
children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
831
|
-
className: annotation.id === selectedAnnotation ? "is-active" : "",
|
|
832
|
-
onClick: () => setSelectedAnnotation(annotation.id),
|
|
833
|
-
children: [
|
|
834
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
|
|
835
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
836
|
-
value: annotation.note,
|
|
837
|
-
rows: 3,
|
|
838
|
-
"aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
|
|
839
|
-
placeholder: t("imageAnnotationPlaceholder"),
|
|
840
|
-
onFocus: () => setSelectedAnnotation(annotation.id),
|
|
841
|
-
onChange: (event) => {
|
|
842
|
-
const note = event.target.value;
|
|
843
|
-
setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
|
|
844
|
-
...item,
|
|
845
|
-
note
|
|
846
|
-
} : item));
|
|
847
|
-
}
|
|
848
|
-
}),
|
|
849
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
850
|
-
type: "button",
|
|
851
|
-
"aria-label": t("imageRemoveAnnotation"),
|
|
852
|
-
onClick: (event) => {
|
|
853
|
-
event.stopPropagation();
|
|
854
|
-
setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
|
|
855
|
-
if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
|
|
856
|
-
},
|
|
857
|
-
children: "×"
|
|
858
|
-
})
|
|
859
|
-
]
|
|
860
|
-
}, annotation.id))
|
|
861
|
-
})]
|
|
862
|
-
}) : null]
|
|
863
|
-
}),
|
|
864
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
865
|
-
className: "codexGeneratedImageEditDock",
|
|
866
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
867
|
-
className: "codexGeneratedImagePrompt",
|
|
868
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
869
|
-
value: prompt,
|
|
870
|
-
placeholder: t("imageEditPrompt"),
|
|
871
|
-
onChange: (event) => setPrompt(event.target.value),
|
|
872
|
-
onKeyDown: (event) => {
|
|
873
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
874
|
-
event.preventDefault();
|
|
875
|
-
continueEditing();
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
871
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
872
|
+
className: "codexGeneratedImageTopbar",
|
|
873
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: imageMeta })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
874
|
+
className: "codexGeneratedImageActions",
|
|
875
|
+
children: [
|
|
876
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
877
|
+
type: "button",
|
|
878
|
+
className: annotationMode ? "is-active" : "",
|
|
879
|
+
"aria-pressed": annotationMode,
|
|
880
|
+
onClick: () => setAnnotationMode((value) => !value),
|
|
881
|
+
children: t("imageAnnotate")
|
|
882
|
+
}),
|
|
883
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
884
|
+
type: "button",
|
|
885
|
+
"aria-label": t("imageZoomOut"),
|
|
886
|
+
disabled: zoom <= .5,
|
|
887
|
+
onClick: () => setZoom((value) => Math.max(.5, value - .25)),
|
|
888
|
+
children: "−"
|
|
889
|
+
}),
|
|
890
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [Math.round(zoom * 100), "%"] }),
|
|
891
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
892
|
+
type: "button",
|
|
893
|
+
"aria-label": t("imageZoomIn"),
|
|
894
|
+
disabled: zoom >= 3,
|
|
895
|
+
onClick: () => setZoom((value) => Math.min(3, value + .25)),
|
|
896
|
+
children: "+"
|
|
897
|
+
}),
|
|
898
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
899
|
+
type: "button",
|
|
900
|
+
onClick: () => setZoom(1),
|
|
901
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
|
|
902
|
+
}),
|
|
903
|
+
original === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
904
|
+
href: src,
|
|
905
|
+
download: downloadName,
|
|
906
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
|
|
907
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
908
|
+
type: "button",
|
|
909
|
+
disabled: downloadState === "pending",
|
|
910
|
+
onClick: () => {
|
|
911
|
+
downloadOriginal().catch(() => void 0);
|
|
912
|
+
},
|
|
913
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), downloadState === "pending" ? t("imageDownloadPreparing") : downloadState === "failed" ? t("imageDownloadFailed") : t("imageDownload")]
|
|
914
|
+
}),
|
|
915
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
879
916
|
type: "button",
|
|
880
917
|
disabled: editBusy,
|
|
881
918
|
onClick: () => {
|
|
882
919
|
continueEditing();
|
|
883
920
|
},
|
|
884
|
-
children: editBusy ? t("imageEditPreparing") : t("imageEdit")
|
|
885
|
-
})
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
921
|
+
children: editBusy ? t("imageEditPreparing") : editError ? t("imageEditFailed") : t("imageEdit")
|
|
922
|
+
}),
|
|
923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
924
|
+
type: "button",
|
|
925
|
+
className: "codexGeneratedImageClose",
|
|
926
|
+
"aria-label": t("imageClosePreview"),
|
|
927
|
+
onClick: close,
|
|
928
|
+
children: "×"
|
|
929
|
+
})
|
|
930
|
+
]
|
|
931
|
+
})]
|
|
932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
933
|
+
className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
|
|
934
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
935
|
+
className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
|
|
936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
937
|
+
className: "codexGeneratedImageSurface",
|
|
938
|
+
onClick: addAnnotation,
|
|
939
|
+
style: { transform: "scale(" + zoom + ")" },
|
|
940
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
941
|
+
src,
|
|
942
|
+
alt: label
|
|
943
|
+
}), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
944
|
+
type: "button",
|
|
945
|
+
className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
|
|
946
|
+
"aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
|
|
947
|
+
style: {
|
|
948
|
+
left: annotation.x * 100 + "%",
|
|
949
|
+
top: annotation.y * 100 + "%"
|
|
950
|
+
},
|
|
951
|
+
onClick: (event) => {
|
|
952
|
+
event.stopPropagation();
|
|
953
|
+
setSelectedAnnotation(annotation.id);
|
|
954
|
+
},
|
|
955
|
+
children: index + 1
|
|
956
|
+
}, annotation.id))]
|
|
957
|
+
}), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
958
|
+
className: "codexGeneratedImageAnnotateHint",
|
|
959
|
+
children: t("imageAnnotateHint")
|
|
890
960
|
}) : null]
|
|
891
|
-
})
|
|
892
|
-
|
|
961
|
+
}), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
962
|
+
className: "codexGeneratedImageComments",
|
|
963
|
+
"aria-label": t("imageAnnotate"),
|
|
964
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("imageAnnotate") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("imageAnnotateHint") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
965
|
+
className: "codexGeneratedImageCommentList",
|
|
966
|
+
children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
967
|
+
className: annotation.id === selectedAnnotation ? "is-active" : "",
|
|
968
|
+
onClick: () => setSelectedAnnotation(annotation.id),
|
|
969
|
+
children: [
|
|
970
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
|
|
971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
972
|
+
value: annotation.note,
|
|
973
|
+
rows: 3,
|
|
974
|
+
"aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
|
|
975
|
+
placeholder: t("imageAnnotationPlaceholder"),
|
|
976
|
+
onFocus: () => setSelectedAnnotation(annotation.id),
|
|
977
|
+
onChange: (event) => {
|
|
978
|
+
const note = event.target.value;
|
|
979
|
+
setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
|
|
980
|
+
...item,
|
|
981
|
+
note
|
|
982
|
+
} : item));
|
|
983
|
+
},
|
|
984
|
+
onKeyDown: (event) => {
|
|
985
|
+
if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
|
|
986
|
+
event.preventDefault();
|
|
987
|
+
event.stopPropagation();
|
|
988
|
+
setSelectedAnnotation(void 0);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}),
|
|
992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
993
|
+
type: "button",
|
|
994
|
+
"aria-label": t("imageRemoveAnnotation"),
|
|
995
|
+
onClick: (event) => {
|
|
996
|
+
event.stopPropagation();
|
|
997
|
+
setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
|
|
998
|
+
if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
|
|
999
|
+
},
|
|
1000
|
+
children: "×"
|
|
1001
|
+
})
|
|
1002
|
+
]
|
|
1003
|
+
}, annotation.id))
|
|
1004
|
+
})]
|
|
1005
|
+
}) : null]
|
|
1006
|
+
})]
|
|
893
1007
|
}), document.body);
|
|
894
1008
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
895
1009
|
ref: triggerRef,
|
|
@@ -904,13 +1018,14 @@ window.__ModuleLoader__.load({
|
|
|
904
1018
|
})
|
|
905
1019
|
}), lightbox] });
|
|
906
1020
|
}
|
|
907
|
-
function CodexImageToolRow({ block, loadImage, attachForEdit, getImageViewer, t }) {
|
|
1021
|
+
function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, t }) {
|
|
908
1022
|
const settled = block?.kind === "tool-result";
|
|
909
1023
|
const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
|
|
910
1024
|
const failed = settled && block.isError === true;
|
|
911
1025
|
const state = !settled ? "running" : failed ? "error" : "done";
|
|
912
1026
|
const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
|
|
913
1027
|
const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
|
|
1028
|
+
const original = decodeImagePresentation(block?.meta)?.original;
|
|
914
1029
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
915
1030
|
className: "codexImageTool",
|
|
916
1031
|
"data-state": state,
|
|
@@ -940,6 +1055,9 @@ window.__ModuleLoader__.load({
|
|
|
940
1055
|
className: "codexImageToolGallery",
|
|
941
1056
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
|
|
942
1057
|
attachment: image.attachment,
|
|
1058
|
+
original,
|
|
1059
|
+
rpc,
|
|
1060
|
+
sessionId,
|
|
943
1061
|
loadImage,
|
|
944
1062
|
attachForEdit,
|
|
945
1063
|
getImageViewer,
|
|
@@ -1080,6 +1198,18 @@ window.__ModuleLoader__.load({
|
|
|
1080
1198
|
}
|
|
1081
1199
|
const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
1082
1200
|
const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));
|
|
1201
|
+
const formatRunway = (seconds, t) => {
|
|
1202
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return void 0;
|
|
1203
|
+
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
1204
|
+
const days = Math.floor(minutes / 1440);
|
|
1205
|
+
const hours = Math.floor(minutes % 1440 / 60);
|
|
1206
|
+
if (days > 0) return hours > 0 ? fill(t("runwayDaysHours"), {
|
|
1207
|
+
days,
|
|
1208
|
+
hours
|
|
1209
|
+
}) : fill(t("runwayDays"), { days });
|
|
1210
|
+
if (hours > 0) return fill(t("runwayHours"), { hours });
|
|
1211
|
+
return fill(t("runwayMinutes"), { minutes });
|
|
1212
|
+
};
|
|
1083
1213
|
function useQuickQuota(rpc, enabled, model) {
|
|
1084
1214
|
const [quota, setQuota] = (0, react.useState)();
|
|
1085
1215
|
(0, react.useEffect)(() => {
|
|
@@ -1155,7 +1285,12 @@ window.__ModuleLoader__.load({
|
|
|
1155
1285
|
children: [
|
|
1156
1286
|
choice("off", t("quickQuotaOff")),
|
|
1157
1287
|
choice(QUICK_QUOTA_MODE_PERCENT, t("quickQuotaPercent")),
|
|
1158
|
-
choice("bar", t("quickQuotaBar"))
|
|
1288
|
+
choice("bar", t("quickQuotaBar")),
|
|
1289
|
+
choice(QUICK_QUOTA_MODE_FORECAST, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1290
|
+
t("quickQuotaForecast"),
|
|
1291
|
+
" ",
|
|
1292
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("quickQuotaBeta") })
|
|
1293
|
+
] }))
|
|
1159
1294
|
]
|
|
1160
1295
|
})]
|
|
1161
1296
|
});
|
|
@@ -1360,11 +1495,16 @@ window.__ModuleLoader__.load({
|
|
|
1360
1495
|
const current = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot()).current;
|
|
1361
1496
|
const codex = current?.provider === "openai-codex";
|
|
1362
1497
|
const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
|
|
1498
|
+
const forecastMode = preferenceSnapshot.quickQuotaMode === QUICK_QUOTA_MODE_FORECAST;
|
|
1363
1499
|
const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
|
|
1364
1500
|
if (!quotaEnabled || quota === void 0) return null;
|
|
1365
1501
|
const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
|
|
1366
1502
|
const display = percent(value);
|
|
1367
|
-
const
|
|
1503
|
+
const duration = quota.forecast?.status === "ready" ? formatRunway(quota.forecast.runwaySeconds, t) : void 0;
|
|
1504
|
+
const label = duration === void 0 ? fill(t("quickQuotaStatus"), { value: display }) : fill(t("quickQuotaForecastStatus"), {
|
|
1505
|
+
value: display,
|
|
1506
|
+
duration
|
|
1507
|
+
});
|
|
1368
1508
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1369
1509
|
className: "codexComposerQuota",
|
|
1370
1510
|
role: "status",
|
|
@@ -1375,7 +1515,7 @@ window.__ModuleLoader__.load({
|
|
|
1375
1515
|
max: 100,
|
|
1376
1516
|
value,
|
|
1377
1517
|
"aria-hidden": "true"
|
|
1378
|
-
}) : `${display}%`
|
|
1518
|
+
}) : forecastMode && duration !== void 0 ? `${display}% · ${quota.forecast.survivesReset ? "≥" : "≈"}${duration}` : `${display}%`
|
|
1379
1519
|
});
|
|
1380
1520
|
}
|
|
1381
1521
|
function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
|
|
@@ -2281,7 +2421,10 @@ window.__ModuleLoader__.load({
|
|
|
2281
2421
|
}),
|
|
2282
2422
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2283
2423
|
className: "codexSubscriptionLimitMeta",
|
|
2284
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children:
|
|
2424
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: window.forecast?.status === "ready" ? fill(t("quotaForecast"), {
|
|
2425
|
+
symbol: window.forecast.survivesReset ? "≥" : "≈",
|
|
2426
|
+
duration: formatRunway(window.forecast.runwaySeconds, t)
|
|
2427
|
+
}) : windowLabel(window.windowSeconds, t) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetTime, {
|
|
2285
2428
|
resetsAt: window.resetsAt,
|
|
2286
2429
|
t
|
|
2287
2430
|
})]
|
|
@@ -2437,43 +2580,50 @@ window.__ModuleLoader__.load({
|
|
|
2437
2580
|
t
|
|
2438
2581
|
})
|
|
2439
2582
|
}, CodexSection));
|
|
2440
|
-
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
2441
|
-
name: "conversation.input.right",
|
|
2442
|
-
id: "codex-subscription-quota",
|
|
2443
|
-
order: 15,
|
|
2444
|
-
locale: NS,
|
|
2445
|
-
inject: (sessionId) => ({
|
|
2446
|
-
preference,
|
|
2447
|
-
rpc: connection.rpc,
|
|
2448
|
-
t,
|
|
2449
|
-
directory: ctx.modelDirectories.directoryFor(sessionId).store
|
|
2450
|
-
})
|
|
2451
|
-
}, CodexComposerQuota));
|
|
2452
2583
|
const sessions = ctx.get("sessions");
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2584
|
+
const installDirectorySlots = (scope) => {
|
|
2585
|
+
const modelDirectories = scope.get("modelDirectories");
|
|
2586
|
+
scope.slots.inject("conversation.input.right", () => scope.slots.register({
|
|
2587
|
+
name: "conversation.input.right",
|
|
2588
|
+
id: "codex-subscription-quota",
|
|
2589
|
+
order: 15,
|
|
2590
|
+
locale: NS,
|
|
2591
|
+
inject: (sessionId) => ({
|
|
2592
|
+
preference,
|
|
2593
|
+
rpc: connection.rpc,
|
|
2594
|
+
t,
|
|
2595
|
+
directory: modelDirectories.directoryFor(sessionId).store
|
|
2596
|
+
})
|
|
2597
|
+
}, CodexComposerQuota));
|
|
2598
|
+
scope.slots.inject("conversation.input.model", () => scope.slots.register({
|
|
2599
|
+
name: "conversation.input.model",
|
|
2600
|
+
priority: -10,
|
|
2601
|
+
locale: NS,
|
|
2602
|
+
inject: (sessionId) => {
|
|
2603
|
+
const directory = modelDirectories.directoryFor(sessionId);
|
|
2604
|
+
const available = sessions.subagentAddress(sessionId) === void 0;
|
|
2605
|
+
return {
|
|
2606
|
+
available,
|
|
2607
|
+
directory: directory.store,
|
|
2608
|
+
load: () => {
|
|
2609
|
+
if (available) directory.load();
|
|
2610
|
+
},
|
|
2611
|
+
select: (selection) => available ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false),
|
|
2612
|
+
preference
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
}, CodexModelSelect));
|
|
2616
|
+
};
|
|
2617
|
+
if (ctx.get("remote.session") === void 0) installDirectorySlots(ctx);
|
|
2618
|
+
else ctx.inject(["remote.session"], installDirectorySlots);
|
|
2471
2619
|
const conversation = ctx.get("conversation");
|
|
2472
2620
|
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
2473
2621
|
name: "tool.call.toolview",
|
|
2474
2622
|
key: "codex_image_generate",
|
|
2475
2623
|
locale: NS,
|
|
2476
2624
|
inject: (sessionId) => ({
|
|
2625
|
+
sessionId,
|
|
2626
|
+
rpc: connection.rpc,
|
|
2477
2627
|
t,
|
|
2478
2628
|
loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
|
|
2479
2629
|
getImageViewer: () => {
|