dsh-image-gen 0.8.0 → 0.8.6

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
@@ -87,6 +87,8 @@ window.__ModuleLoader__.load({
87
87
  const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
88
88
  /** Browser route the settings card probes provider connectivity through. */
89
89
  const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
90
+ /** Non-secret settings capability probe, available even when the host cannot serve a form. */
91
+ const SETTINGS_HEALTH_ROUTE = "/plugins/dsh-image-gen/settings-health";
90
92
  /** Browser route the workbench infinite canvas pushes live state through. */
91
93
  const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
92
94
  /** Upload a selected canvas original to the host attachment store. */
@@ -217,7 +219,7 @@ window.__ModuleLoader__.load({
217
219
  const DEFAULT_SUBSCRIPTION_MODELS = {
218
220
  "chatgpt-sub": "gpt-image-2.5-flare",
219
221
  "grok-sub": "grok-imagine-image-2.0",
220
- "google-sub": "gemini-3-pro-image"
222
+ "google-sub": "gemini-3.1-flash-image"
221
223
  };
222
224
  const DEFAULT_MODELS = {
223
225
  google: DEFAULT_GOOGLE_MODEL,
@@ -246,6 +248,38 @@ window.__ModuleLoader__.load({
246
248
  "google-sub": ""
247
249
  };
248
250
  //#endregion
251
+ //#region lib/types/client/settings-state.js
252
+ /** Preserve modern form states while accepting legacy scopes without status/mode. */
253
+ function settingsState(snapshot, health) {
254
+ if (snapshot.mode === "memory") return "remote";
255
+ if (health?.settings === "live" && !health.liveSchema) return "incompatible";
256
+ if (snapshot.status === "loading") return "loading";
257
+ if (snapshot.status === "unavailable") return "unavailable";
258
+ return snapshot.writable ? "ready" : "readOnly";
259
+ }
260
+ /** A failed probe cannot identify the cause; keep the host's own form state. */
261
+ async function fetchSettingsHealth(signal) {
262
+ try {
263
+ const response = await fetch(SETTINGS_HEALTH_ROUTE, {
264
+ cache: "no-store",
265
+ signal
266
+ });
267
+ if (!response.ok) return void 0;
268
+ const health = await response.json();
269
+ if (health === null || typeof health.liveSchema !== "boolean" || ![
270
+ "live",
271
+ "legacy",
272
+ "unavailable"
273
+ ].includes(health.settings ?? "")) return void 0;
274
+ return {
275
+ settings: health.settings,
276
+ liveSchema: health.liveSchema
277
+ };
278
+ } catch {
279
+ return;
280
+ }
281
+ }
282
+ //#endregion
249
283
  //#region lib/types/comfyui-workflow.js
250
284
  /** Pure ComfyUI API-workflow validation and placeholder injection. */
251
285
  const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
@@ -1978,6 +2012,49 @@ window.__ModuleLoader__.load({
1978
2012
  const active = configured.find((profile) => profile.provider === activeProvider);
1979
2013
  return (active === void 0 ? configured : [active, ...configured.filter((profile) => profile.provider !== activeProvider)]).slice(0, 2).map((profile) => profile.provider);
1980
2014
  }
2015
+ const RATIO_SORT_ORDER = [
2016
+ "auto",
2017
+ "1:1",
2018
+ "3:2",
2019
+ "2:3",
2020
+ "4:3",
2021
+ "3:4",
2022
+ "4:5",
2023
+ "5:4",
2024
+ "16:9",
2025
+ "9:16",
2026
+ "21:9"
2027
+ ];
2028
+ const QUALITY_SORT_ORDER = [
2029
+ "auto",
2030
+ "standard",
2031
+ "1k",
2032
+ "1K",
2033
+ "2k",
2034
+ "2K",
2035
+ "3K",
2036
+ "4K",
2037
+ "low",
2038
+ "medium",
2039
+ "high",
2040
+ "xhigh",
2041
+ "max",
2042
+ "hd"
2043
+ ];
2044
+ /** Union of every configured profile's options, for the shared comparison pickers. */
2045
+ function comparisonOptionUnion(profiles, key) {
2046
+ const sortOrder = key === "ratioOptions" ? RATIO_SORT_ORDER : QUALITY_SORT_ORDER;
2047
+ const seen = /* @__PURE__ */ new Map();
2048
+ for (const profile of profiles) {
2049
+ if (!profile.configured) continue;
2050
+ for (const option of profile[key]) if (!seen.has(option.value)) seen.set(option.value, option);
2051
+ }
2052
+ const rank = (value) => {
2053
+ const index = sortOrder.indexOf(value);
2054
+ return index === -1 ? sortOrder.length : index;
2055
+ };
2056
+ return [...seen.values()].sort((a, b) => rank(a.value) - rank(b.value));
2057
+ }
1981
2058
  //#endregion
1982
2059
  //#region node_modules/.pnpm/@tldraw+utils@5.3.0/node_modules/@tldraw/utils/dist-esm/lib/version.mjs
1983
2060
  const TLDRAW_LIBRARY_VERSION_KEY = "__TLDRAW_LIBRARY_VERSIONS__";
@@ -116834,6 +116911,8 @@ img.ProseMirror-separator {
116834
116911
  model: "Model",
116835
116912
  ratio: "比例",
116836
116913
  quality: "清晰度",
116914
+ subscriptionParameterHint: "所选比例会自动补充到生成提示词中;实际尺寸和清晰度仍由 ChatGPT 决定,请以生成结果为准。",
116915
+ subscriptionRatioMismatch: "ChatGPT 订阅未按所选比例出图,请查看实际尺寸。",
116837
116916
  start: "开始生成",
116838
116917
  generating: "正在生成…",
116839
116918
  cancelGenerate: "取消生成",
@@ -116971,6 +117050,8 @@ img.ProseMirror-separator {
116971
117050
  model: "Model",
116972
117051
  ratio: "Aspect ratio",
116973
117052
  quality: "Quality",
117053
+ subscriptionParameterHint: "The selected aspect ratio is automatically added to the generation prompt. ChatGPT still determines the final dimensions and quality; check the generated result.",
117054
+ subscriptionRatioMismatch: "ChatGPT subscription returned a different aspect ratio. Check the actual dimensions.",
116974
117055
  start: "Generate",
116975
117056
  generating: "Generating…",
116976
117057
  cancelGenerate: "Cancel",
@@ -117185,8 +117266,6 @@ img.ProseMirror-separator {
117185
117266
  const maxReferences = comparisonEnabled ? comparisonProviders.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
117186
117267
  const referencesRef = (0, react.useRef)(references);
117187
117268
  referencesRef.current = references;
117188
- const comparisonSelectionsAllSubscription = comparisonEnabled && comparisonProviders.length > 0 && comparisonProviders.every((provider) => isSubscriptionProvider(provider));
117189
- const singleProviderIsSubscription = !comparisonEnabled && isSubscriptionProvider(provider);
117190
117269
  const t = (key, values) => {
117191
117270
  let text = COPY$1[lang][key];
117192
117271
  for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
@@ -117349,6 +117428,8 @@ img.ProseMirror-separator {
117349
117428
  ratio,
117350
117429
  quality
117351
117430
  ]);
117431
+ 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]);
117352
117433
  const applyProvider = (profile) => {
117353
117434
  setProvider(profile.provider);
117354
117435
  setModel(profile.model);
@@ -117690,6 +117771,7 @@ img.ProseMirror-separator {
117690
117771
  success: String(successes.length),
117691
117772
  failed: String(failed)
117692
117773
  }));
117774
+ else if (galleryEntries.some(hasSubscriptionRatioMismatch)) flash(t("subscriptionRatioMismatch"));
117693
117775
  return;
117694
117776
  }
117695
117777
  const response = await fetch(STUDIO_ROUTE, {
@@ -117753,6 +117835,7 @@ img.ProseMirror-separator {
117753
117835
  success: String(generatedList.length),
117754
117836
  failed: String(payload.failedCount)
117755
117837
  }));
117838
+ else if (galleryEntries.some(hasSubscriptionRatioMismatch)) flash(t("subscriptionRatioMismatch"));
117756
117839
  } catch (submitError) {
117757
117840
  if (controller.signal.aborted) return;
117758
117841
  setError(messageOf(submitError));
@@ -118505,7 +118588,8 @@ img.ProseMirror-separator {
118505
118588
  (0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(selected.createdAt, lang) }),
118506
118589
  (0, react_jsx_runtime.jsx)("span", { children: selected.provider }),
118507
118590
  (0, react_jsx_runtime.jsx)("span", { children: selected.model }),
118508
- (0, react_jsx_runtime.jsx)("span", { children: selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" })
118591
+ (0, react_jsx_runtime.jsx)("span", { children: selected.provider === "chatgpt-sub" ? selected.output : selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" }),
118592
+ hasSubscriptionRatioMismatch(selected) && (0, react_jsx_runtime.jsx)("span", { children: t("subscriptionRatioMismatch") })
118509
118593
  ]
118510
118594
  })
118511
118595
  ] })
@@ -118689,82 +118773,70 @@ img.ProseMirror-separator {
118689
118773
  onClick: () => toggleComparison(true),
118690
118774
  children: [(0, react_jsx_runtime.jsx)(Copy, { size: 13 }), t("compareModels")]
118691
118775
  })]
118692
- }), comparisonEnabled ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
118693
- className: "dsh-ig-compare-box",
118694
- children: [
118695
- (0, react_jsx_runtime.jsxs)("div", {
118696
- className: "dsh-ig-compare-heading",
118697
- children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("strong", { children: t("compareSelect") }), (0, react_jsx_runtime.jsx)("small", { children: t("compareHint") })] }), (0, react_jsx_runtime.jsx)("span", { children: t("compareSelected", { count: String(comparisonTargets.length) }) })]
118698
- }),
118699
- (0, react_jsx_runtime.jsx)("div", {
118700
- className: "dsh-ig-model-checks",
118701
- children: comparisonProfiles.map((item) => {
118702
- const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
118703
- return (0, react_jsx_runtime.jsxs)("button", {
118704
- type: "button",
118705
- className: target ? "is-selected" : "",
118706
- "aria-pressed": Boolean(target),
118707
- onClick: () => toggleComparisonProvider(item.provider),
118708
- children: [
118709
- (0, react_jsx_runtime.jsx)("span", {
118710
- className: "dsh-ig-model-checkmark",
118711
- children: target ? (0, react_jsx_runtime.jsx)(Check, { size: 12 }) : null
118712
- }),
118713
- (0, react_jsx_runtime.jsxs)("span", {
118714
- className: "dsh-ig-model-copy",
118715
- children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
118716
- title: item.model,
118717
- children: item.model
118718
- })]
118719
- }),
118720
- target && (0, react_jsx_runtime.jsxs)("span", {
118721
- className: "dsh-ig-model-settings",
118722
- children: [
118723
- target.ratio,
118724
- " · ",
118725
- target.quality,
118726
- target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
118727
- ]
118728
- })
118729
- ]
118730
- }, item.provider);
118731
- })
118732
- }),
118733
- (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
118734
- ]
118735
- }), comparisonSelectionsAllSubscription ? (0, react_jsx_runtime.jsxs)("div", {
118736
- className: "dsh-ig-field-grid",
118737
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118738
- label: t("ratio"),
118739
- value: "auto",
118740
- onChange: () => {},
118741
- options: [{
118742
- value: "auto",
118743
- label: lang === "en" ? "Channel default" : "通道默认"
118744
- }]
118745
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118746
- label: t("quality"),
118747
- value: "auto",
118748
- onChange: () => {},
118749
- options: [{
118750
- value: "auto",
118751
- label: lang === "en" ? "Channel default" : "通道默认"
118752
- }]
118753
- })]
118754
- }) : (0, react_jsx_runtime.jsxs)("div", {
118755
- className: "dsh-ig-field-grid",
118756
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118757
- label: t("ratio"),
118758
- value: ratio,
118759
- onChange: setRatio,
118760
- options: comparisonRatioOptions(lang)
118761
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118762
- label: t("quality"),
118763
- value: quality,
118764
- onChange: setQuality,
118765
- options: comparisonQualityOptions(lang)
118766
- })]
118767
- })] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118776
+ }), comparisonEnabled ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118777
+ (0, react_jsx_runtime.jsxs)("div", {
118778
+ className: "dsh-ig-compare-box",
118779
+ children: [
118780
+ (0, react_jsx_runtime.jsxs)("div", {
118781
+ className: "dsh-ig-compare-heading",
118782
+ children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("strong", { children: t("compareSelect") }), (0, react_jsx_runtime.jsx)("small", { children: t("compareHint") })] }), (0, react_jsx_runtime.jsx)("span", { children: t("compareSelected", { count: String(comparisonTargets.length) }) })]
118783
+ }),
118784
+ (0, react_jsx_runtime.jsx)("div", {
118785
+ className: "dsh-ig-model-checks",
118786
+ children: comparisonProfiles.map((item) => {
118787
+ const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
118788
+ return (0, react_jsx_runtime.jsxs)("button", {
118789
+ type: "button",
118790
+ className: target ? "is-selected" : "",
118791
+ "aria-pressed": Boolean(target),
118792
+ onClick: () => toggleComparisonProvider(item.provider),
118793
+ children: [
118794
+ (0, react_jsx_runtime.jsx)("span", {
118795
+ className: "dsh-ig-model-checkmark",
118796
+ children: target ? (0, react_jsx_runtime.jsx)(Check, { size: 12 }) : null
118797
+ }),
118798
+ (0, react_jsx_runtime.jsxs)("span", {
118799
+ className: "dsh-ig-model-copy",
118800
+ children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
118801
+ title: item.model,
118802
+ children: item.model
118803
+ })]
118804
+ }),
118805
+ target && (0, react_jsx_runtime.jsxs)("span", {
118806
+ className: "dsh-ig-model-settings",
118807
+ children: [
118808
+ target.ratio,
118809
+ " · ",
118810
+ target.quality,
118811
+ target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
118812
+ ]
118813
+ })
118814
+ ]
118815
+ }, item.provider);
118816
+ })
118817
+ }),
118818
+ (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
118819
+ ]
118820
+ }),
118821
+ (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
+ })]
118834
+ }),
118835
+ comparisonTargets.some((target) => target.profile.provider === "chatgpt-sub") && (0, react_jsx_runtime.jsx)("div", {
118836
+ className: "dsh-ig-inline-note",
118837
+ children: t("subscriptionParameterHint")
118838
+ })
118839
+ ] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118768
118840
  (0, react_jsx_runtime.jsxs)("div", {
118769
118841
  className: "dsh-ig-field-grid",
118770
118842
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
@@ -118785,26 +118857,7 @@ img.ProseMirror-separator {
118785
118857
  }]
118786
118858
  })]
