dsh-image-gen 0.6.7 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +391 -375
  3. package/README.md +391 -375
  4. package/README.zh-CN.md +3 -3
  5. package/docs/assets/readme/chat-generate.webp +0 -0
  6. package/docs/assets/readme/other-features.webp +0 -0
  7. package/lib/client.js +315 -49
  8. package/lib/client.js.map +1 -1
  9. package/lib/index.js +259 -49
  10. package/lib/types/client/conversation-image-revisions.d.ts +2 -2
  11. package/lib/types/client/gallery-view.d.ts +4 -0
  12. package/lib/types/client/index.d.ts +7 -1
  13. package/lib/types/client/multi-model-compare.d.ts +3 -3
  14. package/lib/types/client/provider-pill.d.ts +1 -1
  15. package/lib/types/client/studio-chat-panel.d.ts +23 -0
  16. package/lib/types/client/studio-view.d.ts +4 -0
  17. package/lib/types/client/tl/diagram-landing.d.ts +75 -0
  18. package/lib/types/client/tl/diagram-layout.d.ts +76 -0
  19. package/lib/types/client/tl/diagram-measure.d.ts +40 -0
  20. package/lib/types/config.d.ts +19 -2
  21. package/lib/types/diagram.d.ts +42 -0
  22. package/lib/types/openai-compatible.d.ts +3 -0
  23. package/lib/types/seedream.d.ts +7 -0
  24. package/lib/types/shared.d.ts +55 -4
  25. package/lib/types/studio-chat.d.ts +48 -0
  26. package/lib/types/studio.d.ts +3 -2
  27. package/lib/types/subscription/manager.d.ts +15 -0
  28. package/lib/types/subscription/vendors/antigravity.d.ts +10 -1
  29. package/lib/types/subscription/vendors/codex.d.ts +2 -0
  30. package/lib/types/subscription/vendors/grok.d.ts +2 -0
  31. package/lib/types/subscription.d.ts +7 -5
  32. package/package.json +11 -9
  33. package/skills/install-dsh-image-gen/SKILL.md +22 -22
  34. package/lib/types/client/inspiration-api.d.ts +0 -10
  35. package/lib/types/client/inspiration-detail.d.ts +0 -14
  36. package/lib/types/client/tldraw/generated/tldraw-assets.d.ts +0 -12
  37. package/lib/types/client/tldraw/generated/tldraw-css.d.ts +0 -1
  38. package/lib/types/client/tldraw/tldraw-style.d.ts +0 -1
  39. package/lib/types/client/tldraw/tldraw-workspace.d.ts +0 -1
  40. package/lib/types/inspiration/cache.d.ts +0 -20
  41. package/lib/types/inspiration/downloader.d.ts +0 -27
  42. package/lib/types/inspiration/routes.d.ts +0 -13
  43. package/lib/types/inspiration/sources.d.ts +0 -33
  44. package/lib/types/inspiration/types.d.ts +0 -65
  45. package/lib/types/inspiration/updater.d.ts +0 -7
