dsh-image-gen 0.8.6 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -72,6 +72,27 @@ window.__ModuleLoader__.load({
72
72
  }
73
73
  if (typeof CSS !== "undefined") ensureCssSupports(CSS);
74
74
  //#endregion
75
+ //#region lib/types/watermark.js
76
+ /** Model names alone do not establish the protocol of an unknown relay. */
77
+ function officialContract(config, mode) {
78
+ const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? config.openaiCompatModel;
79
+ if (typeof model !== "string") return void 0;
80
+ try {
81
+ const url = new URL(config.openaiCompatBaseURL ?? "");
82
+ if (url.protocol !== "https:" || url.port || url.search || url.hash) return void 0;
83
+ const path = url.pathname.replace(/\/+$/, "");
84
+ if (url.hostname === "token.sensenova.cn" && path === "/v1" && model === "sensenova-u1.5-lite") return "sensenova";
85
+ if (/^[a-z0-9-]+\.(?:cn-beijing|ap-southeast-1|us-east-1|eu-central-1|ap-northeast-1|cn-hongkong)\.maas\.aliyuncs\.com$/.test(url.hostname) && path === "/compatible-mode/v1" && /^qwen-image-(?:3\.0(?:-pro)?|2\.1-(?:pro|turbo))$/.test(model)) return "qwen";
86
+ } catch {}
87
+ }
88
+ /** Known official edit contracts take precedence over relay-specific format settings. */
89
+ function compatEditFormat(config) {
90
+ const contract = officialContract(config, "edit");
91
+ if (contract === "sensenova") return "jsonImageUrlArray";
92
+ if (contract === "qwen") return "qwenJson";
93
+ return config.openaiCompatEditFormat ?? "multipart";
94
+ }
95
+ //#endregion
75
96
  //#region lib/types/shared.js
76
97
  /** Browser route used by the generated-image card. */
77
98
  const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
@@ -155,6 +176,17 @@ window.__ModuleLoader__.load({
155
176
  function cloudCredentialRef(provider) {
156
177
  return CLOUD_IMAGE_PROVIDERS.includes(provider) ? CLOUD_CREDENTIAL_REFS[provider] : void 0;
157
178
  }
179
+ const STUDIO_WATERMARK_VALUES = [
180
+ "default",
181
+ "off",
182
+ "on"
183
+ ];
184
+ function studioWatermarkCapability(profile, mode) {
185
+ return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
186
+ status: "unknown",
187
+ reason: "channel-unconfirmed"
188
+ });
189
+ }
158
190
  /** Default endpoints and base URLs. */
159
191
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
160
192
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
@@ -1991,21 +2023,119 @@ window.__ModuleLoader__.load({
1991
2023
  ], { type: "application/zip" });
1992
2024
  }
1993
2025
  //#endregion
2026
+ //#region lib/types/openai-output.js
2027
+ const PROVIDER_DEFAULT_QUALITY = "__default__";
2028
+ const DEFAULT_COMPAT_QUALITIES = [
2029
+ "auto",
2030
+ "low",
2031
+ "medium",
2032
+ "high"
2033
+ ];
2034
+ const LEGACY_SIZES = [
2035
+ "1024x1024",
2036
+ "1536x1024",
2037
+ "1024x1536"
2038
+ ];
2039
+ const OPENAI_SIZES = {
2040
+ "1:1": {
2041
+ "1K": "1024x1024",
2042
+ "2K": "2048x2048"
2043
+ },
2044
+ "3:2": {
2045
+ "1K": "1536x1024",
2046
+ "2K": "2304x1536"
2047
+ },
2048
+ "2:3": {
2049
+ "1K": "1024x1536",
2050
+ "2K": "1536x2304"
2051
+ },
2052
+ "4:3": {
2053
+ "1K": "1152x864",
2054
+ "2K": "2048x1536"
2055
+ },
2056
+ "3:4": {
2057
+ "1K": "864x1152",
2058
+ "2K": "1536x2048"
2059
+ },
2060
+ "16:9": {
2061
+ "1K": "1536x864",
2062
+ "2K": "2048x1152",
2063
+ "4K": "3840x2160"
2064
+ },
2065
+ "9:16": {
2066
+ "1K": "864x1536",
2067
+ "2K": "1152x2048",
2068
+ "4K": "2160x3840"
2069
+ }
2070
+ };
2071
+ function flexibleSizeModel(model) {
2072
+ return /^gpt-image-2(?:$|[-.])/.test(model);
2073
+ }
2074
+ function officialSizeTable(config, model) {
2075
+ if (config.openaiSizes && Object.keys(config.openaiSizes).length > 0) return config.openaiSizes;
2076
+ if (flexibleSizeModel(model)) return OPENAI_SIZES;
2077
+ return {
2078
+ "1:1": { "1K": LEGACY_SIZES[0] },
2079
+ "3:2": { "1K": LEGACY_SIZES[1] },
2080
+ "2:3": { "1K": LEGACY_SIZES[2] }
2081
+ };
2082
+ }
2083
+ /** Check the official model contract before spending a request. Relays own their size contract. */
2084
+ function validateOfficialSize(model, size) {
2085
+ if (size === "auto") return;
2086
+ if (!flexibleSizeModel(model)) {
2087
+ if (!LEGACY_SIZES.includes(size)) throw new Error(`模型 ${model} 不支持尺寸 ${size};请使用 1024x1024、1536x1024 或 1024x1536`);
2088
+ return;
2089
+ }
2090
+ const match = /^(\d+)x(\d+)$/.exec(size);
2091
+ const width = Number(match?.[1]);
2092
+ const height = Number(match?.[2]);
2093
+ const pixels = width * height;
2094
+ if (!match || width % 16 !== 0 || height % 16 !== 0 || Math.max(width, height) > 3840 || pixels < 655360 || pixels > 8294400 || Math.max(width, height) > 3 * Math.min(width, height)) throw new Error(`OpenAI 尺寸 ${size} 无效:边长须为 16 的倍数且不超过 3840,比例在 1:3 到 3:1,像素数在 655360 到 8294400`);
2095
+ }
2096
+ //#endregion
1994
2097
  //#region lib/types/client/multi-model-compare.js
1995
2098
  /** Map one shared output intent to settings accepted by every target model. */