118787
118859
  }),
118788
- singleProviderIsSubscription ? (0, react_jsx_runtime.jsxs)("div", {
118789
- className: "dsh-ig-field-grid",
118790
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118791
- label: t("ratio"),
118792
- value: "auto",
118793
- onChange: () => {},
118794
- options: [{
118795
- value: "auto",
118796
- label: lang === "en" ? "Channel default" : "通道默认"
118797
- }]
118798
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118799
- label: t("quality"),
118800
- value: "auto",
118801
- onChange: () => {},
118802
- options: [{
118803
- value: "auto",
118804
- label: lang === "en" ? "Channel default" : "通道默认"
118805
- }]
118806
- })]
118807
- }) : (0, react_jsx_runtime.jsxs)("div", {
118860
+ (0, react_jsx_runtime.jsxs)("div", {
118808
118861
  className: "dsh-ig-field-grid",
118809
118862
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118810
118863
  label: t("ratio"),
@@ -118818,6 +118871,10 @@ img.ProseMirror-separator {
118818
118871
  options: localizeQualityOptions(activeProfile?.qualityOptions ?? [], lang)
118819
118872
  })]
118820
118873
  }),
118874
+ provider === "chatgpt-sub" && (0, react_jsx_runtime.jsx)("div", {
118875
+ className: "dsh-ig-inline-note",
118876
+ children: t("subscriptionParameterHint")
118877
+ }),
118821
118878
  (0, react_jsx_runtime.jsxs)("div", {
118822
118879
  className: "dsh-ig-field",
118823
118880
  children: [(0, react_jsx_runtime.jsx)("label", { children: t("count") }), (0, react_jsx_runtime.jsx)("div", {
@@ -119133,6 +119190,15 @@ img.ProseMirror-separator {
119133
119190
  ]
119134
119191
  });
119135
119192
  };
119193
+ function hasSubscriptionRatioMismatch(item) {
119194
+ if (item.provider !== "chatgpt-sub" || item.aspectRatio === "auto") return false;
119195
+ const width = item.attachment.width;
119196
+ const height = item.attachment.height;
119197
+ if (width === void 0 || height === void 0 || width <= 0 || height <= 0) return false;
119198
+ const parts = item.aspectRatio?.split(":").map(Number);
119199
+ if (parts === void 0 || parts.length !== 2 || !(parts[0] > 0) || !(parts[1] > 0)) return false;
119200
+ return Math.abs(width / height / (parts[0] / parts[1]) - 1) > .12;
119201
+ }
119136
119202
  const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
119137
119203
  className: "dsh-ig-field-select",
119138
119204
  children: [(0, react_jsx_runtime.jsx)("span", { children: label }), (0, react_jsx_runtime.jsx)("select", {
@@ -119169,6 +119235,14 @@ img.ProseMirror-separator {
119169
119235
  zh: "3:4 竖向",
119170
119236
  en: "3:4 Portrait"
119171
119237
  },
119238
+ "4:5": {
119239
+ zh: "4:5 肖像",
119240
+ en: "4:5 Portrait"
119241
+ },
119242
+ "5:4": {
119243
+ zh: "5:4 横向",
119244
+ en: "5:4 Landscape"
119245
+ },
119172
119246
  "16:9": {
119173
119247
  zh: "16:9 宽屏",
119174
119248
  en: "16:9 Widescreen"
@@ -119176,6 +119250,10 @@ img.ProseMirror-separator {
119176
119250
  "9:16": {
119177
119251
  zh: "9:16 竖屏",
119178
119252
  en: "9:16 Portrait"
119253
+ },
119254
+ "21:9": {
119255
+ zh: "21:9 超宽",
119256
+ en: "21:9 Ultrawide"
119179
119257
  }
119180
119258
  };
119181
119259
  const LOCALIZED_QUALITIES = {
@@ -119184,8 +119262,32 @@ img.ProseMirror-separator {
119184
119262
  en: "Standard (Recommended)"
119185
119263
  },
119186
119264
  auto: {
119187
- zh: "模型自动",
119188
- en: "Model Auto"
119265
+ zh: "自动",
119266
+ en: "Auto"
119267
+ },
119268
+ low: {
119269
+ zh: "低(快速)",
119270
+ en: "Low (Fast)"
119271
+ },
119272
+ medium: {
119273
+ zh: "中",
119274
+ en: "Medium"
119275
+ },
119276
+ high: {
119277
+ zh: "高",
119278
+ en: "High"
119279
+ },
119280
+ xhigh: {
119281
+ zh: "超高",
119282
+ en: "Extra High"
119283
+ },
119284
+ max: {
119285
+ zh: "最高",
119286
+ en: "Max"
119287
+ },
119288
+ hd: {
119289
+ zh: "高清",
119290
+ en: "HD"
119189
119291
  }
119190
119292
  };
119191
119293
  function localizeRatioOptions(options, lang) {
@@ -119200,38 +119302,6 @@ img.ProseMirror-separator {
119200
119302
  label: LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
119201
119303
  }));
119202
119304
  }
119203
- function comparisonRatioOptions(lang) {
119204
- return [
119205
- "1:1",
119206
- "3:2",
119207
- "2:3",
119208
- "16:9",
119209
- "9:16"
119210
- ].map((value) => ({
119211
- value,
119212
- label: LOCALIZED_RATIOS[value]?.[lang] ?? value
119213
- }));
119214
- }
119215
- function comparisonQualityOptions(lang) {
119216
- return [
119217
- {
119218
- value: "standard",
119219
- label: lang === "zh" ? "标准" : "Standard"
119220
- },
119221
- {
119222
- value: "1K",
119223
- label: "1K"
119224
- },
119225
- {
119226
- value: "2K",
119227
- label: "2K"
119228
- },
119229
- {
119230
- value: "4K",
119231
- label: "4K"
119232
- }
119233
- ];
119234
- }
119235
119305
  const RecentItem = ({ item, active, onClick }) => {
119236
119306
  const [setRef, inView] = useInView();
119237
119307
  const image = useAttachmentImage(item.attachment, inView);
@@ -122037,6 +122107,19 @@ img.ProseMirror-separator {
122037
122107
  }
122038
122108
  /** Extract standard aspect ratio for precise filtering */
122039
122109
  function getItemRatio(item) {
122110
+ if (item.provider === "chatgpt-sub" && item.attachment.width && item.attachment.height) {
122111
+ const actual = item.attachment.width / item.attachment.height;
122112
+ for (const [label, value] of [
122113
+ ["1:1", 1],
122114
+ ["16:9", 16 / 9],
122115
+ ["9:16", 9 / 16],
122116
+ ["4:3", 4 / 3],
122117
+ ["3:4", 3 / 4],
122118
+ ["3:2", 3 / 2],
122119
+ ["2:3", 2 / 3]
122120
+ ]) if (Math.abs(actual / value - 1) < .08) return label;
122121
+ return "custom";
122122
+ }
122040
122123
  if (item.aspectRatio && item.aspectRatio !== "custom") return item.aspectRatio;
122041
122124
  if (item.output) {
122042
122125
  const ratioMatch = item.output.match(/\b(1:1|16:9|9:16|4:3|3:4|3:2|2:3)\b/);
@@ -123572,11 +123655,10 @@ img.ProseMirror-separator {
123572
123655
  //#region lib/types/client/image-result-node.js
123573
123656
  const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
123574
123657
  /**
123575
- * One image result row per Turn, anchored at its own tool/result position while
123576
- * the Turn runs, then optionally re-anchored beside the final answer once the
123577
- * Turn closes (only needed under the folding Compact transcript view).
123658
+ * Give each image Tool result its own Chat row at the event that produced it.
123659
+ * PTC (run_code) sub-calls already have their own stable sub-call identity.
123578
123660
  */
123579
- function createImageResultDefinition(options = {}) {
123661
+ function createImageResultDefinition() {
123580
123662
  return {
123581
123663
  kind: IMAGE_RESULT_NODE_KIND,
123582
123664
  target: "chat",
@@ -123584,77 +123666,52 @@ img.ProseMirror-separator {
123584
123666
  if (event.type === "tool/ptc-dispatch") {
123585
123667
  const subCallId = event.data.subCallId;
123586
123668
  if (typeof subCallId !== "string" || subCallId === "") return null;
123587
- return imageResultFromPtcDispatch(event) === void 0 ? null : {
123669
+ return imageResultsFromPtcDispatch(event).length === 0 ? null : {
123588
123670
  id: `ptc:${subCallId}`,
123589
123671
  role: "start"
123590
123672
  };
123591
123673
  }
123592
- const turn = eventTurn(event);
123593
- if (turn === void 0) return null;
123594
- if (event.type === "turn/start") return {
123595
- id: String(turn),
123674
+ if (event.type === "tool/result" && eventTurn(event) !== void 0 && imageResultsFromMeta(event.data.meta).length > 0) return {
123675
+ id: `result:${event.seq}`,
123596
123676
  role: "start"
123597
123677
  };
123598
- if (event.type === "turn/end") return {
123599
- id: String(turn),
123600
- role: "update"
123601
- };
123602
- if (event.type === "assistant/message") return {
123603
- id: String(turn),
123604
- role: "update"
123605
- };
123606
- if (event.type === "tool/result" && imageResultFromMeta(event.data.meta) !== void 0) return {
123607
- id: String(turn),
123608
- role: "update"
123609
- };
123610
123678
  return null;
123611
123679
  },
123612
123680
  start: (_context, match) => {
123613
123681
  if (match.event.type === "tool/ptc-dispatch") {
123614
- const result = imageResultFromPtcDispatch(match.event);
123615
- if (result === void 0) throw new Error("dsh-image-result start requires a plugin image dispatch");
123616
- return { results: [{
123682
+ const results = imageResultsFromPtcDispatch(match.event);
123683
+ if (results.length === 0) throw new Error("dsh-image-result start requires a plugin image dispatch");
123684
+ return { results: results.map((result) => ({
123617
123685
  ...result,
123618
123686
  seq: match.event.seq
123619
- }] };
123687
+ })) };
123620
123688
  }
123621
123689
  const turn = eventTurn(match.event);
123622
- if (match.event.type !== "turn/start" || turn === void 0) throw new Error("dsh-image-result start requires turn/start");
123690
+ const results = match.event.type === "tool/result" ? imageResultsFromMeta(match.event.data.meta) : [];
123691
+ if (turn === void 0 || results.length === 0) throw new Error("dsh-image-result start requires an image Tool result");
123623
123692
  return {
123624
123693
  turn,
123625
- results: []
123694
+ results: results.map((result) => ({
123695
+ ...result,
123696
+ seq: match.event.seq
123697
+ }))
123626
123698
  };
123627
123699
  },
123628
123700
  update: (context, match) => {
123629
- if (match.event.type === "turn/end") return {
123630
- ...context.state,
123631
- endSeq: match.event.seq
123632
- };
123633
- if (match.event.type === "assistant/message") return {
123634
- ...context.state,
123635
- answerSeq: match.event.seq
123636
- };
123637
- if (match.event.type === "tool/ptc-dispatch") {
123638
- const result = imageResultFromPtcDispatch(match.event);
123639
- return result === void 0 ? context.state : appendResult(context.state, result, match.event.seq);
123640
- }
123641
- if (match.event.type !== "tool/result") return context.state;
123642
- const result = imageResultFromMeta(match.event.data.meta);
123643
- if (result === void 0) return context.state;
123644
- return appendResult(context.state, result, match.event.seq);
123701
+ if (match.event.type !== "tool/ptc-dispatch") return context.state;
123702
+ return imageResultsFromPtcDispatch(match.event).reduce((state, result) => appendResult(state, result, match.event.seq), context.state);
123645
123703
  },
123646
123704
  buildViewNode: (context) => {
123647
123705
  const state = context.state;
123648
- const last = state?.results.at(-1);
123649
- if (state === void 0 || last === void 0) return null;
123650
- const location = context.matches.at(-1)?.location ?? context.start?.location ?? { kind: "unresolved" };
123706
+ const first = state?.results[0];
123707
+ if (state === void 0 || first === void 0) return null;
123651
123708
  return {
123652
123709
  key: context.key,
123653
123710
  kind: IMAGE_RESULT_NODE_KIND,
123654
123711
  id: context.id,
123655
123712
  target: "chat",
123656
- anchorSeq: state.endSeq !== void 0 && compactTranscript(options) ? state.answerSeq ?? state.endSeq ?? last.seq : last.seq,
123657
- location,
123713
+ anchorSeq: first.seq,
123714
+ location: { kind: "session" },
123658
123715
  visibility: "visible",
123659
123716
  data: {
123660
123717
  ...state.turn !== void 0 ? { turn: state.turn } : {},
@@ -123675,13 +123732,6 @@ img.ProseMirror-separator {
123675
123732
  }]
123676
123733
  };
123677
123734
  }
123678
- function compactTranscript(options) {
123679
- try {
123680
- return options.isCompactTranscript?.() ?? true;
123681
- } catch {
123682
- return true;
123683
- }
123684
- }
123685
123735
  /** Parse the plugin-owned durable presentation metadata from a Tool result event. */
123686
123736
  function imageResultFromMeta(value) {
123687
123737
  const meta = record(value);
@@ -123698,12 +123748,26 @@ img.ProseMirror-separator {
123698
123748
  ...typeof meta.seed === "number" ? { seed: meta.seed } : {}
123699
123749
  };
123700
123750
  }
123751
+ /** Parse all successful images from either a single or batch Tool result. */
123752
+ function imageResultsFromMeta(value) {
123753
+ const meta = record(value);
123754
+ if (meta?.kind === "dsh-image-gen-batch" && Array.isArray(meta.images)) return meta.images.flatMap((image) => {
123755
+ const result = imageResultFromMeta(image);
123756
+ return result === void 0 ? [] : [result];
123757
+ });
123758
+ const result = imageResultFromMeta(value);
123759
+ return result === void 0 ? [] : [result];
123760
+ }
123701
123761
  function eventTurn(event) {
123702
123762
  const turn = event.data.turn;
123703
123763
  return typeof turn === "number" && Number.isSafeInteger(turn) && turn >= 0 ? turn : void 0;
123704
123764
  }
123705
123765
  /** Plugin tools whose PTC (run_code) dispatch results are image results. */
123706
- const PTC_IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set(["generate_image", "edit_image"]);
123766
+ const PTC_IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set([
123767
+ "generate_image",
123768
+ "generate_images",
123769
+ "edit_image"
123770
+ ]);
123707
123771
  /**
123708
123772
  * Fixed summary text rendered by the tools' imageOutput(). PTC dispatch events
123709
123773
  * carry no presentationMeta, so provider/model/output are recovered from this
@@ -123711,42 +123775,41 @@ img.ProseMirror-separator {
123711
123775
  */
123712
123776
  const PTC_SUMMARY_PATTERN = /^(?:Generated|Edited) one image with ([^/\s]+)\/(\S+) \((.+)\)\. Attachment ID: /;
123713
123777
  const PTC_SAVED_TO_PATTERN = / It was also saved to the workspace as (.+)\. (?:(?:Saving it to the workspace failed)|(?:It has no local file path)|(?:Respond to the user))/;
123714
- /**
123715
- * Parse a plugin image result from a tool/ptc-dispatch event (#38). PTC
123716
- * run_code sub-calls carry neither a turn nor presentationMeta, so the result
123717
- * is rebuilt from the dispatch payload itself: the image content block for the
123718
- * attachment, arguments for the prompt (and edit source ids), and the tool's
123719
- * fixed-format summary text for provider/model/output. Returns undefined for
123720
- * anything that is not a successful image result from our own image tools.
123721
- */
123722
- function imageResultFromPtcDispatch(event) {
123723
- if (event.type !== "tool/ptc-dispatch") return void 0;
123778
+ /** Pair each rendered image with its own summary, including batches with failed items. */
123779
+ function imageResultsFromPtcDispatch(event) {
123780
+ if (event.type !== "tool/ptc-dispatch") return [];
123724
123781
  const data = event.data;
123725
- if (data.isError === true) return void 0;
123726
- if (typeof data.name !== "string" || !PTC_IMAGE_TOOL_NAMES.has(data.name)) return void 0;
123782
+ if (data.isError === true) return [];
123783
+ if (typeof data.name !== "string" || !PTC_IMAGE_TOOL_NAMES.has(data.name)) return [];
123727
123784
  const content = Array.isArray(data.content) ? data.content : [];
123728
- let attachment;
123785
+ const results = [];
123729
123786
  let summaryText = "";
123787
+ const args = record(data.arguments);
123788
+ const sourceIds = sourceAttachmentIds(args);
123730
123789
  for (const block of content) {
123731
123790
  const candidate = record(block);
123732
123791
  if (candidate === void 0) continue;
123733
- if (candidate.type === "image" && attachment === void 0) attachment = imageAttachment(candidate.attachment);
123734
- else if (candidate.type === "text" && summaryText === "" && typeof candidate.text === "string") summaryText = candidate.text;
123792
+ if (candidate.type === "text" && typeof candidate.text === "string") {
123793
+ summaryText = candidate.text;
123794
+ continue;
123795
+ }
123796
+ if (candidate.type !== "image") continue;
123797
+ const attachment = imageAttachment(candidate.attachment);
123798
+ if (attachment === void 0) continue;
123799
+ const summary = PTC_SUMMARY_PATTERN.exec(summaryText);
123800
+ const savedTo = PTC_SAVED_TO_PATTERN.exec(summaryText)?.[1];
123801
+ const promptAt = data.name === "generate_images" ? summaryText.indexOf("\nImage prompt: ") : -1;
123802
+ results.push({
123803
+ attachment,
123804
+ prompt: promptAt < 0 ? stringValue(args?.prompt, "Generated Image") : summaryText.slice(promptAt + 15),
123805
+ provider: summary?.[1] ?? "",
123806
+ model: summary?.[2] ?? "",
123807
+ output: summary?.[3] ?? "",
123808
+ ...savedTo !== void 0 ? { savedTo } : {},
123809
+ ...sourceIds !== void 0 ? { sourceAttachmentIds: sourceIds } : {}
123810
+ });
123735
123811
  }
123736
- if (attachment === void 0) return void 0;
123737
- const summary = PTC_SUMMARY_PATTERN.exec(summaryText);
123738
- const savedTo = PTC_SAVED_TO_PATTERN.exec(summaryText)?.[1];
123739
- const args = record(data.arguments);
123740
- const sourceIds = sourceAttachmentIds(args);
123741
- return {
123742
- attachment,
123743
- prompt: stringValue(args?.prompt, "Generated Image"),
123744
- provider: summary?.[1] ?? "",
123745
- model: summary?.[2] ?? "",
123746
- output: summary?.[3] ?? "",
123747
- ...savedTo !== void 0 ? { savedTo } : {},
123748
- ...sourceIds !== void 0 ? { sourceAttachmentIds: sourceIds } : {}
123749
- };
123812
+ return results;
123750
123813
  }
123751
123814
  /** Collect edit_image source attachment ids (single or list form) from dispatch arguments. */
123752
123815
  function sourceAttachmentIds(args) {
@@ -123808,16 +123871,16 @@ img.ProseMirror-separator {
123808
123871
  const isNew = event.seq > lastSeq;
123809
123872
  lastSeq = Math.max(lastSeq, event.seq);
123810
123873
  if (change.kind !== "append" || !isNew) continue;
123811
- const result = event.type === "tool/result" ? imageResultFromMeta(event.data.meta) : imageResultFromPtcDispatch(event);
123812
- if (result === void 0) continue;
123813
- pushTlLandingsLive([{
123874
+ const results = event.type === "tool/result" ? imageResultsFromMeta(event.data.meta) : imageResultsFromPtcDispatch(event);
123875
+ if (results.length === 0) continue;
123876
+ pushTlLandingsLive(results.map((result) => ({
123814
123877
  galleryId: String(result.attachment.attachmentId),
123815
123878
  attachment: result.attachment,
123816
123879
  fromConversation: true,
123817
123880
  prompt: result.prompt,
123818
123881
  provider: result.provider,
123819
123882
  model: result.model
123820
- }]);
123883
+ })));
123821
123884
  }
123822
123885
  });
123823
123886
  };
@@ -124754,7 +124817,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124754
124817
  `;
124755
124818
  //#endregion
124756
124819
  //#region lib/types/client/index.js
124757
- console.info(`[dsh-image-gen] canvas bundle 2026-09-28T16:41:08.496Z`);
124820
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-08T04:58:18.947Z`);
124758
124821
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
124759
124822
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
124760
124823
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -124766,6 +124829,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124766
124829
  defaultProvider: "默认 Provider",
124767
124830
  defaultProviderHint: "Agent 生图默认使用;Studio 与工具调用可临时指定其他 Provider。",
124768
124831
  settingsReadOnly: "设置由配置文件提供,只读;如需修改请编辑对应的配置来源。",
124832
+ settingsLoading: "正在加载图像生成设置,请稍候。",
124833
+ settingsRemote: "当前连接不支持保存到宿主,请在 DSH 本机页面修改设置。",
124834
+ settingsUnavailable: "宿主尚未提供图像生成设置,请检查插件是否已启用及其加载日志。",
124835
+ settingsIncompatible: "当前加载的设置库不支持保存。请将插件所在环境的 @deepseek-ai/schemastery 更新到 3.18.4 或兼容的 3.x 版本,并重启 DSH。",
124769
124836
  providerGoogle: "Google Gemini",
124770
124837
  providerOpenAI: "OpenAI",
124771
124838
  providerOpenAICompat: "OpenAI 兼容(中转站)",
@@ -124782,6 +124849,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124782
124849
  subBadgeUnknown: "状态未知",
124783
124850
  subSectionTitle: "订阅生图",
124784
124851
  subHint: "通过已登录的订阅账号生图,无需 API Key。登录在你的授权下进行,不会修改任何 API Key 或默认 Provider。",
124852
+ subProxyHint: "需要代理时,请在登录前开启代理软件的 TUN(虚拟网卡)模式。普通系统代理可能无法覆盖 DSH 后端请求,导致网页授权后登录失败。",
124785
124853
  subAccountLabel: "账号",
124786
124854
  subAccountEmail: "已登录:{email}",
124787
124855
  subAccountNone: "未登录",
@@ -124816,6 +124884,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124816
124884
  clearKeyFailed: "清除 Key 失败",
124817
124885
  saveKeyFailed: "保存 Key 失败",
124818
124886
  saveKeyFirst: "请先保存 API Key,再拉取模型或测试连接。",
124887
+ saveEndpointFirst: "接口地址尚未保存,请先点击保存,再拉取模型或测试连接。",
124888
+ endpointRequired: "请填写接口地址并保存,再拉取模型或测试连接。",
124889
+ savedEndpointMissing: "未读取到已保存的接口地址,请保存后重试。",
124819
124890
  clearKeyUnsupported: "当前版本 DSH 不支持在此清除 Key,请到凭据管理中删除。",
124820
124891
  endpoint: "接口地址",
124821
124892
  reset: "重置",
@@ -124911,6 +124982,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124911
124982
  defaultProvider: "Default provider",
124912
124983
  defaultProviderHint: "Used by the Agent by default; the Studio and tool calls can switch per call.",
124913
124984
  settingsReadOnly: "Settings come from a profile file and are read-only; edit that source to change them.",
124985
+ settingsLoading: "Settings are loading; please wait.",
124986
+ settingsRemote: "This connection cannot save settings to the host. Edit them from the local DSH page.",
124987
+ settingsUnavailable: "The host has not provided image generation settings. Check that the plugin is enabled and review its load logs.",
124988
+ settingsIncompatible: "The loaded settings library cannot save these settings. Update @deepseek-ai/schemastery in the plugin environment to 3.18.4 or a compatible 3.x version, then restart DSH.",
124914
124989
  providerGoogle: "Google Gemini",
124915
124990
  providerOpenAI: "OpenAI",
124916
124991
  providerOpenAICompat: "OpenAI-compatible (relay)",
@@ -124927,6 +125002,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124927
125002
  subBadgeUnknown: "Unknown",
124928
125003
  subSectionTitle: "Subscription generation",
124929
125004
  subHint: "Generates through a logged-in subscription account; no API key needed. Signing in never changes any API key or the default provider.",
125005
+ subProxyHint: "If you need a proxy, enable TUN (virtual network adapter) mode before signing in. A system proxy alone may not cover DSH backend requests, causing sign-in to fail after browser authorization.",
124930
125006
  subAccountLabel: "Account",
124931
125007
  subAccountEmail: "Signed in: {email}",
124932
125008
  subAccountNone: "Not signed in",
@@ -124961,6 +125037,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124961
125037
  clearKeyFailed: "Failed to clear key",
124962
125038
  saveKeyFailed: "Failed to save the key",
124963
125039
  saveKeyFirst: "Save the API key first, then fetch models or test the connection.",
125040
+ saveEndpointFirst: "The endpoint address has not been saved. Click Save before fetching models or testing the connection.",
125041
+ endpointRequired: "Enter and save an endpoint address before fetching models or testing the connection.",
125042
+ savedEndpointMissing: "No saved endpoint address was found. Save it and try again.",
124964
125043
  clearKeyUnsupported: "This DSH build cannot clear keys here; remove it from credential management instead.",
124965
125044
  endpoint: "Endpoint / Base URL",
124966
125045
  reset: "Reset",
@@ -125360,7 +125439,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125360
125439
  return {
125361
125440
  getSnapshot: () => ({
125362
125441
  value: void 0,
125363
- writable: false
125442
+ writable: false,
125443
+ status: "unavailable"
125364
125444
  }),
125365
125445
  subscribe: () => () => {},
125366
125446
  set: async () => false
@@ -125371,11 +125451,6 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125371
125451
  const configForms = ctx.get("configForms");
125372
125452
  const settingsScopeApi = configForms === void 0 ? ctx.get("settingsScope") : void 0;
125373
125453
  const scope = configForms?.get("image-gen") ?? settingsScopeApi?.bind({ namespace: "image-generation" }) ?? degradedScope();
125374
- const chatScope = configForms?.get("ui-chat") ?? settingsScopeApi?.bind({ namespace: "ui-chat" }) ?? degradedScope();
125375
- const isCompactTranscript = () => {
125376
- const mode = chatScope.getSnapshot().value?.transcriptView;
125377
- return configForms === void 0 ? mode !== "normal" : mode !== "standard" && mode !== "detailed" && mode !== "verbose";
125378
- };
125379
125454
  const locale = ctx.get("locale");
125380
125455
  const promotion = { enabled: false };
125381
125456
  ctx.inject(["sessions"], (owner) => {
@@ -125403,7 +125478,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125403
125478
  if (uiConversation === void 0) throw new Error("dsh-image-gen: uiConversation has an incompatible interface");
125404
125479
  promotion.enabled = true;
125405
125480
  const ownerRegister = owner.slots.register.bind(owner.slots);
125406
- owner.effect(() => uiConversation.events.register(createImageResultDefinition({ isCompactTranscript })), "dsh-image-gen: promoted image result node");
125481
+ owner.effect(() => uiConversation.events.register(createImageResultDefinition()), "dsh-image-gen: promoted image result node");
125407
125482
  owner.slots.inject("conversation.chat.node", () => ownerRegister({
125408
125483
  name: "conversation.chat.node",
125409
125484
  key: IMAGE_RESULT_NODE_KIND,
@@ -125502,6 +125577,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125502
125577
  promoted: promotion.enabled
125503
125578
  })
125504
125579
  }, GeneratedImageCard));
125580
+ ctx.slots.inject("tool.call.toolview", () => register({
125581
+ name: "tool.call.toolview",
125582
+ key: "generate_images",
125583
+ inject: () => ({
125584
+ locale,
125585
+ promoted: promotion.enabled
125586
+ })
125587
+ }, GeneratedImagesCard));
125505
125588
  ctx.slots.inject("tool.call.toolview", () => register({
125506
125589
  name: "tool.call.toolview",
125507
125590
  key: "edit_image",
@@ -125697,6 +125780,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125697
125780
  function ImageGenerationSettingsCard(props) {
125698
125781
  const [open, setOpen] = (0, react.useState)(false);
125699
125782
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
125783
+ const [health, setHealth] = (0, react.useState)();
125784
+ const state = settingsState(snapshot, health);
125785
+ const settingsWritable = state === "ready";
125700
125786
  const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
125701
125787
  const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
125702
125788
  const [providerMessage, setProviderMessage] = (0, react.useState)("");
@@ -125718,9 +125804,28 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125718
125804
  const [subTick, setSubTick] = (0, react.useState)(0);
125719
125805
  const [subBusy, setSubBusy] = (0, react.useState)({});
125720
125806
  const [subPending, setSubPending] = (0, react.useState)([]);
125721
- (0, react.useEffect)(() => props.scope.subscribe(() => {
125722
- setSnapshot(props.scope.getSnapshot());
125723
- }), [props.scope]);
125807
+ (0, react.useEffect)(() => {
125808
+ const sync = () => {
125809
+ setSnapshot(props.scope.getSnapshot());
125810
+ };
125811
+ const unsubscribe = props.scope.subscribe(sync);
125812
+ sync();
125813
+ return unsubscribe;
125814
+ }, [props.scope]);
125815
+ (0, react.useEffect)(() => {
125816
+ if (!open || snapshot.mode === "memory") return void 0;
125817
+ const controller = new AbortController();
125818
+ fetchSettingsHealth(controller.signal).then((value) => {
125819
+ if (!controller.signal.aborted) setHealth(value);
125820
+ });
125821
+ return () => {
125822
+ controller.abort();
125823
+ };
125824
+ }, [
125825
+ open,
125826
+ snapshot.status,
125827
+ snapshot.mode
125828
+ ]);
125724
125829
  (0, react.useEffect)(() => {
125725
125830
  return props.locale?.subscribe?.(() => {
125726
125831
  setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
@@ -125872,7 +125977,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125872
125977
  if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
125873
125978
  return text;
125874
125979
  };
125875
- const saveSetting = (field, value) => writeSetting(props.scope, field, value, t("settingsWriteRejected"));
125980
+ const settingsMessage = state === "ready" ? void 0 : t({
125981
+ loading: "settingsLoading",
125982
+ remote: "settingsRemote",
125983
+ unavailable: "settingsUnavailable",
125984
+ readOnly: "settingsReadOnly",
125985
+ incompatible: "settingsIncompatible"
125986
+ }[state]);
125987
+ const saveSetting = async (field, value) => {
125988
+ if (settingsMessage !== void 0) throw new Error(settingsMessage);
125989
+ await writeSetting(props.scope, field, value, t("settingsWriteRejected"));
125990
+ };
125876
125991
  const providerLabels = {
125877
125992
  google: t("providerGoogle"),
125878
125993
  openai: t("providerOpenAI"),
@@ -126071,11 +126186,23 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126071
126186
  updateRow(provider, { saving: false });
126072
126187
  }
126073
126188
  };
126189
+ /** These actions use host settings, so drafts must be saved before making a request. */
126190
+ const providerActionBlockMessage = (provider) => {
126191
+ const row = rows[provider];
126192
+ if (row.keyInput.trim().length > 0) return t("saveKeyFirst");
126193
+ if (isSubscriptionProvider(provider)) return void 0;
126194
+ const address = row.baseURL.trim();
126195
+ if (address.length === 0) return t("endpointRequired");
126196
+ if (address !== baseURLOf(provider, props.scope.getSnapshot().value).trim()) return t("saveEndpointFirst");
126197
+ };
126198
+ /** Localize the host's missing-address error while preserving upstream details. */
126199
+ const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
126074
126200
  /** Probe through the host route so the browser side never touches credential values. */
126075
126201
  const testConnection = async (provider) => {
126076
- if (rows[provider].keyInput.trim().length > 0) {
126202
+ const blocked = providerActionBlockMessage(provider);
126203
+ if (blocked !== void 0) {
126077
126204
  updateRow(provider, {
126078
- message: t("saveKeyFirst"),
126205
+ message: blocked,
126079
126206
  messageIsError: false,
126080
126207
  testResult: void 0
126081
126208
  });
@@ -126107,9 +126234,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126107
126234
  };
126108
126235
  /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
126109
126236
  const fetchProviderModels = async (provider) => {
126110
- if (rows[provider].keyInput.trim().length > 0) {
126237
+ const blocked = providerActionBlockMessage(provider);
126238
+ if (blocked !== void 0) {
126111
126239
  updateRow(provider, {
126112
- modelFetchMessage: t("saveKeyFirst"),
126240
+ modelFetchMessage: blocked,
126113
126241
  modelFetchIsError: false
126114
126242
  });
126115
126243
  return;
@@ -126146,7 +126274,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126146
126274
  modelFetchIsError: true
126147
126275
  });
126148
126276
  else updateRow(provider, {
126149
- modelFetchMessage: payload.message ?? t("testFailed"),
126277
+ modelFetchMessage: providerErrorMessage(payload.message),
126150
126278
  modelFetchIsError: true
126151
126279
  });
126152
126280
  } catch (cause) {
@@ -126197,7 +126325,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126197
126325
  if (result.ok) return t("testOk");
126198
126326
  if (result.reason === "missing-key") return t("badgeMissing");
126199
126327
  if (result.reason === "unauthorized") return t("testUnauthorized");
126200
- return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${result.message}` : ""}`;
126328
+ return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${providerErrorMessage(result.message)}` : ""}`;
126201
126329
  };
126202
126330
  const badgeOf = (provider) => {
126203
126331
  if (provider === "comfyui") return {
@@ -126392,7 +126520,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126392
126520
  updateRow(provider, { keyInput: event.target.value });
126393
126521
  },
126394
126522
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
126395
- disabled: !snapshot.writable || keyReadOnly || keyUnavailable
126523
+ disabled: !settingsWritable || keyReadOnly || keyUnavailable
126396
126524
  }),
126397
126525
  (0, react_jsx_runtime.jsx)("span", {
126398
126526
  className: "dsh-ig-hint",
@@ -126417,7 +126545,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126417
126545
  updateRow(provider, { baseURL: event.target.value });
126418
126546
  },
126419
126547
  required: true,
126420
- disabled: !snapshot.writable
126548
+ disabled: !settingsWritable
126421
126549
  }), provider !== "openai-compat" ? (0, react_jsx_runtime.jsx)("button", {
126422
126550
  type: "button",
126423
126551
  className: "dsh-ig-btn-reset",
@@ -126425,7 +126553,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126425
126553
  onClick: () => {
126426
126554
  updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
126427
126555
  },
126428
- disabled: !snapshot.writable,
126556
+ disabled: !settingsWritable,
126429
126557
  children: t("reset")
126430
126558
  }) : null]
126431
126559
  }),
@@ -126453,7 +126581,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126453
126581
  },
126454
126582
  list: `dsh-ig-${provider}-model-options`,
126455
126583
  required: provider !== "openai-compat",
126456
- disabled: !snapshot.writable
126584
+ disabled: !settingsWritable
126457
126585
  }),
126458
126586
  (0, react_jsx_runtime.jsx)("datalist", {
126459
126587
  id: `dsh-ig-${provider}-model-options`,
@@ -126462,7 +126590,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126462
126590
  (0, react_jsx_runtime.jsx)("button", {
126463
126591
  type: "button",
126464
126592
  className: "dsh-ig-btn-secondary",
126465
- disabled: row.fetchingModels || row.saving || !snapshot.writable,
126593
+ disabled: row.fetchingModels || row.saving || !settingsWritable,
126466
126594
  onClick: () => {
126467
126595
  fetchProviderModels(provider);
126468
126596
  },
@@ -126476,7 +126604,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126476
126604
  updateRow(provider, { model: event.target.value });
126477
126605
  },
126478
126606
  required: true,
126479
- disabled: !snapshot.writable
126607
+ disabled: !settingsWritable
126480
126608
  }),
126481
126609
  modelPullSupported(provider) ? row.modelFetchMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
126482
126610
  className: `dsh-ig-hint${row.modelFetchIsError ? " dsh-ig-hint-error" : ""}`,
@@ -126501,7 +126629,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126501
126629
  onChange: (event) => {
126502
126630
  updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
126503
126631
  },
126504
- disabled: !snapshot.writable,
126632
+ disabled: !settingsWritable,
126505
126633
  children: [
126506
126634
  (0, react_jsx_runtime.jsx)("option", {
126507
126635
  value: "multipart",
@@ -126537,7 +126665,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126537
126665
  onChange: (event) => {
126538
126666
  updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
126539
126667
  },
126540
- disabled: !snapshot.writable,
126668
+ disabled: !settingsWritable,
126541
126669
  children: [(0, react_jsx_runtime.jsx)("option", {
126542
126670
  value: "jpeg",
126543
126671
  children: t("arkOutputFormatJpeg")
@@ -126565,7 +126693,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126565
126693
  onChange: (event) => {
126566
126694
  updateRow(provider, { watermark: event.target.value === "on" });
126567
126695
  },
126568
- disabled: !snapshot.writable,
126696
+ disabled: !settingsWritable,
126569
126697
  children: [(0, react_jsx_runtime.jsx)("option", {
126570
126698
  value: "on",
126571
126699
  children: t("arkWatermarkOn")
@@ -126593,7 +126721,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126593
126721
  onChange: (event) => {
126594
126722
  updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
126595
126723
  },
126596
- disabled: !snapshot.writable,
126724
+ disabled: !settingsWritable,
126597
126725
  children: [(0, react_jsx_runtime.jsx)("option", {
126598
126726
  value: "opaque",
126599
126727
  children: t("arkBackgroundOpaque")
@@ -126624,7 +126752,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126624
126752
  },
126625
126753
  placeholder: t("editExtraPlaceholder"),
126626
126754
  rows: 2,
126627
- disabled: !snapshot.writable
126755
+ disabled: !settingsWritable
126628
126756
  }),
126629
126757
  (0, react_jsx_runtime.jsx)("span", {
126630
126758
  className: "dsh-ig-hint",
@@ -126662,7 +126790,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126662
126790
  (0, react_jsx_runtime.jsx)("button", {
126663
126791
  className: "dsh-ig-save",
126664
126792
  type: "submit",
126665
- disabled: row.saving || row.testing || row.fetchingModels || !snapshot.writable,
126793
+ disabled: row.saving || row.testing || row.fetchingModels || !settingsWritable,
126666
126794
  children: row.saving ? t("saving") : t("save")
126667
126795
  })
126668
126796
  ]
@@ -126699,7 +126827,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126699
126827
  updateRow("comfyui", { baseURL: event.target.value });
126700
126828
  },
126701
126829
  required: true,
126702
- disabled: !snapshot.writable
126830
+ disabled: !settingsWritable
126703
126831
  }), (0, react_jsx_runtime.jsx)("button", {
126704
126832
  type: "button",
126705
126833
  className: "dsh-ig-btn-reset",
@@ -126707,7 +126835,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126707
126835
  onClick: () => {
126708
126836
  updateRow("comfyui", { baseURL: DEFAULT_BASE_URLS.comfyui });
126709
126837
  },
126710
- disabled: !snapshot.writable,
126838
+ disabled: !settingsWritable,
126711
126839
  children: t("reset")
126712
126840
  })]
126713
126841
  }),
@@ -126813,7 +126941,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126813
126941
  updateRow("comfyui", { timeoutSeconds: Number(event.target.value) });
126814
126942
  },
126815
126943
  required: true,
126816
- disabled: !snapshot.writable
126944
+ disabled: !settingsWritable
126817
126945
  }),
126818
126946
  (0, react_jsx_runtime.jsx)("span", {
126819
126947
  className: "dsh-ig-hint",
@@ -126840,7 +126968,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126840
126968
  }), (0, react_jsx_runtime.jsx)("button", {
126841
126969
  className: "dsh-ig-save",
126842
126970
  type: "submit",
126843
- disabled: row.saving || !snapshot.writable || row.workflows.length === 0,
126971
+ disabled: row.saving || !settingsWritable || row.workflows.length === 0,
126844
126972
  children: row.saving ? t("saving") : t("save")
126845
126973
  })]
126846
126974
  })]
@@ -126864,13 +126992,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126864
126992
  children: [
126865
126993
  (0, react_jsx_runtime.jsxs)("div", {
126866
126994
  className: "dsh-ig-field",
126867
- children: [(0, react_jsx_runtime.jsx)("span", {
126868
- className: "dsh-ig-label",
126869
- children: t("subSectionTitle")
126870
- }), (0, react_jsx_runtime.jsx)("p", {
126871
- className: "dsh-ig-hint",
126872
- children: t("subHint")
126873
- })]
126995
+ children: [
126996
+ (0, react_jsx_runtime.jsx)("span", {
126997
+ className: "dsh-ig-label",
126998
+ children: t("subSectionTitle")
126999
+ }),
127000
+ (0, react_jsx_runtime.jsx)("p", {
127001
+ className: "dsh-ig-hint",
127002
+ children: t("subHint")
127003
+ }),
127004
+ (0, react_jsx_runtime.jsx)("p", {
127005
+ className: "dsh-ig-hint",
127006
+ role: "note",
127007
+ children: t("subProxyHint")
127008
+ })
127009
+ ]
126874
127010
  }),
126875
127011
  (0, react_jsx_runtime.jsxs)("div", {
126876
127012
  className: "dsh-ig-field",
@@ -126953,7 +127089,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126953
127089
  }), (0, react_jsx_runtime.jsx)("button", {
126954
127090
  className: "dsh-ig-save",
126955
127091
  type: "submit",
126956
- disabled: row.saving || !snapshot.writable,
127092
+ disabled: row.saving || !settingsWritable,
126957
127093
  children: row.saving ? t("saving") : t("save")
126958
127094
  })]
126959
127095
  })]
@@ -127042,10 +127178,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127042
127178
  }), open ? (0, react_jsx_runtime.jsxs)("div", {
127043
127179
  className: "dsh-ig-body",
127044
127180
  children: [
127045
- !snapshot.writable ? (0, react_jsx_runtime.jsx)("p", {
127181
+ settingsMessage !== void 0 ? (0, react_jsx_runtime.jsx)("p", {
127046
127182
  className: "dsh-ig-status dsh-ig-status-readonly",
127047
127183
  role: "note",
127048
- children: t("settingsReadOnly")
127184
+ children: settingsMessage
127049
127185
  }) : null,
127050
127186
  (0, react_jsx_runtime.jsxs)("div", {
127051
127187
  className: "dsh-ig-field",
@@ -127067,7 +127203,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127067
127203
  onChange: () => {
127068
127204
  selectDefaultProvider(provider);
127069
127205
  },
127070
- disabled: !snapshot.writable
127206
+ disabled: !settingsWritable
127071
127207
  }), providerLabels[provider]]
127072
127208
  }, provider))
127073
127209
  }),
@@ -127103,7 +127239,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127103
127239
  onChange: (event) => {
127104
127240
  toggleSaveToWorkspace(event.target.checked);
127105
127241
  },
127106
- disabled: !snapshot.writable
127242
+ disabled: !settingsWritable
127107
127243
  }), (0, react_jsx_runtime.jsx)("span", {
127108
127244
  className: "dsh-ig-label",
127109
127245
  children: t("saveToWorkspace")
@@ -127133,7 +127269,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127133
127269
  if (event.key === "Enter") commitWorkspaceFolder();
127134
127270
  },
127135
127271
  placeholder: "dsh-image-gen",
127136
- disabled: !snapshot.writable
127272
+ disabled: !settingsWritable
127137
127273
  }),
127138
127274
  (0, react_jsx_runtime.jsx)("span", {
127139
127275
  className: "dsh-ig-hint",
@@ -127164,7 +127300,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127164
127300
  onChange: (event) => {
127165
127301
  toggleShowPill(event.target.checked);
127166
127302
  },
127167
- disabled: !snapshot.writable
127303
+ disabled: !settingsWritable
127168
127304
  }), (0, react_jsx_runtime.jsx)("span", {
127169
127305
  className: "dsh-ig-label",
127170
127306
  children: t("showPill")
@@ -127196,15 +127332,41 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127196
127332
  sessionId: props.sessionId
127197
127333
  });
127198
127334
  }