package/lib/client.js CHANGED
@@ -129,6 +129,12 @@ window.__ModuleLoader__.load({
129
129
  "xai",
130
130
  "zhipu"
131
131
  ];
132
+ /**
133
+ * Providers the browser workbench can drive: the BYOK cloud set plus the
134
+ * logged-in subscription channels. ComfyUI stays out; it has its own workflow
135
+ * pipeline and no shared request shape.
136
+ */
137
+ const STUDIO_PROVIDERS = [...CLOUD_IMAGE_PROVIDERS, ...SUBSCRIPTION_PROVIDERS];
132
138
  /** The credential reference each cloud provider's API key is stored under. */
133
139
  const CLOUD_CREDENTIAL_REFS = {
134
140
  google: "GEMINI_API_KEY",
@@ -116255,6 +116261,9 @@ img.ProseMirror-separator {
116255
116261
  configuredCount: "API 已配置 · {count}",
116256
116262
  unconfiguredStatus: "API 未配置",
116257
116263
  providerStatus: "云端提供商与模型状态",
116264
+ configuredSplit: "API {api} · 订阅 {sub}",
116265
+ subSignedIn: "已登录",
116266
+ subSignedOut: "未登录",
116258
116267
  collapseSidebar: "折叠最近生成",
116259
116268
  expandSidebar: "展开最近生成",
116260
116269
  collapseGenerate: "折叠生成面板",
@@ -116360,6 +116369,9 @@ img.ProseMirror-separator {
116360
116369
  configuredCount: "API configured · {count}",
116361
116370
  unconfiguredStatus: "Not configured",
116362
116371
  providerStatus: "Provider & Model Status",
116372
+ configuredSplit: "API {api} · Sub {sub}",
116373
+ subSignedIn: "Signed in",
116374
+ subSignedOut: "Signed out",
116363
116375
  collapseSidebar: "Collapse sidebar",
116364
116376
  expandSidebar: "Expand recent list",
116365
116377
  collapseGenerate: "Collapse generate panel",
@@ -116374,7 +116386,7 @@ img.ProseMirror-separator {
116374
116386
  };
116375
116387
  /** localStorage flag remembering that the one-time infinite-canvas hint was dismissed. */
116376
116388
  const INFINITE_CANVAS_HINT_KEY = "dsh-ig-infinite-canvas-hint-dismissed";
116377
- const StudioView = ({ locale, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
116389
+ const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
116378
116390
  const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
116379
116391
  const [config, setConfig] = (0, react.useState)(null);
116380
116392
  const [configLoading, setConfigLoading] = (0, react.useState)(true);
@@ -116481,15 +116493,28 @@ img.ProseMirror-separator {
116481
116493
  const maxReferences = comparisonEnabled ? comparisonProviders.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
116482
116494
  const referencesRef = (0, react.useRef)(references);
116483
116495
  referencesRef.current = references;
116496
+ const comparisonSelectionsAllSubscription = comparisonEnabled && comparisonProviders.length > 0 && comparisonProviders.every((provider) => isSubscriptionProvider(provider));
116497
+ const singleProviderIsSubscription = !comparisonEnabled && isSubscriptionProvider(provider);
116484
116498
  const t = (key, values) => {
116485
116499
  let text = COPY$1[lang][key];
116486
116500
  for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
116487
116501
  return text;
116488
116502
  };
116489
116503
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
116490
- const loadConfig = (0, react.useCallback)(async () => {
116491
- setConfigLoading(true);
116492
- setConfigError(null);
116504
+ const formStateRef = (0, react.useRef)({
116505
+ provider,
116506
+ model
116507
+ });
116508
+ formStateRef.current = {
116509
+ provider,
116510
+ model
116511
+ };
116512
+ const loadConfig = (0, react.useCallback)(async (options) => {
116513
+ const silent = options?.silent === true;
116514
+ if (!silent) {
116515
+ setConfigLoading(true);
116516
+ setConfigError(null);
116517
+ }
116493
116518
  const controller = new AbortController();
116494
116519
  try {
116495
116520
  const response = await fetch(STUDIO_ROUTE, {
@@ -116499,20 +116524,35 @@ img.ProseMirror-separator {
116499
116524
  const payload = await response.json();
116500
116525
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
116501
116526
  setConfig(payload);
116527
+ if (silent) {
116528
+ const { provider: currentProvider, model: currentModel } = formStateRef.current;
116529
+ const activeProfile = payload.providers.find((item) => item.provider === currentProvider);
116530
+ if (activeProfile !== void 0 && activeProfile.model !== currentModel) setModel(activeProfile.model);
116531
+ setComparisonProviders((current) => current.filter((selectedProvider) => payload.providers.some((item) => item.provider === selectedProvider && item.configured)));
116532
+ return;
116533
+ }
116502
116534
  const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
116503
116535
  if (initial !== void 0) {
116504
116536
  applyProvider(initial);
116505
116537
  setComparisonProviders(initialComparisonProviders(payload.providers, initial.provider));
116506
116538
  }
116507
116539
  } catch (fetchError) {
116508
- if (!controller.signal.aborted) setConfigError(messageOf(fetchError));
116540
+ if (!silent && !controller.signal.aborted) setConfigError(messageOf(fetchError));
116509
116541
  } finally {
116510
- setConfigLoading(false);
116542
+ if (!silent) setConfigLoading(false);
116511
116543
  }
116512
116544
  }, []);
116513
116545
  (0, react.useEffect)(() => {
116514
116546
  loadConfig();
116515
116547
  }, [loadConfig]);
116548
+ (0, react.useEffect)(() => {
116549
+ if (credentialEvents === void 0) return;
116550
+ return credentialEvents.listen(() => {
116551
+ window.setTimeout(() => {
116552
+ loadConfig({ silent: true });
116553
+ }, 1e3);
116554
+ });
116555
+ }, [credentialEvents, loadConfig]);
116516
116556
  (0, react.useEffect)(() => {
116517
116557
  let mounted = true;
116518
116558
  const load = () => void getGalleryItems().then((next) => {
@@ -116584,6 +116624,8 @@ img.ProseMirror-separator {
116584
116624
  }, []);
116585
116625
  const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
116586
116626
  const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
116627
+ const apiConfiguredCount = config?.providers.filter((item) => item.configured && !isSubscriptionProvider(item.provider)).length ?? 0;
116628
+ const subscriptionSignedInCount = config?.providers.filter((item) => item.configured && isSubscriptionProvider(item.provider)).length ?? 0;
116587
116629
  const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
116588
116630
  const comparisonProfiles = (0, react.useMemo)(() => (config?.providers ?? []).filter((item) => item.configured && (mode === "generate" || item.supportsEditing)), [config, mode]);
116589
116631
  const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality), [
@@ -117179,7 +117221,10 @@ img.ProseMirror-separator {
117179
117221
  (0, react_jsx_runtime.jsx)("span", { className: `dsh-ig-status-dot ${configuredCount > 0 ? "is-ready" : "is-muted"}` }),
117180
117222
  (0, react_jsx_runtime.jsx)("span", {
117181
117223
  className: "dsh-ig-status-text",
117182
- children: configuredCount > 0 ? t("configuredCount", { count: String(configuredCount) }) : t("unconfiguredStatus")
117224
+ children: configuredCount > 0 ? subscriptionSignedInCount > 0 ? t("configuredSplit", {
117225
+ api: String(apiConfiguredCount),
117226
+ sub: String(subscriptionSignedInCount)
117227
+ }) : t("configuredCount", { count: String(apiConfiguredCount) }) : t("unconfiguredStatus")
117183
117228
  }),
117184
117229
  (0, react_jsx_runtime.jsx)(ChevronDown, {
117185
117230
  size: 11,
@@ -117200,7 +117245,7 @@ img.ProseMirror-separator {
117200
117245
  children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
117201
117246
  }), (0, react_jsx_runtime.jsx)("span", {
117202
117247
  className: "dsh-ig-status-item-tag",
117203
- children: p.configured ? t("configured") : t("unconfigured")
117248
+ children: isSubscriptionProvider(p.provider) ? p.configured ? t("subSignedIn") : t("subSignedOut") : p.configured ? t("configured") : t("unconfigured")
117204
117249
  })]
117205
117250
  }, p.provider))
117206
117251
  })]
@@ -117646,7 +117691,26 @@ img.ProseMirror-separator {
117646
117691
  }),
117647
117692
  (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
117648
117693
  ]
117649
- }), (0, react_jsx_runtime.jsxs)("div", {
117694
+ }), comparisonSelectionsAllSubscription ? (0, react_jsx_runtime.jsxs)("div", {
117695
+ className: "dsh-ig-field-grid",
117696
+ children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
117697
+ label: t("ratio"),
117698
+ value: "auto",
117699
+ onChange: () => {},
117700
+ options: [{
117701
+ value: "auto",
117702
+ label: lang === "en" ? "Channel default" : "通道默认"
117703
+ }]
117704
+ }), (0, react_jsx_runtime.jsx)(FieldSelect, {
117705
+ label: t("quality"),
117706
+ value: "auto",
117707
+ onChange: () => {},
117708
+ options: [{
117709
+ value: "auto",
117710
+ label: lang === "en" ? "Channel default" : "通道默认"
117711
+ }]
117712
+ })]
117713
+ }) : (0, react_jsx_runtime.jsxs)("div", {
117650
117714
  className: "dsh-ig-field-grid",
117651
117715
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
117652
117716
  label: t("ratio"),
@@ -117680,7 +117744,26 @@ img.ProseMirror-separator {
117680
117744
  }]
117681
117745
  })]
117682
117746
  }),