1996
- function buildComparisonTargets(profiles, selectedProviders, ratio, quality) {
2099
+ function buildComparisonTargets(profiles, selectedProviders, ratio, quality, output) {
1997
2100
  const selected = new Set(selectedProviders);
1998
2101
  return profiles.filter((profile) => profile.configured && selected.has(profile.provider)).map((profile) => {
1999
2102
  const targetRatio = profile.ratioOptions.some((option) => option.value === ratio) ? ratio : profile.defaultRatio;
2000
- const targetQuality = profile.qualityOptions.some((option) => option.value === quality) ? quality : profile.defaultQuality;
2103
+ const generationOptions = generationQualityOptions(profile);
2104
+ const qualityIntent = output === void 0 ? quality : profile.provider === "openai" || profile.provider === "chatgpt-sub" || profile.provider === "zhipu" ? output.generationQuality : output.resolution;
2105
+ const targetQuality = profile.qualityOptions.some((option) => option.value === qualityIntent) ? qualityIntent : profile.defaultQuality;
2106
+ const resolutions = profile.resolutionOptions?.[targetRatio];
2107
+ const targetResolution = output && resolutions ? resolutions.some((option) => option.value === output.resolution) ? output.resolution : resolutions[0]?.value : void 0;
2108
+ const targetGenerationQuality = output && profile.provider === "openai-compat" ? generationOptions.some((option) => option.value === output.generationQuality) ? output.generationQuality : profile.defaultGenerationQuality ?? "__default__" : void 0;
2001
2109
  return {
2002
2110
  profile,
2003
2111
  ratio: targetRatio,
2004
- quality: targetQuality,
2005
- adjusted: targetRatio !== ratio || targetQuality !== quality
2112
+ quality: profile.provider === "openai-compat" ? targetResolution ?? targetQuality : targetQuality,
2113
+ adjusted: targetRatio !== ratio || targetQuality !== qualityIntent || targetResolution !== void 0 && targetResolution !== output?.resolution || targetGenerationQuality !== void 0 && targetGenerationQuality !== output?.generationQuality,
2114
+ ...targetResolution === void 0 ? {} : { resolution: targetResolution },
2115
+ ...targetGenerationQuality === void 0 ? {} : { generationQuality: targetGenerationQuality }
2006
2116
  };
2007
2117
  });
2008
2118
  }
2119
+ function generationQualityOptions(profile) {
2120
+ return profile.generationQualityOptions ?? ([
2121
+ "openai",
2122
+ "chatgpt-sub",
2123
+ "zhipu"
2124
+ ].includes(profile.provider) ? profile.qualityOptions : []);
2125
+ }
2126
+ /** Keep pixel tiers separate from upstream quality values in comparison mode. */
2127
+ function comparisonOutputOptions(profiles, ratio, kind) {
2128
+ const options = /* @__PURE__ */ new Map();
2129
+ for (const profile of profiles) {
2130
+ if (!profile.configured) continue;
2131
+ const entries = kind === "generationQuality" ? generationQualityOptions(profile) : profile.resolutionOptions ? profile.resolutionOptions[ratio] ?? profile.resolutionOptions[profile.defaultRatio] ?? [] : ["chatgpt-sub", "zhipu"].includes(profile.provider) ? [] : profile.qualityOptions;
2132
+ for (const entry of entries) if (!options.has(entry.value)) options.set(entry.value, {
2133
+ value: entry.value,
2134
+ label: entry.value === "__default__" ? entry.label : entry.value
2135
+ });
2136
+ }
2137
+ return [...options.values()];
2138
+ }
2009
2139
  /** Start with two models, not every configured API, to avoid surprise spend. */
2010
2140
  function initialComparisonProviders(profiles, activeProvider) {
2011
2141
  const configured = profiles.filter((profile) => profile.configured);
@@ -2056,6 +2186,75 @@ window.__ModuleLoader__.load({
2056
2186
  return [...seen.values()].sort((a, b) => rank(a.value) - rank(b.value));
2057
2187
  }
2058
2188
  //#endregion
2189
+ //#region lib/types/client/watermark-control.js
2190
+ const REASONS = {
2191
+ "channel-unconfirmed": {
2192
+ zh: "此渠道尚未确认水印支持,可按接口文档配置对应操作的水印字段。",
2193
+ en: "Watermark support is unconfirmed. Configure the matching operation using the channel documentation."
2194
+ },
2195
+ "model-unconfirmed": {
2196
+ zh: "当前模型尚未确认水印支持。",
2197
+ en: "Watermark support is unconfirmed for this model."
2198
+ },
2199
+ "account-unconfirmed": {
2200
+ zh: "此接口的水印参数和账号开通条件尚未确认。",
2201
+ en: "The watermark parameter and account requirements are unconfirmed for this API."
2202
+ },
2203
+ "edit-format": {
2204
+ zh: "当前图生图请求格式不支持已配置的水印字段。",
2205
+ en: "The configured watermark field is unavailable in this editing format."
2206
+ },
2207
+ "no-api-control": {
2208
+ zh: "此接口没有已确认的可见水印开关。",
2209
+ en: "No visible-watermark control has been confirmed for this API."
2210
+ }
2211
+ };
2212
+ /** Keep the control discoverable while clearly distinguishing confirmed and unknown contracts. */
2213
+ const WatermarkControl = ({ capability, value, onChange, lang }) => {
2214
+ const noteId = (0, react.useId)();
2215
+ const supported = capability.status === "supported";
2216
+ const reason = REASONS[capability.reason ?? "channel-unconfirmed"][lang];
2217
+ return (0, react_jsx_runtime.jsxs)("label", {
2218
+ className: "dsh-ig-field-select",
2219
+ children: [
2220
+ (0, react_jsx_runtime.jsx)("span", {
2221
+ id: `${noteId}-label`,
2222
+ children: lang === "en" ? "Visible watermark" : "可见水印"
2223
+ }),
2224
+ (0, react_jsx_runtime.jsx)("select", {
2225
+ value: supported ? value : "default",
2226
+ disabled: !supported,
2227
+ "aria-labelledby": `${noteId}-label`,
2228
+ "aria-describedby": supported ? void 0 : noteId,
2229
+ title: supported ? lang === "en" ? "Controls the visible watermark added during generation." : "控制生成图片时添加的可见水印。" : reason,
2230
+ onChange: (event) => onChange(event.target.value),
2231
+ children: supported ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2232
+ (0, react_jsx_runtime.jsx)("option", {
2233
+ value: "default",
2234
+ children: lang === "en" ? "Channel default" : "渠道默认"
2235
+ }),
2236
+ (0, react_jsx_runtime.jsx)("option", {
2237
+ value: "off",
2238
+ children: lang === "en" ? "Off" : "关闭"
2239
+ }),
2240
+ (0, react_jsx_runtime.jsx)("option", {
2241
+ value: "on",
2242
+ children: lang === "en" ? "On" : "开启"
2243
+ })
2244
+ ] }) : (0, react_jsx_runtime.jsx)("option", {
2245
+ value: "default",
2246
+ children: capability.status === "unknown" ? lang === "en" ? "Support unconfirmed" : "支持未确认" : lang === "en" ? "No confirmed control" : "无已确认开关"
2247
+ })
2248
+ }),
2249
+ !supported ? (0, react_jsx_runtime.jsx)("small", {
2250
+ id: noteId,
2251
+ className: "dsh-ig-inline-note",
2252
+ children: reason
2253
+ }) : null
2254
+ ]
2255
+ });
2256
+ };
2257
+ //#endregion
2059
2258
  //#region node_modules/.pnpm/@tldraw+utils@5.3.0/node_modules/@tldraw/utils/dist-esm/lib/version.mjs
2060
2259
  const TLDRAW_LIBRARY_VERSION_KEY = "__TLDRAW_LIBRARY_VERSIONS__";
