dsh-image-gen 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -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 } : {},
@@ -123031,9 +123363,33 @@ img.ProseMirror-separator {
123031
123363
  children: activeTab === "gallery" || activeTab === "favorites" ? items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
123032
123364
  className: "dsh-ig-gallery-empty",
123033
123365
  children: [
123034
- (0, react_jsx_runtime.jsx)("div", {
123366
+ (0, react_jsx_runtime.jsxs)("svg", {
123035
123367
  className: "dsh-ig-gallery-empty-icon",
123036
- children: "🖼️"
123368
+ width: "44",
123369
+ height: "44",
123370
+ viewBox: "0 0 24 24",
123371
+ fill: "none",
123372
+ stroke: "currentColor",
123373
+ strokeWidth: "1.5",
123374
+ strokeLinecap: "round",
123375
+ strokeLinejoin: "round",
123376
+ "aria-hidden": "true",
123377
+ children: [
123378
+ (0, react_jsx_runtime.jsx)("rect", {
123379
+ x: "3",
123380
+ y: "3",
123381
+ width: "18",
123382
+ height: "18",
123383
+ rx: "2",
123384
+ ry: "2"
123385
+ }),
123386
+ (0, react_jsx_runtime.jsx)("circle", {
123387
+ cx: "9",
123388
+ cy: "9",
123389
+ r: "2"
123390
+ }),
123391
+ (0, react_jsx_runtime.jsx)("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
123392
+ ]
123037
123393
  }),
123038
123394
  (0, react_jsx_runtime.jsx)("div", {
123039
123395
  className: "dsh-ig-gallery-empty-title",
@@ -123047,9 +123403,18 @@ img.ProseMirror-separator {
123047
123403
  }) : activeTab === "favorites" && filteredItems.length === 0 && search === "" && selectedProvider === "all" && selectedRatio === "all" ? (0, react_jsx_runtime.jsxs)("div", {
123048
123404
  className: "dsh-ig-gallery-empty",
123049
123405
  children: [
123050
- (0, react_jsx_runtime.jsx)("div", {
123406
+ (0, react_jsx_runtime.jsx)("svg", {
123051
123407
  className: "dsh-ig-gallery-empty-icon",
123052
- children: "🤍"
123408
+ width: "44",
123409
+ height: "44",
123410
+ viewBox: "0 0 24 24",
123411
+ fill: "none",
123412
+ stroke: "currentColor",
123413
+ strokeWidth: "1.5",
123414
+ strokeLinecap: "round",
123415
+ strokeLinejoin: "round",
123416
+ "aria-hidden": "true",
123417
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" })
123053
123418
  }),
123054
123419
  (0, react_jsx_runtime.jsx)("div", {
123055
123420
  className: "dsh-ig-gallery-empty-title",
@@ -123063,9 +123428,22 @@ img.ProseMirror-separator {
123063
123428
  }) : filteredItems.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
123064
123429
  className: "dsh-ig-gallery-empty",
123065
123430
  children: [
123066
- (0, react_jsx_runtime.jsx)("div", {
123431
+ (0, react_jsx_runtime.jsxs)("svg", {
123067
123432
  className: "dsh-ig-gallery-empty-icon",
123068
- children: "🔍"
123433
+ width: "44",
123434
+ height: "44",
123435
+ viewBox: "0 0 24 24",
123436
+ fill: "none",
123437
+ stroke: "currentColor",
123438
+ strokeWidth: "1.5",
123439
+ strokeLinecap: "round",
123440
+ strokeLinejoin: "round",
123441
+ "aria-hidden": "true",
123442
+ children: [(0, react_jsx_runtime.jsx)("circle", {
123443
+ cx: "11",
123444
+ cy: "11",
123445
+ r: "8"
123446
+ }), (0, react_jsx_runtime.jsx)("path", { d: "m21 21-4.3-4.3" })]
123069
123447
  }),
123070
123448
  (0, react_jsx_runtime.jsx)("div", {
123071
123449
  className: "dsh-ig-gallery-empty-title",
@@ -124070,6 +124448,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124070
124448
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(206px,264px)}
124071
124449
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-generate-collapsed{grid-template-columns:minmax(150px,190px) minmax(0,1fr)}
124072
124450
  .dsh-ig-page-in-sidebar .dsh-ig-workbench-grid.is-sidebar-collapsed.is-generate-collapsed{grid-template-columns:minmax(0,1fr)}
124451
+ .dsh-ig-field-grid.is-output-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
124073
124452
  .dsh-ig-page-in-sidebar .dsh-ig-recent-item{min-width:0}
124074
124453
  .dsh-ig-page-in-sidebar .dsh-ig-generate-panel .dsh-ig-field-grid{grid-template-columns:1fr}
124075
124454
  .dsh-ig-page-in-sidebar .dsh-ig-tl-canvas{min-height:280px}
@@ -124366,7 +124745,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124366
124745
  function isRevision(value) {
124367
124746
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
124368
124747
  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";
124748
+ 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
124749
  }
124371
124750
  function isAttachment(value) {
124372
124751
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -124374,6 +124753,31 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124374
124753
  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
124754
  }
124376
124755
  //#endregion
124756
+ //#region lib/types/client/output-settings.js
124757
+ /** Validate all advanced output fields before the settings form starts writing. */
124758
+ function parseExtraFields(text, message) {
124759
+ if (!text.trim()) return {};
124760
+ let value;
124761
+ try {
124762
+ value = JSON.parse(text);
124763
+ } catch {
124764
+ throw new Error(message);
124765
+ }
124766
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(message);
124767
+ return value;
124768
+ }
124769
+ function parseSizeTable(text, message) {
124770
+ const value = parseExtraFields(text, message);
124771
+ const table = {};
124772
+ for (const [ratio, tiers] of Object.entries(value)) {
124773
+ 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);
124774
+ const entries = Object.entries(tiers);
124775
+ 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);
124776
+ table[ratio] = Object.fromEntries(entries.map(([tier, size]) => [tier, size.trim()]));
124777
+ }
124778
+ return table;
124779
+ }
124780
+ //#endregion
124377
124781
  //#region lib/types/client/settings-write.js
124378
124782
  /** DSH 0.1.7 resolves refused writes to false; older settings scopes resolve to void. */
124379
124783
  async function writeSetting(scope, field, value, rejectedMessage) {
@@ -124817,7 +125221,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124817
125221
  `;
124818
125222
  //#endregion
124819
125223
  //#region lib/types/client/index.js
124820
- console.info(`[dsh-image-gen] canvas bundle 2026-10-08T04:58:18.947Z`);
125224
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-13T08:42:18.331Z`);
124821
125225
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
124822
125226
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
124823
125227
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -124896,9 +125300,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124896
125300
  endpointHintOpenAICompat: "中转站/自建服务的 OpenAI 兼容 /v1 地址(必填),例如 https://your-relay.example.com/v1。",
124897
125301
  editFormat: "图生图请求形态",
124898
125302
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
125303
+ editFormatAutomatic: "自动:官方接口 JSON 格式",
124899
125304
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
124900
125305
  editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
124901
- editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有契约(如商汤 SenseNova 的 JSON images 数组,或要求 reference_images base64 数组的中转)时才需要切换。",
125306
+ editFormatHint: "商汤、通义已确认的官方接口自动使用对应 JSON 格式;其他中转按接口文档选择。文生图不受影响。",
124902
125307
  editExtra: "附加 JSON 字段",
124903
125308
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
124904
125309
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -124915,6 +125320,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124915
125320
  arkBackgroundTransparent: "透明",
124916
125321
  arkBackgroundHint: "仅图生图有效,且要求参考图本身带透明通道;文生图会被方舟拒绝,因此这一项只在图生图时发出。",
124917
125322
  editExtraInvalid: "附加 JSON 字段必须是合法的 JSON 对象。",
125323
+ sizeTable: "自定义分辨率表(JSON)",
125324
+ sizeTableHint: "格式为「比例 → 档位 → 像素尺寸」。留空使用默认尺寸;例如 {\"16:9\":{\"2K\":\"2048x1152\"}}。",
125325
+ sizeTableInvalid: "分辨率表须为「比例 → 数字档位(如 2K)→ 非空尺寸字符串」的 JSON 对象。",
125326
+ defaultQuality: "默认生成质量",
125327
+ defaultQualityHint: "留空不发送 quality。该参数与分辨率独立,上游须支持所填值。",
125328
+ qualityOptions: "可选生成质量(逗号分隔)",
125329
+ generateExtra: "文生图附加字段(JSON)",
125330
+ generateExtraHint: "仅文生图生效,合并到请求体末尾,可覆盖 size 和 quality。关闭商汤水印可填 {\"watermark\":false}。",
125331
+ editSizeMode: "JSON 图生图尺寸",
125332
+ editSizeAuto: "自动(兼容商汤)",
125333
+ editSizeSelected: "使用所选分辨率",
124918
125334
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
124919
125335
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
124920
125336
  endpointHintXAI: "xAI 官方 api.x.ai 的 /v1 地址。",
@@ -125049,9 +125465,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125049
125465
  endpointHintOpenAICompat: "OpenAI-compatible /v1 base URL of your relay or self-hosted service (required), e.g. https://your-relay.example.com/v1.",
125050
125466
  editFormat: "Edit request format",
125051
125467
  editFormatMultipart: "Standard multipart (official OpenAI)",
125468
+ editFormatAutomatic: "Automatic: official JSON contract",
125052
125469
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
125053
125470
  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.",
125471
+ 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
125472
  editExtra: "Extra JSON fields",
125056
125473
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
125057
125474
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -125068,6 +125485,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125068
125485
  arkBackgroundTransparent: "Transparent",
125069
125486
  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
125487
  editExtraInvalid: "Extra JSON fields must be a valid JSON object.",
125488
+ sizeTable: "Custom resolution table (JSON)",
125489
+ sizeTableHint: "Aspect ratio → tier → pixel size. Leave empty for defaults; e.g. {\"16:9\":{\"2K\":\"2048x1152\"}}.",
125490
+ sizeTableInvalid: "Use a JSON object mapping ratios to numeric tiers (e.g. 2K) and nonempty size strings.",
125491
+ defaultQuality: "Default generation quality",
125492
+ defaultQualityHint: "Leave empty to omit quality. Independent of resolution; the upstream must support this value.",
125493
+ qualityOptions: "Available generation qualities (comma-separated)",
125494
+ generateExtra: "Generation extra fields (JSON)",
125495
+ generateExtraHint: "Generation only. Merged last; can override size and quality. Use {\"watermark\":false} for SenseNova.",
125496
+ editSizeMode: "JSON edit dimensions",
125497
+ editSizeAuto: "Auto (SenseNova compatible)",
125498
+ editSizeSelected: "Use selected resolution",
125071
125499
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
125072
125500
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
125073
125501
  endpointHintXAI: "Official xAI api.x.ai /v1 base URL.",
@@ -125131,20 +125559,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125131
125559
  }
125132
125560
  };
125133
125561
  const STYLE = `
125134
- .dsh-ig-card{list-style:none;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);transition:border-color .16s,background .16s;overflow:hidden}
125135
- .dsh-ig-card:hover{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
125136
- .dsh-ig-card-open{background:var(--dsw-alias-bg-layer-2,#fff);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
125137
- .dsh-ig-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-radius:12px}
125138
- .dsh-ig-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}
125562
+ .dsh-ig-card{list-style:none;border:0;background:none;padding:0}
125563
+ .dsh-ig-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 0 6px}
125139
125564
  .dsh-ig-head-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
125140
125565
  .dsh-ig-title{display:block;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}
125141
125566
  .dsh-ig-desc{display:block;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b)}
125142
- .dsh-ig-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s;display:inline-flex;align-items:center}
125143
- .dsh-ig-chevron-open{transform:rotate(180deg)}
125144
- .dsh-ig-body{border-top:1px solid var(--dsw-alias-border-l2,#eee);padding:0 16px 16px}
125145
- .dsh-ig-field{display:grid;gap:6px;margin-top:14px}
125567
+ .dsh-ig-body{padding:0}
125568
+ .dsh-ig-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}
125569
+ .dsh-ig-field + .dsh-ig-field{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}
125146
125570
  .dsh-ig-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,inherit)}
125147
- .dsh-ig-input{box-sizing:border-box;width:100%;padding:8px 12px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}
125571
+ .dsh-ig-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;font-size:13px;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}
125148
125572
  .dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}
125149
125573
  .dsh-ig-textarea{resize:vertical;min-height:56px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.5}
125150
125574
  .dsh-ig-input-group{display:flex;gap:8px;align-items:center}
@@ -125160,7 +125584,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125160
125584
  .dsh-ig-workflow-active{display:inline-flex;align-items:center;cursor:pointer;flex:none}
125161
125585
  .dsh-ig-workflow-active input[type=radio]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}
125162
125586
  .dsh-ig-workflow-name{flex:1;min-width:0}
125163
- .dsh-ig-btn-reset{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
125587
+ .dsh-ig-btn-reset{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
125164
125588
  .dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
125165
125589
  .dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
125166
125590
  .dsh-ig-hint-error{color:var(--dsw-alias-label-error,#d33)}
@@ -125169,19 +125593,20 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125169
125593
  .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
125170
125594
  .dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
125171
125595
  .dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}
125172
- .dsh-ig-save{appearance:none;border:0;border-radius:8px;padding:6px 16px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
125596
+ .dsh-ig-save{appearance:none;border:0;border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:400;cursor:pointer;transition:opacity .15s}
125173
125597
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
125174
125598
 
125175
125599
  /* Provider list: one expandable row per provider, each saving independently. */
125176
- .dsh-ig-providers{display:grid;gap:10px;margin-top:14px}
125177
- .dsh-ig-provider-row{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;background:var(--dsw-alias-bg-layer-3,transparent);overflow:hidden;transition:border-color .16s}
125178
- .dsh-ig-provider-row-open{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
125179
- .dsh-ig-provider-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:11px 12px}
125180
- .dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}
125600
+ .dsh-ig-providers{display:flex;flex-direction:column;margin-top:6px}
125601
+ .dsh-ig-provider-row{border:0;background:none;padding:14px 0}
125602
+ .dsh-ig-provider-row + .dsh-ig-provider-row{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}
125603
+ .dsh-ig-provider-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}
125604
+ .dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c78ff);outline-offset:2px;border-radius:var(--dsw-radius-sm,4px)}
125605
+ .dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-secondary,inherit)}
125181
125606
  .dsh-ig-provider-name{flex:1;min-width:0;font-size:13.5px;font-weight:550;color:var(--dsw-alias-label-primary,inherit)}
125182
125607
  .dsh-ig-provider-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s;display:inline-flex;align-items:center}
125183
125608
  .dsh-ig-provider-chevron-open{transform:rotate(180deg)}
125184
- .dsh-ig-provider-body{border-top:1px solid var(--dsw-alias-border-l2,#eee);padding:2px 12px 14px}
125609
+ .dsh-ig-provider-body{border-top:0;padding:8px 0 2px}
125185
125610
  .dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;font-size:11.5px;line-height:1.6;padding:2px 9px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}
125186
125611
  .dsh-ig-badge-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
125187
125612
  .dsh-ig-badge-ok{border-color:rgba(34,197,94,.45);color:#15803d;background:rgba(34,197,94,.08)}
@@ -125200,15 +125625,15 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125200
125625
  /* Row-level actions: test connection, clear key, save. */
125201
125626
  .dsh-ig-row-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}
125202
125627
  .dsh-ig-row-buttons{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
125203
- .dsh-ig-btn-secondary{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:6px 14px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s,opacity .15s}
125628
+ .dsh-ig-btn-secondary{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s,opacity .15s}
125204
125629
  .dsh-ig-btn-secondary:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
125205
125630
  .dsh-ig-btn-secondary:disabled{opacity:.45;cursor:default}
125206
125631
  .dsh-ig-btn-danger{color:#b91c1c;border-color:rgba(239,68,68,.4)}
125207
125632
  .dsh-ig-btn-danger:hover:not(:disabled){background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.6)}
125208
125633
 
125209
125634
  /* Workspace section within the settings card. */
125210
- .dsh-ig-section{display:grid;gap:6px;margin-top:16px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
125211
- .dsh-ig-section-title{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary,#7b818b);text-transform:uppercase}
125635
+ .dsh-ig-section{display:flex;flex-direction:column;gap:6px;margin-top:20px;padding-top:14px;border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}
125636
+ .dsh-ig-section-title{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary,inherit)}
125212
125637
  .dsh-ig-status-readonly{color:var(--dsw-alias-label-tertiary,#7b818b);font-style:italic}
125213
125638
 
125214
125639
  .dsh-ig-result{display:grid;gap:10px;max-width:520px}
@@ -125317,7 +125742,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125317
125742
 
125318
125743
  /* Empty State */
125319
125744
  .dsh-ig-gallery-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:360px;text-align:center;color:var(--dsw-alias-label-tertiary,#94a3b8)}
125320
- .dsh-ig-gallery-empty-icon{font-size:44px;margin-bottom:10px}
125745
+ .dsh-ig-gallery-empty-icon{display:block;width:44px;height:44px;margin-bottom:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);opacity:.7}
125321
125746
  .dsh-ig-gallery-empty-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin-bottom:4px}
125322
125747
  .dsh-ig-gallery-empty-desc{font-size:13px;max-width:360px;line-height:1.5}
125323
125748
 
@@ -125509,11 +125934,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125509
125934
  locale,
125510
125935
  credentialEvents
125511
125936
  });
125512
- owner.slots.inject("settings.plugins.tab", () => ownerRegister({
125513
- name: "settings.plugins.tab",
125514
- id: IMAGE_GENERATION_NAMESPACE,
125515
- order: 30,
125516
- label: () => locale?.getSnapshot?.()?.active?.startsWith("en") ? "Image generation" : "图像生成",
125937
+ owner.slots.inject("plugins.bundle.config", () => ownerRegister({
125938
+ name: "plugins.bundle.config",
125939
+ key: "dsh-image-gen",
125517
125940
  locale,
125518
125941
  inject: injectSettingsFace
125519
125942
  }, ImageGenerationSettingsCard));
@@ -125692,6 +126115,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125692
126115
  timeoutSeconds: DEFAULT_COMFYUI_TIMEOUT_MS / 1e3,
125693
126116
  editFormat: "multipart",
125694
126117
  editExtraText: "",
126118
+ sizeTableText: "",
126119
+ generationQuality: "",
126120
+ qualityOptionsText: DEFAULT_COMPAT_QUALITIES.join(", "),
126121
+ generateExtraText: "",
126122
+ editSizeMode: "auto",
125695
126123
  outputFormat: "jpeg",
125696
126124
  watermark: true,
125697
126125
  background: "opaque"
@@ -125759,6 +126187,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125759
126187
  ...emptyProviderRow(),
125760
126188
  model: modelOf(provider, value),
125761
126189
  baseURL: baseURLOf(provider, value),
126190
+ ...provider === "openai" ? { sizeTableText: editExtraTextOf(value?.openaiSizes) } : {},
125762
126191
  ...provider === "comfyui" ? {
125763
126192
  workflows: resolveComfyUIWorkflows(value ?? {}),
125764
126193
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
@@ -125766,7 +126195,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125766
126195
  } : {},
125767
126196
  ...provider === "openai-compat" ? {
125768
126197
  editFormat: parseEditFormat(value?.openaiCompatEditFormat),
125769
- editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
126198
+ editExtraText: editExtraTextOf(value?.openaiCompatEditExtra),
126199
+ sizeTableText: editExtraTextOf(value?.openaiCompatSizes),
126200
+ generationQuality: value?.openaiCompatQuality ?? "",
126201
+ qualityOptionsText: (value?.openaiCompatQualityOptions ?? DEFAULT_COMPAT_QUALITIES).join(", "),
126202
+ generateExtraText: editExtraTextOf(value?.openaiCompatExtraBody),
126203
+ editSizeMode: value?.openaiCompatEditSizeMode === "selected" ? "selected" : "auto"
125770
126204
  } : {},
125771
126205
  ...provider === "seedream" ? {
125772
126206
  outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
@@ -125778,7 +126212,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125778
126212
  }
125779
126213
  /** Edit each provider independently, pick an explicit default, and verify keys inline. */
125780
126214
  function ImageGenerationSettingsCard(props) {
125781
- const [open, setOpen] = (0, react.useState)(false);
126215
+ const [open] = (0, react.useState)(true);
125782
126216
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
125783
126217
  const [health, setHealth] = (0, react.useState)();
125784
126218
  const state = settingsState(snapshot, health);
@@ -126133,25 +126567,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126133
126567
  await saveSetting("comfyuiTimeoutMs", Math.max(1, Math.round(row.timeoutSeconds)) * 1e3);
126134
126568
  } else if (isSubscriptionProvider(provider)) await Promise.resolve();
126135
126569
  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
- }
126570
+ const editExtra = provider === "openai-compat" ? parseExtraFields(row.editExtraText, t("editExtraInvalid")) : {};
126571
+ const sizeTable = provider === "openai" || provider === "openai-compat" ? parseSizeTable(row.sizeTableText, t("sizeTableInvalid")) : {};
126572
+ const generateExtra = provider === "openai-compat" ? parseExtraFields(row.generateExtraText, t("editExtraInvalid")) : {};
126573
+ if (provider === "openai") for (const tiers of Object.values(sizeTable)) for (const size of Object.values(tiers)) validateOfficialSize(row.model, size);
126150
126574
  await saveSetting(modelFieldOf(provider), row.model);
126151
126575
  await saveSetting(baseURLFieldOf(provider), row.baseURL);
126576
+ if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126152
126577
  if (provider === "openai-compat") {
126153
126578
  await saveSetting("openaiCompatEditFormat", row.editFormat);
126154
126579
  await saveSetting("openaiCompatEditExtra", editExtra);
126580
+ await saveSetting("openaiCompatSizes", sizeTable);
126581
+ await saveSetting("openaiCompatQuality", row.generationQuality.trim());
126582
+ await saveSetting("openaiCompatQualityOptions", [...new Set(row.qualityOptionsText.split(",").map((value) => value.trim()).filter(Boolean))]);
126583
+ await saveSetting("openaiCompatExtraBody", generateExtra);
126584
+ await saveSetting("openaiCompatEditSizeMode", row.editSizeMode);
126155
126585
  }
126156
126586
  if (provider === "seedream") {
126157
126587
  await saveSetting("seedreamOutputFormat", row.outputFormat);
@@ -126493,6 +126923,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126493
126923
  };
126494
126924
  const renderCloudBody = (provider) => {
126495
126925
  const row = rows[provider];
126926
+ const officialEditFormat = provider === "openai-compat" ? compatEditFormat({
126927
+ openaiCompatBaseURL: row.baseURL,
126928
+ openaiCompatModel: row.model
126929
+ }) : "multipart";
126930
+ const automaticEditFormat = officialEditFormat !== "multipart";
126931
+ const editFormat = automaticEditFormat ? officialEditFormat : row.editFormat;
126496
126932
  const keyRef = cloudCredentialRef(provider) ?? "";
126497
126933
  const keyReadOnly = row.keyInfo?.writable === false;
126498
126934
  const keyUnavailable = !credentialsAvailable();
@@ -126625,12 +127061,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126625
127061
  }),
126626
127062
  (0, react_jsx_runtime.jsxs)("select", {
126627
127063
  className: "dsh-ig-input",
126628
- value: row.editFormat,
127064
+ value: automaticEditFormat ? "__official__" : row.editFormat,
126629
127065
  onChange: (event) => {
126630
127066
  updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
126631
127067
  },
126632
- disabled: !settingsWritable,
127068
+ disabled: !settingsWritable || automaticEditFormat,
126633
127069
  children: [
127070
+ automaticEditFormat ? (0, react_jsx_runtime.jsx)("option", {
127071
+ value: "__official__",
127072
+ children: t("editFormatAutomatic")
127073
+ }) : null,
126634
127074
  (0, react_jsx_runtime.jsx)("option", {
126635
127075
  value: "multipart",
126636
127076
  children: t("editFormatMultipart")
@@ -126737,7 +127177,110 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126737
127177
  ]
126738
127178
  })
126739
127179
  ] }) : null,
126740
- provider === "openai-compat" && row.editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
127180
+ provider === "openai" || provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127181
+ className: "dsh-ig-field",
127182
+ children: [
127183
+ (0, react_jsx_runtime.jsx)("span", {
127184
+ className: "dsh-ig-label",
127185
+ children: t("sizeTable")
127186
+ }),
127187
+ (0, react_jsx_runtime.jsx)("textarea", {
127188
+ className: "dsh-ig-input dsh-ig-textarea",
127189
+ value: row.sizeTableText,
127190
+ onChange: (event) => {
127191
+ updateRow(provider, { sizeTableText: event.target.value });
127192
+ },
127193
+ rows: 3,
127194
+ disabled: !settingsWritable
127195
+ }),
127196
+ (0, react_jsx_runtime.jsx)("span", {
127197
+ className: "dsh-ig-hint",
127198
+ children: t("sizeTableHint")
127199
+ })
127200
+ ]
127201
+ }) : null,
127202
+ provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
127203
+ (0, react_jsx_runtime.jsxs)("label", {
127204
+ className: "dsh-ig-field",
127205
+ children: [
127206
+ (0, react_jsx_runtime.jsx)("span", {
127207
+ className: "dsh-ig-label",
127208
+ children: t("defaultQuality")
127209
+ }),
127210
+ (0, react_jsx_runtime.jsx)("input", {
127211
+ className: "dsh-ig-input",
127212
+ value: row.generationQuality,
127213
+ onChange: (event) => {
127214
+ updateRow(provider, { generationQuality: event.target.value });
127215
+ },
127216
+ placeholder: "high",
127217
+ disabled: !settingsWritable
127218
+ }),
127219
+ (0, react_jsx_runtime.jsx)("span", {
127220
+ className: "dsh-ig-hint",
127221
+ children: t("defaultQualityHint")
127222
+ })
127223
+ ]
127224
+ }),
127225
+ (0, react_jsx_runtime.jsxs)("label", {
127226
+ className: "dsh-ig-field",
127227
+ children: [(0, react_jsx_runtime.jsx)("span", {
127228
+ className: "dsh-ig-label",
127229
+ children: t("qualityOptions")
127230
+ }), (0, react_jsx_runtime.jsx)("input", {
127231
+ className: "dsh-ig-input",
127232
+ value: row.qualityOptionsText,
127233
+ onChange: (event) => {
127234
+ updateRow(provider, { qualityOptionsText: event.target.value });
127235
+ },
127236
+ disabled: !settingsWritable
127237
+ })]
127238
+ }),
127239
+ (0, react_jsx_runtime.jsxs)("label", {
127240
+ className: "dsh-ig-field",
127241
+ children: [
127242
+ (0, react_jsx_runtime.jsx)("span", {
127243
+ className: "dsh-ig-label",
127244
+ children: t("generateExtra")
127245
+ }),
127246
+ (0, react_jsx_runtime.jsx)("textarea", {
127247
+ className: "dsh-ig-input dsh-ig-textarea",
127248
+ value: row.generateExtraText,
127249
+ onChange: (event) => {
127250
+ updateRow(provider, { generateExtraText: event.target.value });
127251
+ },
127252
+ placeholder: "{\"watermark\":false}",
127253
+ rows: 2,
127254
+ disabled: !settingsWritable
127255
+ }),
127256
+ (0, react_jsx_runtime.jsx)("span", {
127257
+ className: "dsh-ig-hint",
127258
+ children: t("generateExtraHint")
127259
+ })
127260
+ ]
127261
+ })
127262
+ ] }) : null,
127263
+ 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", {
127264
+ className: "dsh-ig-field",
127265
+ children: [(0, react_jsx_runtime.jsx)("span", {
127266
+ className: "dsh-ig-label",
127267
+ children: t("editSizeMode")
127268
+ }), (0, react_jsx_runtime.jsxs)("select", {
127269
+ className: "dsh-ig-input",
127270
+ value: row.editSizeMode,
127271
+ onChange: (event) => {
127272
+ updateRow(provider, { editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
127273
+ },
127274
+ disabled: !settingsWritable,
127275
+ children: [(0, react_jsx_runtime.jsx)("option", {
127276
+ value: "auto",
127277
+ children: t("editSizeAuto")
127278
+ }), (0, react_jsx_runtime.jsx)("option", {
127279
+ value: "selected",
127280
+ children: t("editSizeSelected")
127281
+ })]
127282
+ })]
127283
+ }) : null, (0, react_jsx_runtime.jsxs)("label", {
126741
127284
  className: "dsh-ig-field",
126742
127285
  children: [
126743
127286
  (0, react_jsx_runtime.jsx)("span", {
@@ -126759,7 +127302,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126759
127302
  children: t("editExtraHint")
126760
127303
  })
126761
127304
  ]
126762
- }) : null,
127305
+ })] }) : null,
126763
127306
  (0, react_jsx_runtime.jsxs)("div", {
126764
127307
  className: "dsh-ig-row-actions",
126765
127308
  children: [(0, react_jsx_runtime.jsx)("p", {
@@ -127102,7 +127645,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127102
127645
  const row = rows[provider];
127103
127646
  const badge = badgeOf(provider);
127104
127647
  return (0, react_jsx_runtime.jsxs)("div", {
127105
- className: `dsh-ig-provider-row ${row.expanded ? "dsh-ig-provider-row-open" : ""}`,
127648
+ className: "dsh-ig-provider-row",
127106
127649
  children: [(0, react_jsx_runtime.jsxs)("button", {
127107
127650
  type: "button",
127108
127651
  className: "dsh-ig-provider-head",
@@ -127143,15 +127686,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127143
127686
  }, provider);
127144
127687
  };
127145
127688
  return (0, react_jsx_runtime.jsxs)("li", {
127146
- className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
127147
- children: [(0, react_jsx_runtime.jsxs)("button", {
127148
- type: "button",
127689
+ className: "dsh-ig-card",
127690
+ children: [(0, react_jsx_runtime.jsx)("div", {
127149
127691
  className: "dsh-ig-head",
127150
- "aria-expanded": open,
127151
- onClick: () => {
127152
- setOpen((value) => !value);
127153
- },
127154
- children: [(0, react_jsx_runtime.jsxs)("span", {
127692
+ children: (0, react_jsx_runtime.jsxs)("span", {
127155
127693
  className: "dsh-ig-head-text",
127156
127694
  children: [(0, react_jsx_runtime.jsx)("span", {
127157
127695
  className: "dsh-ig-title",
@@ -127160,22 +127698,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127160
127698
  className: "dsh-ig-desc",
127161
127699
  children: t("description")
127162
127700
  })]
127163
- }), (0, react_jsx_runtime.jsx)("span", {
127164
- className: `dsh-ig-chevron ${open ? "dsh-ig-chevron-open" : ""}`,
127165
- "aria-hidden": "true",
127166
- children: (0, react_jsx_runtime.jsx)("svg", {
127167
- width: "14",
127168
- height: "14",
127169
- viewBox: "0 0 16 16",
127170
- fill: "none",
127171
- stroke: "currentColor",
127172
- strokeWidth: "2",
127173
- strokeLinecap: "round",
127174
- strokeLinejoin: "round",
127175
- children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
127176
- })
127177
- })]
127178
- }), open ? (0, react_jsx_runtime.jsxs)("div", {
127701
+ })
127702
+ }), (0, react_jsx_runtime.jsxs)("div", {
127179
127703
  className: "dsh-ig-body",
127180
127704
  children: [
127181
127705
  settingsMessage !== void 0 ? (0, react_jsx_runtime.jsx)("p", {
@@ -127319,7 +127843,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127319
127843
  })]
127320
127844
  })
127321
127845
  ]
127322
- }) : null]
127846
+ })]
127323
127847
  });
127324
127848
  }
127325
127849
  /** Keep the legacy Tool row for old DSH and hand modern results to the independent Chat node. */
@@ -127535,7 +128059,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127535
128059
  try {
127536
128060
  const request = conversationRegenerateRequest(activeResult, regeneratePrompt, selectedRevision === void 0 ? void 0 : {
127537
128061
  ratio: selectedRevision.ratio,
127538
- quality: selectedRevision.quality
128062
+ quality: selectedRevision.quality,
128063
+ resolution: selectedRevision.resolution,
128064
+ generationQuality: selectedRevision.generationQuality,
128065
+ watermark: selectedRevision.watermark
127539
128066
  });
127540
128067
  const response = await fetch(STUDIO_ROUTE, {
127541
128068
  method: "POST",
@@ -127554,7 +128081,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127554
128081
  output: payload.output,
127555
128082
  createdAt: payload.createdAt,
127556
128083
  ratio: request.ratio,
127557
- quality: request.quality
128084
+ quality: request.quality,
128085
+ ...request.resolution === void 0 ? {} : { resolution: request.resolution },
128086
+ ...request.generationQuality === void 0 ? {} : { generationQuality: request.generationQuality },
128087
+ ...request.watermark === void 0 ? {} : { watermark: request.watermark }
127558
128088
  };
127559
128089
  const savedOk = await saveGalleryItem({
127560
128090
  id: String(revision.attachment.attachmentId),
@@ -127565,6 +128095,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127565
128095
  createdAt: revision.createdAt,
127566
128096
  aspectRatio: revision.ratio,
127567
128097
  imageSize: revision.quality,
128098
+ resolution: revision.resolution,
128099
+ generationQuality: revision.generationQuality,
128100
+ watermark: revision.watermark,
127568
128101
  output: revision.output,
127569
128102
  ...savedTo ? { savedTo } : {},
127570
128103
  ...sessionId ? { sessionId } : {}