117683
- (0, react_jsx_runtime.jsxs)("div", {
117747
+ singleProviderIsSubscription ? (0, react_jsx_runtime.jsxs)("div", {
117748
+ className: "dsh-ig-field-grid",
117749
+ children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
117750
+ label: t("ratio"),
117751
+ value: "auto",
117752
+ onChange: () => {},
117753
+ options: [{
117754
+ value: "auto",
117755
+ label: lang === "en" ? "Channel default" : "通道默认"
117756
+ }]
117757
+ }), (0, react_jsx_runtime.jsx)(FieldSelect, {
117758
+ label: t("quality"),
117759
+ value: "auto",
117760
+ onChange: () => {},
117761
+ options: [{
117762
+ value: "auto",
117763
+ label: lang === "en" ? "Channel default" : "通道默认"
117764
+ }]
117765
+ })]
117766
+ }) : (0, react_jsx_runtime.jsxs)("div", {
117684
117767
  className: "dsh-ig-field-grid",
117685
117768
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
117686
117769
  label: t("ratio"),
@@ -119533,7 +119616,7 @@ img.ProseMirror-separator {
119533
119616
  return "1:1";
119534
119617
  }
119535
119618
  const GalleryViewTab = (props) => {
119536
- const { locale, sessionId, useSessions, useWorkspaces, inSidebar, defaultTab, initialCanvasSurface } = props;
119619
+ const { locale, sessionId, useSessions, useWorkspaces, credentialEvents, inSidebar, defaultTab, initialCanvasSurface } = props;
119537
119620
  const [activeTab, setActiveTab] = (0, react.useState)(defaultTab ?? "gallery");
119538
119621
  const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
119539
119622
  const [items, setItems] = (0, react.useState)([]);
@@ -120290,6 +120373,7 @@ img.ProseMirror-separator {
120290
120373
  onUsePrompt: useInspirationPrompt
120291
120374
  }) : (0, react_jsx_runtime.jsx)(StudioView, {
120292
120375
  locale,
120376
+ credentialEvents,
120293
120377
  workspace: activeWorkspace,
120294
120378
  initialPrompt: studioDraft,
120295
120379
  initialCanvasSurface,
@@ -121478,7 +121562,7 @@ img.ProseMirror-separator {
121478
121562
  function isRevision(value) {
121479
121563
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
121480
121564
  const candidate = value;
121481
- return isAttachment(candidate.attachment) && typeof candidate.provider === "string" && CLOUD_IMAGE_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";
121565
+ 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";
121482
121566
  }
121483
121567
  function isAttachment(value) {
121484
121568
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -121523,19 +121607,36 @@ img.ProseMirror-separator {
121523
121607
  switchFailed: "Switch failed"
121524
121608
  }
121525
121609
  };
121526
- /** Short row labels; the settings card keeps the longer descriptive names. */
121610
+ /** Provider row names, per locale. Brand words stay untranslated; only the
121611
+ * qualifier ("订阅" / "Sub") localizes, so the pill and the menu agree with the
121612
+ * settings card in both languages. */
121527
121613
  const PILL_PROVIDER_LABELS = {
121528
- google: "Gemini",
121529
- openai: "OpenAI",
121530
- "openai-compat": "OpenAI 兼容",
121531
- seedream: "Seedream",
121532
- dashscope: "DashScope",
121533
- xai: "Grok",
121534
- zhipu: "智谱 GLM",
121535
- comfyui: "ComfyUI",
121536
- "chatgpt-sub": "ChatGPT 订阅",
121537
- "grok-sub": "Grok 订阅",
121538
- "google-sub": "Google 订阅"
121614
+ zh: {
121615
+ google: "Gemini",
121616
+ openai: "OpenAI",
121617
+ "openai-compat": "OpenAI 兼容",
121618
+ seedream: "Seedream",
121619
+ dashscope: "DashScope",
121620
+ xai: "Grok",
121621
+ zhipu: "智谱 GLM",
121622
+ comfyui: "ComfyUI",
121623
+ "chatgpt-sub": "ChatGPT 订阅",
121624
+ "grok-sub": "Grok 订阅",
121625
+ "google-sub": "Google 订阅"
121626
+ },
121627
+ en: {
121628
+ google: "Gemini",
121629
+ openai: "OpenAI",
121630
+ "openai-compat": "OpenAI Compatible",
121631
+ seedream: "Seedream",
121632
+ dashscope: "DashScope",
121633
+ xai: "Grok",
121634
+ zhipu: "Zhipu GLM",
121635
+ comfyui: "ComfyUI",
121636
+ "chatgpt-sub": "ChatGPT Sub",
121637
+ "grok-sub": "Grok Sub",
121638
+ "google-sub": "Google Sub"
121639
+ }
121539
121640
  };
121540
121641
  /** Compute the fixed-position anchor from the pill button: open upward when
121541
121642
  * the chat pane leaves room above the composer, drop down otherwise. */
@@ -121787,7 +121888,7 @@ img.ProseMirror-separator {
121787
121888
  }),
121788
121889
  (0, react_jsx_runtime.jsx)("span", {
121789
121890
  className: "dsh-ig-pill-provider",
121790
- children: PILL_PROVIDER_LABELS[current]
121891
+ children: PILL_PROVIDER_LABELS[lang][current]
121791
121892
  }),
121792
121893
  (0, react_jsx_runtime.jsx)("span", {
121793
121894
  className: `dsh-ig-pill-caret${open ? " dsh-ig-pill-caret-open" : ""}`,
@@ -121833,7 +121934,7 @@ img.ProseMirror-separator {
121833
121934
  className: "dsh-ig-pill-option-text",
121834
121935
  children: [(0, react_jsx_runtime.jsx)("span", {
121835
121936
  className: "dsh-ig-pill-option-name",
121836
- children: PILL_PROVIDER_LABELS[provider]
121937
+ children: PILL_PROVIDER_LABELS[lang][provider]
121837
121938
  }), (0, react_jsx_runtime.jsx)("span", {
121838
121939
  className: "dsh-ig-pill-option-sub",
121839
121940
  children: subtitleOf(provider)
@@ -121871,16 +121972,20 @@ img.ProseMirror-separator {
121871
121972
  }
121872
121973
  /** Styles for the pill and its fixed-position menu; injected by the client entry. */
121873
121974
  const PROVIDER_PILL_STYLE = `
121874
- .dsh-ig-pill-root{position:relative;display:inline-flex;align-items:center;height:26px}
121875
- .dsh-ig-pill-button{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 8px 0 7px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:13px;background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,#4b5563);font:inherit;font-size:12px;line-height:1;cursor:pointer;transition:border-color .15s,background .15s,color .15s;-webkit-user-select:none;user-select:none}
121975
+ .dsh-ig-pill-root{position:relative;display:inline-flex;align-items:center;height:28px}
121976
+ .dsh-ig-pill-button{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 9px 0 8px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,#4b5563);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:border-color .15s,background .15s,color .15s;-webkit-user-select:none;user-select:none}
121876
121977
  .dsh-ig-pill-button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#9ca3af);color:var(--dsw-alias-label-primary,#111827)}
121877
121978
  .dsh-ig-pill-button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:1px}
121878
121979
  .dsh-ig-pill-button:disabled{opacity:.5;cursor:not-allowed}
121879
121980
  .dsh-ig-pill-button-open{border-color:var(--dsw-alias-brand-primary,#4c78ff);color:var(--dsw-alias-label-primary,#111827)}
121880
121981
  .dsh-ig-pill-icon{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
121881
- .dsh-ig-pill-provider{max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
121982
+ .dsh-ig-pill-provider{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
121882
121983
  .dsh-ig-pill-caret{display:inline-flex;flex:none;transition:transform .15s}
121883
121984
  .dsh-ig-pill-caret-open{transform:rotate(180deg)}
121985
+ @container (max-width:700px){
121986
+ .dsh-ig-pill-caret{display:none}
121987
+ .dsh-ig-pill-button{padding:0 7px 0 6px}
121988
+ }
121884
121989
  .dsh-ig-pill-menu{position:fixed;z-index:1000;min-width:264px;max-width:min(320px,calc(100vw - 24px));padding:6px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);box-shadow:0 12px 32px rgba(0,0,0,.16),0 2px 8px rgba(0,0,0,.08);max-height:min(320px,calc(100vh - 48px));overflow-y:auto}
121885
121990
  .dsh-ig-pill-menu-title{padding:6px 8px 4px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary,#6b7280)}
121886
121991
  .dsh-ig-pill-option{display:flex;width:100%;align-items:center;gap:9px;padding:7px 8px;border:0;border-radius:8px;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer}
@@ -121900,7 +122005,7 @@ img.ProseMirror-separator {
121900
122005
  `;
121901
122006
  //#endregion
121902
122007
  //#region lib/types/client/index.js
121903
- console.info(`[dsh-image-gen] canvas bundle 2026-09-14T07:49:16.664Z`);
122008
+ console.info(`[dsh-image-gen] canvas bundle 2026-09-20T15:13:07.204Z`);
121904
122009
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
121905
122010
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
121906
122011
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -121939,8 +122044,8 @@ img.ProseMirror-separator {
121939
122044
  subLoginOk: "登录成功",
121940
122045
  subModelLabel: "订阅模型",
121941
122046
  subModelHint: "由订阅通道固定,不可更改。",
121942
- subEditUnsupported: "订阅通道暂不支持图生图(编辑图片),仅支持文字生图。",
121943
- subDefaultHint: "订阅 Provider 只能生成,不能编辑图片;需要编辑时请切换到 API Key Provider。",
122047
+ subEditCapability: "订阅生图 · 图生图:支持。对话 edit_image、Studio 图生图与多模型对比均可上传参考图(最多 5 张)。",
122048
+ subDefaultHint: "订阅 Provider 已支持文字生图与图生图;图生图使用各订阅渠道默认参数。",
121944
122049
  apiKeyLabel: "{provider} API Key",
121945
122050
  apiKeyPlaceholder: "留空即可保留已配置的 Key",
121946
122051
  apiKeyHint: "安全保存为 {key};页面不会读回明文。",
@@ -121961,6 +122066,7 @@ img.ProseMirror-separator {
121961
122066
  keyCleared: "已清除 Key",
121962
122067
  clearKeyFailed: "清除 Key 失败",
121963
122068
  saveKeyFailed: "保存 Key 失败",
122069
+ saveKeyFirst: "请先保存 API Key,再拉取模型或测试连接。",
121964
122070
  clearKeyUnsupported: "当前版本 DSH 不支持在此清除 Key,请到凭据管理中删除。",
121965
122071
  endpoint: "接口地址",
121966
122072
  reset: "重置",
@@ -121975,6 +122081,18 @@ img.ProseMirror-separator {
121975
122081
  editExtra: "附加 JSON 字段",
121976
122082
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
121977
122083
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
122084
+ arkOutputFormat: "输出格式",
122085
+ arkOutputFormatPng: "PNG(无损,带透明通道)",
122086
+ arkOutputFormatJpeg: "JPEG(方舟默认,有损)",
122087
+ arkOutputFormatHint: "JPEG 会在主体边缘留下压缩色斑,抠图时变成难去掉的彩色毛边。要后期加工就选 PNG。",
122088
+ arkWatermark: "AI 生成水印",
122089
+ arkWatermarkOn: "添加(方舟默认)",
122090
+ arkWatermarkOff: "不添加",
122091
+ arkWatermarkHint: "方舟默认在右下角烙上“AI 生成”标识。做游戏素材或需要后期处理时关掉。",
122092
+ arkBackground: "背景",
122093
+ arkBackgroundOpaque: "不透明(方舟默认)",
122094
+ arkBackgroundTransparent: "透明",
122095
+ arkBackgroundHint: "仅图生图有效,且要求参考图本身带透明通道;文生图会被方舟拒绝,因此这一项只在图生图时发出。",
121978
122096
  editExtraInvalid: "附加 JSON 字段必须是合法的 JSON 对象。",
121979
122097
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
121980
122098
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
@@ -122068,8 +122186,8 @@ img.ProseMirror-separator {
122068
122186
  subLoginOk: "Signed in",
122069
122187
  subModelLabel: "Subscription model",
122070
122188
  subModelHint: "Fixed by the subscription channel; not changeable.",
122071
- subEditUnsupported: "The subscription channel does not support image editing yet; text-to-image only.",
122072
- subDefaultHint: "Subscription providers can only generate; switch to an API-key provider for editing.",
122189
+ subEditCapability: "Subscription image editing is now supported: edit_image in chats, Studio and multi-model compare all accept reference images (up to 5).",
122190
+ subDefaultHint: "Subscription providers support both text-to-image and image editing; edits use each channel's default parameters.",
122073
122191
  apiKeyLabel: "{provider} API Key",
122074
122192
  apiKeyPlaceholder: "Leave empty to keep configured key",
122075
122193
  apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
@@ -122090,6 +122208,7 @@ img.ProseMirror-separator {
122090
122208
  keyCleared: "Key cleared",
122091
122209
  clearKeyFailed: "Failed to clear key",
122092
122210
  saveKeyFailed: "Failed to save the key",
122211
+ saveKeyFirst: "Save the API key first, then fetch models or test the connection.",
122093
122212
  clearKeyUnsupported: "This DSH build cannot clear keys here; remove it from credential management instead.",
122094
122213
  endpoint: "Endpoint / Base URL",
122095
122214
  reset: "Reset",
@@ -122104,6 +122223,18 @@ img.ProseMirror-separator {
122104
122223
  editExtra: "Extra JSON fields",
122105
122224
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122106
122225
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
122226
+ arkOutputFormat: "Output format",
122227
+ arkOutputFormatPng: "PNG (lossless, keeps an alpha channel)",
122228
+ arkOutputFormatJpeg: "JPEG (Ark default, lossy)",
122229
+ arkOutputFormatHint: "JPEG ringing around the subject turns into coloured fringing that is hard to remove when cutting the background out. Pick PNG if you plan to edit the result.",
122230
+ arkWatermark: "AI generated watermark",
122231
+ arkWatermarkOn: "Add (Ark default)",
122232
+ arkWatermarkOff: "Do not add",
122233
+ arkWatermarkHint: "Ark stamps an \"AI generated\" mark into the bottom-right corner by default. Turn it off for game assets or any post-processing.",
122234
+ arkBackground: "Background",
122235
+ arkBackgroundOpaque: "Opaque (Ark default)",
122236
+ arkBackgroundTransparent: "Transparent",
122237
+ 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.",
122107
122238
  editExtraInvalid: "Extra JSON fields must be a valid JSON object.",
122108
122239
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
122109
122240
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
@@ -122516,16 +122647,25 @@ img.ProseMirror-separator {
122516
122647
  } };
122517
122648
  const injectSettingsItem = (owner) => {
122518
122649
  const ownerRegister = owner.slots.register.bind(owner.slots);
122650
+ const injectSettingsFace = () => ({
122651
+ scope,
122652
+ credentials: credentialsProxy,
122653
+ credentialsAvailable,
122654
+ locale,
122655
+ credentialEvents
122656
+ });
122657
+ owner.slots.inject("settings.plugins.tab", () => ownerRegister({
122658
+ name: "settings.plugins.tab",
122659
+ id: IMAGE_GENERATION_NAMESPACE,
122660
+ order: 30,
122661
+ label: () => locale?.getSnapshot?.()?.active?.startsWith("en") ? "Image generation" : "图像生成",
122662
+ locale,
122663
+ inject: injectSettingsFace
122664
+ }, ImageGenerationSettingsCard));
122519
122665
  owner.slots.inject("settings.plugin.item", () => ownerRegister({
122520
122666
  name: "settings.plugin.item",
122521
122667
  key: IMAGE_GENERATION_NAMESPACE,
122522
- inject: () => ({
122523
- scope,
122524
- credentials: credentialsProxy,
122525
- credentialsAvailable,
122526
- locale,
122527
- credentialEvents
122528
- })
122668
+ inject: injectSettingsFace
122529
122669
  }, ImageGenerationSettingsCard));
122530
122670
  };
122531
122671
  const injectComposerPill = (owner) => {
@@ -122597,7 +122737,10 @@ img.ProseMirror-separator {
122597
122737
  label: () => {
122598
122738
  return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "Gallery" : "画廊";
122599
122739
  },
122600
- inject: () => ({ locale })
122740
+ inject: () => ({
122741
+ locale,
122742
+ credentialEvents
122743
+ })
122601
122744
  }, GalleryViewTab));
122602
122745
  ctx.inject(["sidebarRightTabs"], (owner) => {
122603
122746
  const sidebarTabs = owner.get("sidebarRightTabs");
@@ -122620,6 +122763,7 @@ img.ProseMirror-separator {
122620
122763
  key: SIDEBAR_STUDIO_TAB_ID,
122621
122764
  inject: () => ({
122622
122765
  locale,
122766
+ credentialEvents,
122623
122767
  inSidebar: true,
122624
122768
  defaultTab: "studio",
122625
122769
  initialCanvasSurface: "infinite"
@@ -122684,7 +122828,10 @@ img.ProseMirror-separator {
122684
122828
  activeWorkflow: "",
122685
122829
  timeoutSeconds: DEFAULT_COMFYUI_TIMEOUT_MS / 1e3,
122686
122830
  editFormat: "multipart",
122687
- editExtraText: ""
122831
+ editExtraText: "",
122832
+ outputFormat: "jpeg",
122833
+ watermark: true,
122834
+ background: "opaque"
122688
122835
  };
122689
122836
  }
122690
122837
  /** Settings field each cloud provider persists its model under. */
@@ -122753,6 +122900,11 @@ img.ProseMirror-separator {
122753
122900
  ...provider === "openai-compat" ? {
122754
122901
  editFormat: value?.openaiCompatEditFormat === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart",
122755
122902
  editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
122903
+ } : {},
122904
+ ...provider === "seedream" ? {
122905
+ outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
122906
+ watermark: value?.seedreamWatermark !== false,
122907
+ background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
122756
122908
  } : {}
122757
122909
  };
122758
122910
  return rows;
@@ -122965,6 +123117,11 @@ img.ProseMirror-separator {
122965
123117
  workflows: resolveComfyUIWorkflows(value ?? {}),
122966
123118
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
122967
123119
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
123120
+ } : {},
123121
+ ...provider === "seedream" ? {
123122
+ outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
123123
+ watermark: value?.seedreamWatermark !== false,
123124
+ background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
122968
123125
  } : {}
122969
123126
  };
122970
123127
  return next;
@@ -123096,6 +123253,11 @@ img.ProseMirror-separator {
123096
123253
  await props.scope.set("openaiCompatEditFormat", row.editFormat);
123097
123254
  await props.scope.set("openaiCompatEditExtra", editExtra);
123098
123255
  }
123256
+ if (provider === "seedream") {
123257
+ await props.scope.set("seedreamOutputFormat", row.outputFormat);
123258
+ await props.scope.set("seedreamWatermark", row.watermark);
123259
+ await props.scope.set("seedreamBackground", row.background);
123260
+ }
123099
123261
  if (row.keyInput.trim().length > 0) {
123100
123262
  const keyRef = cloudCredentialRef(provider);
123101
123263
  if (keyRef === void 0) throw new Error(t("comfyuiNoKey"));
@@ -123110,7 +123272,10 @@ img.ProseMirror-separator {
123110
123272
  }
123111
123273
  updateRow(provider, {
123112
123274
  message: t("saved"),
123113
- messageIsError: false
123275
+ messageIsError: false,
123276
+ testResult: void 0,
123277
+ modelFetchMessage: "",
123278
+ modelFetchIsError: false
123114
123279
  });
123115
123280
  } catch (cause) {
123116
123281
  updateRow(provider, {
@@ -123123,6 +123288,14 @@ img.ProseMirror-separator {
123123
123288
  };
123124
123289
  /** Probe through the host route so the browser side never touches credential values. */
123125
123290
  const testConnection = async (provider) => {
123291
+ if (rows[provider].keyInput.trim().length > 0) {
123292
+ updateRow(provider, {
123293
+ message: t("saveKeyFirst"),
123294
+ messageIsError: false,
123295
+ testResult: void 0
123296
+ });
123297
+ return;
123298
+ }
123126
123299
  updateRow(provider, {
123127
123300
  testing: true,
123128
123301
  testResult: void 0,
@@ -123149,6 +123322,13 @@ img.ProseMirror-separator {
123149
123322
  };
123150
123323
  /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
123151
123324
  const fetchProviderModels = async (provider) => {
123325
+ if (rows[provider].keyInput.trim().length > 0) {
123326
+ updateRow(provider, {
123327
+ modelFetchMessage: t("saveKeyFirst"),
123328
+ modelFetchIsError: false
123329
+ });
123330
+ return;
123331
+ }
123152
123332
  updateRow(provider, {
123153
123333
  fetchingModels: true,
123154
123334
  modelFetchMessage: "",
@@ -123497,7 +123677,7 @@ img.ProseMirror-separator {
123497
123677
  (0, react_jsx_runtime.jsx)("button", {
123498
123678
  type: "button",
123499
123679
  className: "dsh-ig-btn-secondary",
123500
- disabled: row.fetchingModels || !snapshot.writable,
123680
+ disabled: row.fetchingModels || row.saving || !snapshot.writable,
123501
123681
  onClick: () => {
123502
123682
  fetchProviderModels(provider);
123503
123683
  },
@@ -123551,6 +123731,92 @@ img.ProseMirror-separator {
123551
123731
  })
123552
123732
  ]
123553
123733
  }) : null,
123734
+ provider === "seedream" ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
123735
+ (0, react_jsx_runtime.jsxs)("label", {
123736
+ className: "dsh-ig-field",
123737
+ children: [
123738
+ (0, react_jsx_runtime.jsx)("span", {
123739
+ className: "dsh-ig-label",
123740
+ children: t("arkOutputFormat")
123741
+ }),
123742
+ (0, react_jsx_runtime.jsxs)("select", {
123743
+ className: "dsh-ig-input",
123744
+ value: row.outputFormat,
123745
+ onChange: (event) => {
123746
+ updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
123747
+ },
123748
+ disabled: !snapshot.writable,
123749
+ children: [(0, react_jsx_runtime.jsx)("option", {
123750
+ value: "jpeg",
123751
+ children: t("arkOutputFormatJpeg")
123752
+ }), (0, react_jsx_runtime.jsx)("option", {
123753
+ value: "png",
123754
+ children: t("arkOutputFormatPng")
123755
+ })]
123756
+ }),
123757
+ (0, react_jsx_runtime.jsx)("span", {
123758
+ className: "dsh-ig-hint",
123759
+ children: t("arkOutputFormatHint")
123760
+ })
123761
+ ]
123762
+ }),
123763
+ (0, react_jsx_runtime.jsxs)("label", {
123764
+ className: "dsh-ig-field",
123765
+ children: [
123766
+ (0, react_jsx_runtime.jsx)("span", {
123767
+ className: "dsh-ig-label",
123768
+ children: t("arkWatermark")
123769
+ }),
123770
+ (0, react_jsx_runtime.jsxs)("select", {
123771
+ className: "dsh-ig-input",
123772
+ value: row.watermark ? "on" : "off",
123773
+ onChange: (event) => {
123774
+ updateRow(provider, { watermark: event.target.value === "on" });
123775
+ },
123776
+ disabled: !snapshot.writable,
123777
+ children: [(0, react_jsx_runtime.jsx)("option", {
123778
+ value: "on",
123779
+ children: t("arkWatermarkOn")
123780
+ }), (0, react_jsx_runtime.jsx)("option", {
123781
+ value: "off",
123782
+ children: t("arkWatermarkOff")
123783
+ })]
123784
+ }),
123785
+ (0, react_jsx_runtime.jsx)("span", {
123786
+ className: "dsh-ig-hint",
123787
+ children: t("arkWatermarkHint")
123788
+ })
123789
+ ]
123790
+ }),
123791
+ (0, react_jsx_runtime.jsxs)("label", {
123792
+ className: "dsh-ig-field",
123793
+ children: [
123794
+ (0, react_jsx_runtime.jsx)("span", {
123795
+ className: "dsh-ig-label",
123796
+ children: t("arkBackground")
123797
+ }),
123798
+ (0, react_jsx_runtime.jsxs)("select", {
123799
+ className: "dsh-ig-input",
123800
+ value: row.background,
123801
+ onChange: (event) => {
123802
+ updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
123803
+ },
123804
+ disabled: !snapshot.writable,
123805
+ children: [(0, react_jsx_runtime.jsx)("option", {
123806
+ value: "opaque",
123807
+ children: t("arkBackgroundOpaque")
123808
+ }), (0, react_jsx_runtime.jsx)("option", {
123809
+ value: "transparent",
123810
+ children: t("arkBackgroundTransparent")
123811
+ })]
123812
+ }),
123813
+ (0, react_jsx_runtime.jsx)("span", {
123814
+ className: "dsh-ig-hint",
123815
+ children: t("arkBackgroundHint")
123816
+ })
123817
+ ]
123818
+ })
123819
+ ] }) : null,
123554
123820
  provider === "openai-compat" && row.editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
123555
123821
  className: "dsh-ig-field",
123556
123822
  children: [
@@ -123586,7 +123852,7 @@ img.ProseMirror-separator {
123586
123852
  (0, react_jsx_runtime.jsx)("button", {
123587
123853
  type: "button",
123588
123854
  className: "dsh-ig-btn-secondary",
123589
- disabled: row.testing,
123855
+ disabled: row.testing || row.saving,
123590
123856
  onClick: () => {
123591
123857
  testConnection(provider);
123592
123858
  },
@@ -123604,7 +123870,7 @@ img.ProseMirror-separator {
123604
123870
  (0, react_jsx_runtime.jsx)("button", {
123605
123871
  className: "dsh-ig-save",
123606
123872
  type: "submit",
123607
- disabled: row.saving || !snapshot.writable,
123873
+ disabled: row.saving || row.testing || row.fetchingModels || !snapshot.writable,
123608
123874
  children: row.saving ? t("saving") : t("save")
123609
123875
  })
123610
123876
  ]
@@ -123871,8 +124137,8 @@ img.ProseMirror-separator {
123871
124137
  (0, react_jsx_runtime.jsx)("div", {
123872
124138
  className: "dsh-ig-field",
123873
124139
  children: (0, react_jsx_runtime.jsx)("span", {
123874
- className: "dsh-ig-label",
123875
- children: t("subEditUnsupported")
124140
+ className: "dsh-ig-hint",
124141
+ children: t("subEditCapability")
123876
124142
  })
123877
124143
  }),
123878
124144
  (0, react_jsx_runtime.jsxs)("div", {