127335
+ /** Suppress the folded duplicate on modern hosts; render every image on legacy hosts. */
127336
+ function GeneratedImagesCard(props) {
127337
+ const block = props.block;
127338
+ let results = imageResultsFromMeta(block.meta ?? block.resultView?.meta);
127339
+ if (results.length === 0 && "kind" in block) results = ((block.resultView?.card === "generic" ? block.resultView.content : block.content) ?? []).flatMap((item) => item.type === "image" && item.attachment !== void 0 ? [{
127340
+ attachment: item.attachment,
127341
+ prompt: "Generated Image",
127342
+ provider: "",
127343
+ model: "",
127344
+ output: ""
127345
+ }] : []);
127346
+ if (props.promoted && results.length > 0) return (0, react_jsx_runtime.jsx)(PromotedResultNotice, { locale: props.locale });
127347
+ if (results.length === 0) {
127348
+ if (!("kind" in block)) return (0, react_jsx_runtime.jsx)(ImageResultCard, { locale: props.locale });
127349
+ const content = block.content ?? block.resultView?.content ?? [];
127350
+ return (0, react_jsx_runtime.jsx)("div", { children: content.filter((item) => item.type === "text").map((item, index) => (0, react_jsx_runtime.jsx)("p", { children: item.text }, index)) });
127351
+ }
127352
+ return (0, react_jsx_runtime.jsx)("div", {
127353
+ className: "dsh-ig-promoted-results",
127354
+ children: results.map((result, index) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
127355
+ result,
127356
+ locale: props.locale,
127357
+ sessionId: props.sessionId
127358
+ }, `${result.attachment.attachmentId}:${index}`))
127359
+ });
127360
+ }
127199
127361
  /** Render modern image artifacts as final conversation output instead of Tool process content. */
127200
127362
  function PromotedImageResultNode(props) {
127201
127363
  return (0, react_jsx_runtime.jsx)("div", {
127202
127364
  className: "dsh-ig-promoted-results",
127203
- children: props.node.data.results.map((result) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
127365
+ children: props.node.data.results.map((result, index) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
127204
127366
  result,
127205
127367
  locale: props.locale,
127206
127368
  sessionId: props.sessionId
127207
- }, result.attachment.attachmentId))
127369
+ }, `${result.attachment.attachmentId}:${index}`))
127208
127370
  });
127209
127371
  }
127210
127372
  function PromotedResultNotice({ locale }) {
@@ -127742,6 +127904,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127742
127904
  }
127743
127905
  //#endregion
127744
127906
  exports.GeneratedImageCard = GeneratedImageCard;
127907
+ exports.GeneratedImagesCard = GeneratedImagesCard;
127745
127908
  exports.ImageGenerationSettingsCard = ImageGenerationSettingsCard;
127746
127909
  exports.PromotedImageResultNode = PromotedImageResultNode;
127747
127910
  exports.apply = apply;