2061
2260
  function getLibraryVersions() {
@@ -117181,6 +117380,9 @@ img.ProseMirror-separator {
117181
117380
  const [model, setModel] = (0, react.useState)("");
117182
117381
  const [ratio, setRatio] = (0, react.useState)("1:1");
117183
117382
  const [quality, setQuality] = (0, react.useState)("1K");
117383
+ const [resolution, setResolution] = (0, react.useState)("1K");
117384
+ const [generationQuality, setGenerationQuality] = (0, react.useState)(PROVIDER_DEFAULT_QUALITY);
117385
+ const [watermark, setWatermark] = (0, react.useState)("default");
117184
117386
  const [prompt, setPrompt] = (0, react.useState)("");
117185
117387
  const [references, setReferences] = (0, react.useState)([]);
117186
117388
  const [count, setCount] = (0, react.useState)(1);
@@ -117422,19 +117624,43 @@ img.ProseMirror-separator {
117422
117624
  const subscriptionSignedInCount = config?.providers.filter((item) => item.configured && isSubscriptionProvider(item.provider)).length ?? 0;
117423
117625
  const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
117424
117626
  const comparisonProfiles = (0, react.useMemo)(() => (config?.providers ?? []).filter((item) => item.configured && (mode === "generate" || item.supportsEditing)), [config, mode]);
117425
- const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality), [
117627
+ const selectedComparisonProfiles = (0, react.useMemo)(() => comparisonProfiles.filter((profile) => comparisonProviders.includes(profile.provider)), [comparisonProfiles, comparisonProviders]);
117628
+ const comparisonResolutionOpts = (0, react.useMemo)(() => comparisonOutputOptions(selectedComparisonProfiles, ratio, "resolution"), [selectedComparisonProfiles, ratio]);
117629
+ const comparisonGenerationQualityOpts = (0, react.useMemo)(() => localizeQualityOptions(comparisonOutputOptions(selectedComparisonProfiles, ratio, "generationQuality"), lang), [
117630
+ selectedComparisonProfiles,
117631
+ ratio,
117632
+ lang
117633
+ ]);
117634
+ const comparisonResolution = comparisonResolutionOpts.some((option) => option.value === resolution) ? resolution : comparisonResolutionOpts[0]?.value ?? "standard";
117635
+ const comparisonGenerationQuality = comparisonGenerationQualityOpts.some((option) => option.value === generationQuality) ? generationQuality : comparisonGenerationQualityOpts[0]?.value ?? "__default__";
117636
+ const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality, {
117637
+ resolution: comparisonResolution,
117638
+ generationQuality: comparisonGenerationQuality
117639
+ }), [
117426
117640
  comparisonProfiles,
117427
117641
  comparisonProviders,
117428
117642
  ratio,
117429
- quality
117643
+ quality,
117644
+ comparisonResolution,
117645
+ comparisonGenerationQuality
117430
117646
  ]);
117647
+ const resolutionOpts = provider === "openai" ? activeProfile?.resolutionOptions?.[ratio] ?? [] : [];
117648
+ const singleQualityOpts = provider === "openai-compat" ? activeProfile?.resolutionOptions?.[ratio] ?? activeProfile?.qualityOptions ?? [] : activeProfile?.qualityOptions ?? [];
117649
+ const selectedQuality = singleQualityOpts.some((option) => option.value === quality) ? quality : singleQualityOpts[0]?.value ?? quality;
117650
+ const selectedResolution = resolutionOpts.some((option) => option.value === resolution) ? resolution : resolutionOpts[0]?.value;
117651
+ const selectedGenerationQuality = activeProfile?.generationQualityOptions?.some((option) => option.value === generationQuality) ? generationQuality : activeProfile?.defaultGenerationQuality ?? "__default__";
117652
+ const watermarkCapability = studioWatermarkCapability(activeProfile, mode);
117653
+ const comparisonWatermarkCapabilities = selectedComparisonProfiles.map((profile) => studioWatermarkCapability(profile, mode));
117654
+ const comparisonWatermarkCapability = comparisonWatermarkCapabilities.find((capability) => capability.status === "supported") ?? comparisonWatermarkCapabilities.find((capability) => capability.status === "unknown") ?? studioWatermarkCapability(selectedComparisonProfiles[0], mode);
117431
117655
  const comparisonRatioOpts = (0, react.useMemo)(() => localizeRatioOptions(comparisonOptionUnion(comparisonProfiles, "ratioOptions"), lang), [comparisonProfiles, lang]);
117432
- const comparisonQualityOpts = (0, react.useMemo)(() => localizeQualityOptions(comparisonOptionUnion(comparisonProfiles, "qualityOptions"), lang), [comparisonProfiles, lang]);
117433
117656
  const applyProvider = (profile) => {
117434
117657
  setProvider(profile.provider);
117435
117658
  setModel(profile.model);
117436
117659
  setRatio(profile.defaultRatio);
117437
117660
  setQuality(profile.defaultQuality);
117661
+ setResolution(profile.defaultResolution ?? profile.defaultQuality);
117662
+ setGenerationQuality(profile.defaultGenerationQuality ?? profile.defaultQuality);
117663
+ setWatermark("default");
117438
117664
  setError(null);
117439
117665
  };
117440
117666
  const changeProvider = (value) => {
@@ -117474,6 +117700,23 @@ img.ProseMirror-separator {
117474
117700
  setSelected(null);
117475
117701
  resetFit();
117476
117702
  };
117703
+ const prepareRegeneration = (item) => {
117704
+ setMode("generate");
117705
+ setComparisonEnabled(false);
117706
+ setPrompt(item.prompt);
117707
+ const profile = config?.providers.find((entry) => entry.provider === item.provider);
117708
+ if (!profile) return;
117709
+ applyProvider(profile);
117710
+ const nextRatio = profile.ratioOptions.some((option) => option.value === item.aspectRatio) ? item.aspectRatio : profile.defaultRatio;
117711
+ setRatio(nextRatio);
117712
+ if (profile.qualityOptions.some((option) => option.value === item.imageSize)) {
117713
+ setQuality(item.imageSize);
117714
+ if (profile.provider === "openai") setGenerationQuality(item.imageSize);
117715
+ }
117716
+ if (profile.resolutionOptions?.[nextRatio]?.some((option) => option.value === item.resolution)) setResolution(item.resolution);
117717
+ if (profile.generationQualityOptions?.some((option) => option.value === item.generationQuality)) setGenerationQuality(item.generationQuality);
117718
+ if (item.watermark && studioWatermarkCapability(profile, "generate").status === "supported") setWatermark(item.watermark);
117719
+ };
117477
117720
  const handleCanvasMouseDown = (e) => {
117478
117721
  if (e.button !== 0 && e.button !== 1) return;
117479
117722
  if (selected === null && (!currentBatch || currentBatch.length === 0)) return;
@@ -117723,6 +117966,9 @@ img.ProseMirror-separator {
117723
117966
  prompt: prompt.trim(),
117724
117967
  ratio: target.ratio,
117725
117968
  quality: target.quality,
117969
+ ...target.resolution === void 0 ? {} : { resolution: target.resolution },
117970
+ ...target.generationQuality === void 0 ? {} : { generationQuality: target.generationQuality },
117971
+ ...studioWatermarkCapability(target.profile, mode).status === "supported" ? { watermark } : {},
117726
117972
  ...workspace?.path ? { workspaceRoot: workspace.path } : {},
117727
117973
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117728
117974
  })
@@ -117750,6 +117996,9 @@ img.ProseMirror-separator {
117750
117996
  createdAt: value.payload.createdAt + idx,
117751
117997
  aspectRatio: value.target.ratio,
117752
117998
  imageSize: value.target.quality,
117999
+ resolution: value.target.resolution,
118000
+ generationQuality: value.target.generationQuality,
118001
+ watermark: studioWatermarkCapability(value.target.profile, mode).status === "supported" ? watermark : void 0,
117753
118002
  output: value.payload.output,
117754
118003
  ...value.payload.savedTo ? { savedTo: value.payload.savedTo } : {},
117755
118004
  ...workspace?.path ? { workspacePath: workspace.path } : {},
@@ -117785,7 +118034,10 @@ img.ProseMirror-separator {
117785
118034
  model,
117786
118035
  prompt: prompt.trim(),
117787
118036
  ratio,
117788
- quality,
118037
+ quality: selectedQuality,
118038
+ ...selectedResolution === void 0 ? {} : { resolution: selectedResolution },
118039
+ ...provider === "openai-compat" ? { generationQuality: selectedGenerationQuality } : {},
118040
+ ...watermarkCapability.status === "supported" ? { watermark } : {},
117789
118041
  ...count > 1 ? { count } : {},
117790
118042
  ...workspace?.path ? { workspaceRoot: workspace.path } : {},
117791
118043
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
@@ -117806,7 +118058,10 @@ img.ProseMirror-separator {
117806
118058
  model: payload.model,
117807
118059
  createdAt: payload.createdAt + idx,
117808
118060
  aspectRatio: ratio,
117809
- imageSize: quality,
118061
+ imageSize: selectedQuality,
118062
+ resolution: selectedResolution,
118063
+ generationQuality: provider === "openai-compat" ? selectedGenerationQuality : void 0,
118064
+ watermark: watermarkCapability.status === "supported" ? watermark : void 0,
117810
118065
  output: gen.output,
117811
118066
  ...gen.savedTo ? { savedTo: gen.savedTo } : {},
117812
118067
  ...workspace?.path ? { workspacePath: workspace.path } : {},
@@ -118537,10 +118792,7 @@ img.ProseMirror-separator {
118537
118792
  children: [(0, react_jsx_runtime.jsx)(PencilLine, { size: 15 }), t("continueEdit")]
118538
118793
  }), (0, react_jsx_runtime.jsxs)("button", {
118539
118794
  type: "button",
118540
- onClick: () => {
118541
- setMode("generate");
118542
- setPrompt(selected.prompt);
118543
- },
118795
+ onClick: () => prepareRegeneration(selected),
118544
118796
  children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 15 }), t("regenerate")]
118545
118797
  })] })]
118546
118798
  }),
@@ -118804,12 +119056,12 @@ img.ProseMirror-separator {
118804
119056
  }),
118805
119057
  target && (0, react_jsx_runtime.jsxs)("span", {
118806
119058
  className: "dsh-ig-model-settings",
118807
- children: [
119059
+ children: [[
118808
119060
  target.ratio,
118809
- " · ",
118810
- target.quality,
118811
- target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
118812
- ]
119061
+ target.resolution,
119062
+ target.quality === target.resolution ? void 0 : target.quality,
119063
+ target.generationQuality === "__default__" ? void 0 : target.generationQuality
119064
+ ].filter(Boolean).join(" · "), target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null]
118813
119065
  })
118814
119066
  ]
118815
119067
  }, item.provider);
@@ -118819,19 +119071,38 @@ img.ProseMirror-separator {
118819
119071
  ]
118820
119072
  }),
118821
119073
  (0, react_jsx_runtime.jsxs)("div", {
118822
- className: "dsh-ig-field-grid",
118823
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118824
- label: t("ratio"),
118825
- value: ratio,
118826
- onChange: setRatio,
118827
- options: comparisonRatioOpts
118828
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118829
- label: t("quality"),
118830
- value: quality,
118831
- onChange: setQuality,
118832
- options: comparisonQualityOpts
118833
- })]
119074
+ className: "dsh-ig-field-grid is-output-grid",
119075
+ children: [
119076
+ (0, react_jsx_runtime.jsx)(FieldSelect, {
119077
+ label: t("ratio"),
119078
+ value: ratio,
119079
+ onChange: setRatio,
119080
+ options: comparisonRatioOpts
119081
+ }),
119082
+ comparisonResolutionOpts.length > 0 ? (0, react_jsx_runtime.jsx)(FieldSelect, {
119083
+ label: lang === "en" ? "Resolution" : "分辨率",
119084
+ value: comparisonResolution,
119085
+ onChange: setResolution,
119086
+ options: comparisonResolutionOpts
119087
+ }) : null,
119088
+ comparisonGenerationQualityOpts.length > 0 ? (0, react_jsx_runtime.jsx)(FieldSelect, {
119089
+ label: lang === "en" ? "Generation quality" : "生成质量",
119090
+ value: comparisonGenerationQuality,
119091
+ onChange: setGenerationQuality,
119092
+ options: comparisonGenerationQualityOpts
119093
+ }) : null,
119094
+ (0, react_jsx_runtime.jsx)(WatermarkControl, {
119095
+ capability: comparisonWatermarkCapability,
119096
+ value: watermark,
119097
+ onChange: setWatermark,
119098
+ lang
119099
+ })
119100
+ ]
118834
119101
  }),
119102
+ comparisonWatermarkCapability.status === "supported" ? (0, react_jsx_runtime.jsx)("div", {
119103
+ className: "dsh-ig-inline-note",
119104
+ children: lang === "en" ? "Watermark applies only to models with confirmed support for this operation." : "水印选项仅用于已确认支持当前操作的模型。"
119105
+ }) : null,
118835
119106
  comparisonTargets.some((target) => target.profile.provider === "chatgpt-sub") && (0, react_jsx_runtime.jsx)("div", {
118836
119107
  className: "dsh-ig-inline-note",
118837
119108
  children: t("subscriptionParameterHint")
@@ -118858,18 +119129,47 @@ img.ProseMirror-separator {
118858
119129
  })]
118859
119130
  }),
118860
119131
  (0, react_jsx_runtime.jsxs)("div", {
118861
- className: "dsh-ig-field-grid",
118862
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118863
- label: t("ratio"),
118864
- value: ratio,
118865
- onChange: setRatio,
118866
- options: localizeRatioOptions(activeProfile?.ratioOptions ?? [], lang)
118867
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118868
- label: t("quality"),
118869
- value: quality,
118870
- onChange: setQuality,
118871
- options: localizeQualityOptions(activeProfile?.qualityOptions ?? [], lang)
118872
- })]
119132
+ className: "dsh-ig-field-grid is-output-grid",
119133
+ children: [
119134
+ (0, react_jsx_runtime.jsx)(FieldSelect, {
119135
+ label: t("ratio"),
119136
+ value: ratio,
119137
+ onChange: setRatio,
119138
+ options: localizeRatioOptions(activeProfile?.ratioOptions ?? [], lang)
119139
+ }),
119140
+ resolutionOpts.length > 0 ? (0, react_jsx_runtime.jsx)(FieldSelect, {
119141
+ label: lang === "en" ? "Resolution" : "分辨率",
119142
+ value: selectedResolution ?? "",
119143
+ onChange: setResolution,
119144
+ options: resolutionOpts
119145
+ }) : null,
119146
+ (0, react_jsx_runtime.jsx)(FieldSelect, {
119147
+ label: [
119148
+ "openai",
119149
+ "chatgpt-sub",
119150
+ "zhipu"
119151
+ ].includes(provider) ? lang === "en" ? "Generation quality" : "生成质量" : lang === "en" ? "Resolution" : "分辨率",
119152
+ value: selectedQuality,
119153
+ onChange: (value) => {
119154
+ setQuality(value);
119155
+ if (provider === "openai") setGenerationQuality(value);
119156
+ else setResolution(value);
119157
+ },
119158
+ options: provider === "openai-compat" ? singleQualityOpts : localizeQualityOptions(singleQualityOpts, lang)
119159
+ }),
119160
+ activeProfile?.generationQualityOptions ? (0, react_jsx_runtime.jsx)(FieldSelect, {
119161
+ label: lang === "en" ? "Generation quality" : "生成质量",
119162
+ value: selectedGenerationQuality,
119163
+ onChange: setGenerationQuality,
119164
+ options: localizeQualityOptions(activeProfile.generationQualityOptions, lang)
119165
+ }) : null,
119166
+ (0, react_jsx_runtime.jsx)(WatermarkControl, {
119167
+ capability: watermarkCapability,
119168
+ value: watermark,
119169
+ onChange: setWatermark,
119170
+ lang
119171
+ })
119172
+ ]
118873
119173
  }),
118874
119174
  provider === "chatgpt-sub" && (0, react_jsx_runtime.jsx)("div", {
118875
119175
  className: "dsh-ig-inline-note",
@@ -119299,7 +119599,7 @@ img.ProseMirror-separator {
119299
119599
  function localizeQualityOptions(options, lang) {
119300
119600
  return options.map((opt) => ({
119301
119601
  value: opt.value,
119302
- label: LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
119602
+ label: opt.value === "__default__" ? lang === "en" ? "Provider default (omit)" : "渠道默认(不发送)" : LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
119303
119603
  }));
119304
119604
  }
119305
119605
  const RecentItem = ({ item, active, onClick }) => {
@@ -121812,17 +122112,20 @@ img.ProseMirror-separator {
121812
122112
  function conversationRegenerateRequest(image, prompt, remembered) {
121813
122113
  if (!CLOUD_IMAGE_PROVIDERS.includes(image.provider)) throw new Error("当前图片使用的 Provider 暂不支持重新生成");
121814
122114
  const provider = image.provider;
121815
- const settings = remembered ?? outputSettings(provider, image.output);
122115
+ const settings = remembered ?? outputSettings(provider, image.output, image.model);
121816
122116
  return {
121817
122117
  mode: "generate",
121818
122118
  provider,
121819
122119
  model: image.model,
121820
122120
  prompt: prompt.trim(),
121821
122121
  ratio: settings.ratio,
121822
- quality: settings.quality
122122
+ quality: settings.quality,
122123
+ ...settings.resolution === void 0 ? {} : { resolution: settings.resolution },
122124
+ ...settings.generationQuality === void 0 ? {} : { generationQuality: settings.generationQuality },
122125
+ ...settings.watermark === void 0 ? {} : { watermark: settings.watermark }
121823
122126
  };
121824
122127
  }
121825
- function outputSettings(provider, output) {
122128
+ function outputSettings(provider, output, model = "") {
121826
122129
  const normalized = (output ?? "").trim();
121827
122130
  if (provider === "google") {
121828
122131
  const [rawRatio, rawQuality] = normalized.split(",").map((value) => value.trim());
@@ -121835,16 +122138,39 @@ img.ProseMirror-separator {
121835
122138
  ratio: "auto",
121836
122139
  quality: normalized === "1K" || normalized === "4K" ? normalized : "2K"
121837
122140
  };
121838
- if (provider === "openai" || provider === "openai-compat" || provider === "xai" || provider === "zhipu") return {
121839
- ratio: ratioFromSize(normalized, "x"),
121840
- quality: "standard"
121841
- };
122141
+ if (provider === "openai" || provider === "openai-compat" || provider === "xai" || provider === "zhipu") {
122142
+ const [size, qualityPart] = normalized.split(",").map((value) => value.trim());
122143
+ const requestedQuality = qualityPart?.startsWith("quality=") ? qualityPart.slice(8) : void 0;
122144
+ const resolution = provider === "openai" ? Object.values(officialSizeTable({}, model)).flatMap((tiers) => Object.entries(tiers)).find(([, pixels]) => pixels === size)?.[0] : void 0;
122145
+ return {
122146
+ ratio: ratioFromSize(size ?? "", "x"),
122147
+ quality: provider === "openai" ? requestedQuality ?? "auto" : "standard",
122148
+ ...resolution === void 0 ? {} : { resolution },
122149
+ ...provider === "openai-compat" && requestedQuality ? { generationQuality: requestedQuality } : {}
122150
+ };
122151
+ }
121842
122152
  return {
121843
122153
  ratio: ratioFromSize(normalized, "*"),
121844
122154
  quality: "standard"
121845
122155
  };
121846
122156
  }
121847
122157
  function ratioFromSize(size, separator) {
122158
+ const dimensions = size.split(separator).map(Number);
122159
+ if (dimensions.length === 2 && dimensions.every((value) => Number.isFinite(value) && value > 0)) {
122160
+ const target = dimensions[0] / dimensions[1];
122161
+ for (const ratio of [
122162
+ "1:1",
122163
+ "3:2",
122164
+ "2:3",
122165
+ "4:3",
122166
+ "3:4",
122167
+ "16:9",
122168
+ "9:16"
122169
+ ]) {
122170
+ const [width, height] = ratio.split(":").map(Number);
122171
+ if (Math.abs(target - width / height) < .015) return ratio;
122172
+ }
122173
+ }
121848
122174
  return (separator === "x" ? {
121849
122175
  "1024x1024": "1:1",
121850
122176
  "1536x1024": "3:2",
@@ -122520,7 +122846,10 @@ img.ProseMirror-separator {
122520
122846
  try {
122521
122847
  const request = conversationRegenerateRequest(previewItem, regeneratePrompt, previewItem.aspectRatio ? {
122522
122848
  ratio: previewItem.aspectRatio,
122523
- quality: previewItem.imageSize || "standard"
122849
+ quality: previewItem.imageSize || "standard",
122850
+ resolution: previewItem.resolution,
122851
+ generationQuality: previewItem.generationQuality,
122852
+ watermark: previewItem.watermark
122524
122853
  } : void 0);
122525
122854
  if (activeWorkspace?.path) request.workspaceRoot = activeWorkspace.path;
122526
122855
  const response = await fetch(STUDIO_ROUTE, {
@@ -122541,6 +122870,9 @@ img.ProseMirror-separator {
122541
122870
  createdAt: payload.createdAt,
122542
122871
  aspectRatio: request.ratio,
122543
122872
  imageSize: request.quality,
122873
+ resolution: request.resolution,
122874
+ generationQuality: request.generationQuality,
122875
+ watermark: request.watermark,
122544
122876
  output: payload.output,
122545
122877
  ...payload.savedTo ? { savedTo: payload.savedTo } : {},
122546
122878
  ...activeWorkspace?.path ? { workspacePath: activeWorkspace.path } : {},
@@ -124070,6 +124402,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124070
124402
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(206px,264px)}
124071
124403
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-generate-collapsed{grid-template-columns:minmax(150px,190px) minmax(0,1fr)}
124072
124404
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-sidebar-collapsed.is-generate-collapsed{grid-template-columns:minmax(0,1fr)}
124405
+ .dsh-ig-field-grid.is-output-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
124073
124406
  .dsh-ig-page-in-sidebar .dsh-ig-recent-item{min-width:0}
124074
124407
  .dsh-ig-page-in-sidebar .dsh-ig-generate-panel .dsh-ig-field-grid{grid-template-columns:1fr}
124075
124408
  .dsh-ig-page-in-sidebar .dsh-ig-tl-canvas{min-height:280px}
@@ -124366,7 +124699,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124366
124699
  function isRevision(value) {
124367
124700
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
124368
124701
  const candidate = value;
124369
- return isAttachment(candidate.attachment) && typeof candidate.provider === "string" && STUDIO_PROVIDERS.includes(candidate.provider) && typeof candidate.prompt === "string" && typeof candidate.model === "string" && typeof candidate.output === "string" && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && typeof candidate.ratio === "string" && typeof candidate.quality === "string";
124702
+ return isAttachment(candidate.attachment) && typeof candidate.provider === "string" && STUDIO_PROVIDERS.includes(candidate.provider) && typeof candidate.prompt === "string" && typeof candidate.model === "string" && typeof candidate.output === "string" && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && typeof candidate.ratio === "string" && typeof candidate.quality === "string" && (candidate.resolution === void 0 || typeof candidate.resolution === "string") && (candidate.generationQuality === void 0 || typeof candidate.generationQuality === "string") && (candidate.watermark === void 0 || STUDIO_WATERMARK_VALUES.some((value) => value === candidate.watermark));
124370
124703
  }
124371
124704
  function isAttachment(value) {
124372
124705
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -124374,6 +124707,31 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124374
124707
  return typeof candidate.attachmentId === "string" && (candidate.mediaType === "image/png" || candidate.mediaType === "image/jpeg" || candidate.mediaType === "image/webp" || candidate.mediaType === "image/gif") && typeof candidate.bytes === "number" && typeof candidate.width === "number" && typeof candidate.height === "number";
124375
124708
  }
124376
124709
  //#endregion
124710
+ //#region lib/types/client/output-settings.js
124711
+ /** Validate all advanced output fields before the settings form starts writing. */
124712
+ function parseExtraFields(text, message) {
124713
+ if (!text.trim()) return {};
124714
+ let value;
124715
+ try {
124716
+ value = JSON.parse(text);
124717
+ } catch {
124718
+ throw new Error(message);
124719
+ }
124720
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(message);
124721
+ return value;
124722
+ }
124723
+ function parseSizeTable(text, message) {
124724
+ const value = parseExtraFields(text, message);
124725
+ const table = {};
124726
+ for (const [ratio, tiers] of Object.entries(value)) {
124727
+ if (!/^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/.test(ratio) || ratio.split(":").some((part) => Number(part) <= 0) || typeof tiers !== "object" || tiers === null || Array.isArray(tiers)) throw new Error(message);
124728
+ const entries = Object.entries(tiers);
124729
+ if (entries.length === 0 || entries.some(([tier, size]) => !/^(\d+(?:\.\d+)?)(K?)$/.test(tier) || Number.parseFloat(tier) <= 0 || typeof size !== "string" || !size.trim())) throw new Error(message);
124730
+ table[ratio] = Object.fromEntries(entries.map(([tier, size]) => [tier, size.trim()]));
124731
+ }
124732
+ return table;
124733
+ }
124734
+ //#endregion
124377
124735
  //#region lib/types/client/settings-write.js
124378
124736
  /** DSH 0.1.7 resolves refused writes to false; older settings scopes resolve to void. */
124379
124737
  async function writeSetting(scope, field, value, rejectedMessage) {
@@ -124817,7 +125175,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124817
125175
  `;
124818
125176
  //#endregion
124819
125177
  //#region lib/types/client/index.js
124820
- console.info(`[dsh-image-gen] canvas bundle 2026-10-08T04:58:18.947Z`);
125178
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-11T07:43:39.734Z`);
124821
125179
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
124822
125180
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
124823
125181
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -124896,9 +125254,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124896
125254
  endpointHintOpenAICompat: "中转站/自建服务的 OpenAI 兼容 /v1 地址(必填),例如 https://your-relay.example.com/v1。",
124897
125255
  editFormat: "图生图请求形态",
124898
125256
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
125257
+ editFormatAutomatic: "自动:官方接口 JSON 格式",
124899
125258
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
124900
125259
  editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
124901
- editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有契约(如商汤 SenseNova 的 JSON images 数组,或要求 reference_images base64 数组的中转)时才需要切换。",
125260
+ editFormatHint: "商汤、通义已确认的官方接口自动使用对应 JSON 格式;其他中转按接口文档选择。文生图不受影响。",
124902
125261
  editExtra: "附加 JSON 字段",
124903
125262
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
124904
125263
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -124915,6 +125274,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124915
125274
  arkBackgroundTransparent: "透明",
124916
125275
  arkBackgroundHint: "仅图生图有效,且要求参考图本身带透明通道;文生图会被方舟拒绝,因此这一项只在图生图时发出。",
124917
125276
  editExtraInvalid: "附加 JSON 字段必须是合法的 JSON 对象。",
125277
+ sizeTable: "自定义分辨率表(JSON)",
125278
+ sizeTableHint: "格式为「比例 → 档位 → 像素尺寸」。留空使用默认尺寸;例如 {\"16:9\":{\"2K\":\"2048x1152\"}}。",
125279
+ sizeTableInvalid: "分辨率表须为「比例 → 数字档位(如 2K)→ 非空尺寸字符串」的 JSON 对象。",
125280
+ defaultQuality: "默认生成质量",
125281
+ defaultQualityHint: "留空不发送 quality。该参数与分辨率独立,上游须支持所填值。",
125282
+ qualityOptions: "可选生成质量(逗号分隔)",
125283
+ generateExtra: "文生图附加字段(JSON)",
125284
+ generateExtraHint: "仅文生图生效,合并到请求体末尾,可覆盖 size 和 quality。关闭商汤水印可填 {\"watermark\":false}。",
125285
+ editSizeMode: "JSON 图生图尺寸",
125286
+ editSizeAuto: "自动(兼容商汤)",
125287
+ editSizeSelected: "使用所选分辨率",
124918
125288
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
124919
125289
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
124920
125290
  endpointHintXAI: "xAI 官方 api.x.ai 的 /v1 地址。",
@@ -125049,9 +125419,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125049
125419
  endpointHintOpenAICompat: "OpenAI-compatible /v1 base URL of your relay or self-hosted service (required), e.g. https://your-relay.example.com/v1.",
125050
125420
  editFormat: "Edit request format",
125051
125421
  editFormatMultipart: "Standard multipart (official OpenAI)",
125422
+ editFormatAutomatic: "Automatic: official JSON contract",
125052
125423
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
125053
125424
  editFormatFormReferenceImages: "Multipart with a reference_images JSON array (base64)",
125054
- editFormatHint: "Text-to-image is unaffected. Only switch when the relay runs image edits on its own contract (e.g. SenseNova's JSON images array, or a relay that takes reference_images as a base64 JSON array); size is pinned to \"auto\" in the JSON images format (the only value SenseNova's edits endpoint accepts) and can be overridden via extra fields.",
125425
+ editFormatHint: "Recognized official SenseNova and Qwen endpoints automatically use their JSON edit contracts. Choose other relay formats according to their documentation. Text-to-image is unaffected.",
125055
125426
  editExtra: "Extra JSON fields",
125056
125427
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
125057
125428
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -125068,6 +125439,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125068
125439
  arkBackgroundTransparent: "Transparent",
125069
125440
  arkBackgroundHint: "Image-to-image only, and every reference image must already carry an alpha channel; Ark rejects it for text-to-image, so it is sent on edits alone.",
125070
125441
  editExtraInvalid: "Extra JSON fields must be a valid JSON object.",
125442
+ sizeTable: "Custom resolution table (JSON)",
125443
+ sizeTableHint: "Aspect ratio → tier → pixel size. Leave empty for defaults; e.g. {\"16:9\":{\"2K\":\"2048x1152\"}}.",
125444
+ sizeTableInvalid: "Use a JSON object mapping ratios to numeric tiers (e.g. 2K) and nonempty size strings.",
125445
+ defaultQuality: "Default generation quality",
125446
+ defaultQualityHint: "Leave empty to omit quality. Independent of resolution; the upstream must support this value.",
125447
+ qualityOptions: "Available generation qualities (comma-separated)",
125448
+ generateExtra: "Generation extra fields (JSON)",
125449
+ generateExtraHint: "Generation only. Merged last; can override size and quality. Use {\"watermark\":false} for SenseNova.",
125450
+ editSizeMode: "JSON edit dimensions",
125451
+ editSizeAuto: "Auto (SenseNova compatible)",
125452
+ editSizeSelected: "Use selected resolution",
125071
125453
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
125072
125454
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
125073
125455
  endpointHintXAI: "Official xAI api.x.ai /v1 base URL.",
@@ -125692,6 +126074,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125692
126074
  timeoutSeconds: DEFAULT_COMFYUI_TIMEOUT_MS / 1e3,
125693
126075
  editFormat: "multipart",
125694
126076
  editExtraText: "",
126077
+ sizeTableText: "",
126078
+ generationQuality: "",
126079
+ qualityOptionsText: DEFAULT_COMPAT_QUALITIES.join(", "),
126080
+ generateExtraText: "",
126081
+ editSizeMode: "auto",
125695
126082
  outputFormat: "jpeg",
125696
126083
  watermark: true,
125697
126084
  background: "opaque"
@@ -125759,6 +126146,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125759
126146
  ...emptyProviderRow(),
125760
126147
  model: modelOf(provider, value),
125761
126148
  baseURL: baseURLOf(provider, value),
126149
+ ...provider === "openai" ? { sizeTableText: editExtraTextOf(value?.openaiSizes) } : {},
125762
126150
  ...provider === "comfyui" ? {
125763
126151
  workflows: resolveComfyUIWorkflows(value ?? {}),
125764
126152
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
@@ -125766,7 +126154,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125766
126154
  } : {},
125767
126155
  ...provider === "openai-compat" ? {
125768
126156
  editFormat: parseEditFormat(value?.openaiCompatEditFormat),
125769
- editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
126157
+ editExtraText: editExtraTextOf(value?.openaiCompatEditExtra),
126158
+ sizeTableText: editExtraTextOf(value?.openaiCompatSizes),
126159
+ generationQuality: value?.openaiCompatQuality ?? "",
126160
+ qualityOptionsText: (value?.openaiCompatQualityOptions ?? DEFAULT_COMPAT_QUALITIES).join(", "),
126161
+ generateExtraText: editExtraTextOf(value?.openaiCompatExtraBody),
126162
+ editSizeMode: value?.openaiCompatEditSizeMode === "selected" ? "selected" : "auto"
125770
126163
  } : {},
125771
126164
  ...provider === "seedream" ? {
125772
126165
  outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
@@ -126133,25 +126526,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126133
126526
  await saveSetting("comfyuiTimeoutMs", Math.max(1, Math.round(row.timeoutSeconds)) * 1e3);
126134
126527
  } else if (isSubscriptionProvider(provider)) await Promise.resolve();
126135
126528
  else {
126136
- let editExtra = {};
126137
- if (provider === "openai-compat") {
126138
- const extraText = row.editExtraText.trim();
126139
- if (extraText.length > 0) {
126140
- let parsed;
126141
- try {
126142
- parsed = JSON.parse(extraText);
126143
- } catch {
126144
- throw new Error(t("editExtraInvalid"));
126145
- }
126146
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(t("editExtraInvalid"));
126147
- editExtra = parsed;
126148
- }
126149
- }
126529
+ const editExtra = provider === "openai-compat" ? parseExtraFields(row.editExtraText, t("editExtraInvalid")) : {};
126530
+ const sizeTable = provider === "openai" || provider === "openai-compat" ? parseSizeTable(row.sizeTableText, t("sizeTableInvalid")) : {};
126531
+ const generateExtra = provider === "openai-compat" ? parseExtraFields(row.generateExtraText, t("editExtraInvalid")) : {};
126532
+ if (provider === "openai") for (const tiers of Object.values(sizeTable)) for (const size of Object.values(tiers)) validateOfficialSize(row.model, size);
126150
126533
  await saveSetting(modelFieldOf(provider), row.model);
126151
126534
  await saveSetting(baseURLFieldOf(provider), row.baseURL);
126535
+ if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126152
126536
  if (provider === "openai-compat") {
126153
126537
  await saveSetting("openaiCompatEditFormat", row.editFormat);
126154
126538
  await saveSetting("openaiCompatEditExtra", editExtra);
126539
+ await saveSetting("openaiCompatSizes", sizeTable);
126540
+ await saveSetting("openaiCompatQuality", row.generationQuality.trim());
126541
+ await saveSetting("openaiCompatQualityOptions", [...new Set(row.qualityOptionsText.split(",").map((value) => value.trim()).filter(Boolean))]);
126542
+ await saveSetting("openaiCompatExtraBody", generateExtra);
126543
+ await saveSetting("openaiCompatEditSizeMode", row.editSizeMode);
126155
126544
  }
126156
126545
  if (provider === "seedream") {
126157
126546
  await saveSetting("seedreamOutputFormat", row.outputFormat);
@@ -126493,6 +126882,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126493
126882
  };
126494
126883
  const renderCloudBody = (provider) => {
126495
126884
  const row = rows[provider];
126885
+ const officialEditFormat = provider === "openai-compat" ? compatEditFormat({
126886
+ openaiCompatBaseURL: row.baseURL,
126887
+ openaiCompatModel: row.model
126888
+ }) : "multipart";
126889
+ const automaticEditFormat = officialEditFormat !== "multipart";
126890
+ const editFormat = automaticEditFormat ? officialEditFormat : row.editFormat;
126496
126891
  const keyRef = cloudCredentialRef(provider) ?? "";
126497
126892
  const keyReadOnly = row.keyInfo?.writable === false;
126498
126893
  const keyUnavailable = !credentialsAvailable();
@@ -126625,12 +127020,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126625
127020
  }),
126626
127021
  (0, react_jsx_runtime.jsxs)("select", {
126627
127022
  className: "dsh-ig-input",
126628
- value: row.editFormat,
127023
+ value: automaticEditFormat ? "__official__" : row.editFormat,
126629
127024
  onChange: (event) => {
126630
127025
  updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
126631
127026
  },
126632
- disabled: !settingsWritable,
127027
+ disabled: !settingsWritable || automaticEditFormat,
126633
127028
  children: [
127029
+ automaticEditFormat ? (0, react_jsx_runtime.jsx)("option", {
127030
+ value: "__official__",
127031
+ children: t("editFormatAutomatic")
127032
+ }) : null,
126634
127033
  (0, react_jsx_runtime.jsx)("option", {
126635
127034
  value: "multipart",
126636
127035
  children: t("editFormatMultipart")
@@ -126737,7 +127136,110 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126737
127136
  ]
126738
127137
  })
126739
127138
  ] }) : null,
126740
- provider === "openai-compat" && row.editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
127139
+ provider === "openai" || provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127140
+ className: "dsh-ig-field",
127141
+ children: [
127142
+ (0, react_jsx_runtime.jsx)("span", {
127143
+ className: "dsh-ig-label",
127144
+ children: t("sizeTable")
127145
+ }),
127146
+ (0, react_jsx_runtime.jsx)("textarea", {
127147
+ className: "dsh-ig-input dsh-ig-textarea",
127148
+ value: row.sizeTableText,
127149
+ onChange: (event) => {
127150
+ updateRow(provider, { sizeTableText: event.target.value });
127151
+ },
127152
+ rows: 3,
127153
+ disabled: !settingsWritable
127154
+ }),
127155
+ (0, react_jsx_runtime.jsx)("span", {
127156
+ className: "dsh-ig-hint",
127157
+ children: t("sizeTableHint")
127158
+ })
127159
+ ]
127160
+ }) : null,
127161
+ provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
127162
+ (0, react_jsx_runtime.jsxs)("label", {
127163
+ className: "dsh-ig-field",
127164
+ children: [
127165
+ (0, react_jsx_runtime.jsx)("span", {
127166
+ className: "dsh-ig-label",
127167
+ children: t("defaultQuality")
127168
+ }),
127169
+ (0, react_jsx_runtime.jsx)("input", {
127170
+ className: "dsh-ig-input",
127171
+ value: row.generationQuality,
127172
+ onChange: (event) => {
127173
+ updateRow(provider, { generationQuality: event.target.value });
127174
+ },
127175
+ placeholder: "high",
127176
+ disabled: !settingsWritable
127177
+ }),
127178
+ (0, react_jsx_runtime.jsx)("span", {
127179
+ className: "dsh-ig-hint",
127180
+ children: t("defaultQualityHint")
127181
+ })
127182
+ ]
127183
+ }),
127184
+ (0, react_jsx_runtime.jsxs)("label", {
127185
+ className: "dsh-ig-field",
127186
+ children: [(0, react_jsx_runtime.jsx)("span", {
127187
+ className: "dsh-ig-label",
127188
+ children: t("qualityOptions")
127189
+ }), (0, react_jsx_runtime.jsx)("input", {
127190
+ className: "dsh-ig-input",
127191
+ value: row.qualityOptionsText,
127192
+ onChange: (event) => {
127193
+ updateRow(provider, { qualityOptionsText: event.target.value });
127194
+ },
127195
+ disabled: !settingsWritable
127196
+ })]
127197
+ }),
127198
+ (0, react_jsx_runtime.jsxs)("label", {
127199
+ className: "dsh-ig-field",
127200
+ children: [
127201
+ (0, react_jsx_runtime.jsx)("span", {
127202
+ className: "dsh-ig-label",
127203
+ children: t("generateExtra")
127204
+ }),
127205
+ (0, react_jsx_runtime.jsx)("textarea", {
127206
+ className: "dsh-ig-input dsh-ig-textarea",
127207
+ value: row.generateExtraText,
127208
+ onChange: (event) => {
127209
+ updateRow(provider, { generateExtraText: event.target.value });
127210
+ },
127211
+ placeholder: "{\"watermark\":false}",
127212
+ rows: 2,
127213
+ disabled: !settingsWritable
127214
+ }),
127215
+ (0, react_jsx_runtime.jsx)("span", {
127216
+ className: "dsh-ig-hint",
127217
+ children: t("generateExtraHint")
127218
+ })
127219
+ ]
127220
+ })
127221
+ ] }) : null,
127222
+ provider === "openai-compat" && (editFormat === "jsonImageUrlArray" || editFormat === "qwenJson") ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
127223
+ className: "dsh-ig-field",
127224
+ children: [(0, react_jsx_runtime.jsx)("span", {
127225
+ className: "dsh-ig-label",
127226
+ children: t("editSizeMode")
127227
+ }), (0, react_jsx_runtime.jsxs)("select", {
127228
+ className: "dsh-ig-input",
127229
+ value: row.editSizeMode,
127230
+ onChange: (event) => {
127231
+ updateRow(provider, { editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
127232
+ },
127233
+ disabled: !settingsWritable,
127234
+ children: [(0, react_jsx_runtime.jsx)("option", {
127235
+ value: "auto",
127236
+ children: t("editSizeAuto")
127237
+ }), (0, react_jsx_runtime.jsx)("option", {
127238
+ value: "selected",
127239
+ children: t("editSizeSelected")
127240
+ })]
127241
+ })]
127242
+ }) : null, (0, react_jsx_runtime.jsxs)("label", {
126741
127243
  className: "dsh-ig-field",
126742
127244
  children: [
126743
127245
  (0, react_jsx_runtime.jsx)("span", {
@@ -126759,7 +127261,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126759
127261
  children: t("editExtraHint")
126760
127262
  })
126761
127263
  ]
126762
- }) : null,
127264
+ })] }) : null,
126763
127265
  (0, react_jsx_runtime.jsxs)("div", {
126764
127266
  className: "dsh-ig-row-actions",
126765
127267
  children: [(0, react_jsx_runtime.jsx)("p", {
@@ -127535,7 +128037,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127535
128037
  try {
127536
128038
  const request = conversationRegenerateRequest(activeResult, regeneratePrompt, selectedRevision === void 0 ? void 0 : {
127537
128039
  ratio: selectedRevision.ratio,
127538
- quality: selectedRevision.quality
128040
+ quality: selectedRevision.quality,
128041
+ resolution: selectedRevision.resolution,
128042
+ generationQuality: selectedRevision.generationQuality,
128043
+ watermark: selectedRevision.watermark
127539
128044
  });
127540
128045
  const response = await fetch(STUDIO_ROUTE, {
127541
128046
  method: "POST",
@@ -127554,7 +128059,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127554
128059
  output: payload.output,
127555
128060
  createdAt: payload.createdAt,
127556
128061
  ratio: request.ratio,
127557
- quality: request.quality
128062
+ quality: request.quality,
128063
+ ...request.resolution === void 0 ? {} : { resolution: request.resolution },
128064
+ ...request.generationQuality === void 0 ? {} : { generationQuality: request.generationQuality },
128065
+ ...request.watermark === void 0 ? {} : { watermark: request.watermark }
127558
128066
  };
127559
128067
  const savedOk = await saveGalleryItem({
127560
128068
  id: String(revision.attachment.attachmentId),
@@ -127565,6 +128073,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127565
128073
  createdAt: revision.createdAt,
127566
128074
  aspectRatio: revision.ratio,
127567
128075
  imageSize: revision.quality,
128076
+ resolution: revision.resolution,
128077
+ generationQuality: revision.generationQuality,
128078
+ watermark: revision.watermark,
127568
128079
  output: revision.output,
127569
128080
  ...savedTo ? { savedTo } : {},
127570
128081
  ...sessionId ? { sessionId } : {}