dsh-image-gen 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -17,20 +17,52 @@ window.__ModuleLoader__.load({
17
17
  const INSPIRATION_ROUTE = "/plugins/dsh-image-gen/inspiration";
18
18
  /** Browser route used for saving generated images to workspace on demand. */
19
19
  const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
20
+ /** Browser route the settings card probes provider connectivity through. */
21
+ const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
20
22
  /** Namespace persisted through DSH Settings. */
21
23
  const IMAGE_GENERATION_NAMESPACE = "image-generation";
24
+ /** Supported providers. */
25
+ const IMAGE_PROVIDERS = [
26
+ "google",
27
+ "openai",
28
+ "openai-compat",
29
+ "seedream",
30
+ "dashscope",
31
+ "xai",
32
+ "zhipu",
33
+ "comfyui"
34
+ ];
22
35
  /** Providers supported by the first browser workbench release. */
23
36
  const CLOUD_IMAGE_PROVIDERS = [
24
37
  "google",
25
38
  "openai",
39
+ "openai-compat",
26
40
  "seedream",
27
- "dashscope"
41
+ "dashscope",
42
+ "xai",
43
+ "zhipu"
28
44
  ];
45
+ /** The credential reference each cloud provider's API key is stored under. */
46
+ const CLOUD_CREDENTIAL_REFS = {
47
+ google: "GEMINI_API_KEY",
48
+ openai: "OPENAI_API_KEY",
49
+ "openai-compat": "DSH_IMAGE_GEN_OPENAI_COMPAT_KEY",
50
+ seedream: "ARK_API_KEY",
51
+ dashscope: "DASHSCOPE_API_KEY",
52
+ xai: "XAI_API_KEY",
53
+ zhipu: "ZHIPUAI_API_KEY"
54
+ };
55
+ /** The credential reference storing this provider's API key, when it uses one. */
56
+ function cloudCredentialRef(provider) {
57
+ return CLOUD_IMAGE_PROVIDERS.includes(provider) ? CLOUD_CREDENTIAL_REFS[provider] : void 0;
58
+ }
29
59
  /** Default endpoints and base URLs. */
30
60
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
31
61
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
32
62
  const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
33
63
  const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
64
+ const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
65
+ const DEFAULT_ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
34
66
  const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
35
67
  const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
36
68
  const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
@@ -39,6 +71,8 @@ window.__ModuleLoader__.load({
39
71
  const DEFAULT_OPENAI_MODEL = "gpt-image-2";
40
72
  const DEFAULT_SEEDREAM_MODEL = "doubao-seedream-5-0-260128";
41
73
  const DEFAULT_DASHSCOPE_MODEL = "qwen-image-3.0";
74
+ const DEFAULT_XAI_MODEL = "grok-imagine-image";
75
+ const DEFAULT_ZHIPU_MODEL = "glm-image";
42
76
  /** Named workflows, falling back to the legacy single-workflow fields when the list is empty. */
43
77
  function resolveComfyUIWorkflows(source) {
44
78
  const named = [];
@@ -85,15 +119,21 @@ window.__ModuleLoader__.load({
85
119
  const DEFAULT_MODELS = {
86
120
  google: DEFAULT_GOOGLE_MODEL,
87
121
  openai: DEFAULT_OPENAI_MODEL,
122
+ "openai-compat": "",
88
123
  seedream: DEFAULT_SEEDREAM_MODEL,
89
124
  dashscope: DEFAULT_DASHSCOPE_MODEL,
125
+ xai: DEFAULT_XAI_MODEL,
126
+ zhipu: DEFAULT_ZHIPU_MODEL,
90
127
  comfyui: DEFAULT_COMFYUI_WORKFLOW_LABEL
91
128
  };
92
129
  const DEFAULT_BASE_URLS = {
93
130
  google: DEFAULT_GOOGLE_ENDPOINT,
94
131
  openai: DEFAULT_OPENAI_BASE_URL,
132
+ "openai-compat": "",
95
133
  seedream: DEFAULT_SEEDREAM_BASE_URL,
96
134
  dashscope: DEFAULT_DASHSCOPE_ENDPOINT,
135
+ xai: DEFAULT_XAI_BASE_URL,
136
+ zhipu: DEFAULT_ZHIPU_BASE_URL,
97
137
  comfyui: DEFAULT_COMFYUI_BASE_URL
98
138
  };
99
139
  //#endregion
@@ -1360,12 +1400,12 @@ window.__ModuleLoader__.load({
1360
1400
  comparePartial: "{success} 个模型生成成功,{failed} 个失败",
1361
1401
  comparing: "正在对比(共 {count} 个模型)…",
1362
1402
  compareStart: "开始对比",
1363
- saveToGallery: "收藏进画廊",
1364
- saveSelected: "收藏选中({count})",
1365
- savedToGallery: "已收藏进画廊",
1366
- savedSelected: "已收藏 {count} 张图片",
1367
- inGallery: "已在画廊",
1368
- needSelectResult: "请先勾选要收藏的图片",
1403
+ saveToGallery: "保存",
1404
+ saveSelected: "保存({count})",
1405
+ savedToGallery: "已保存到画廊",
1406
+ savedSelected: "已保存 {count} 张图片",
1407
+ inGallery: "已保存",
1408
+ needSelectResult: "请先勾选要保存的图片",
1369
1409
  retry: "重新加载",
1370
1410
  configLoadFailed: "工作台配置加载失败,请检查服务后重试。",
1371
1411
  noProvider: "请先在设置中配置至少一个云端图像 Provider 的 API Key。",
@@ -4267,7 +4307,7 @@ window.__ModuleLoader__.load({
4267
4307
  ratio: "auto",
4268
4308
  quality: normalized === "1K" || normalized === "4K" ? normalized : "2K"
4269
4309
  };
4270
- if (provider === "openai") return {
4310
+ if (provider === "openai" || provider === "openai-compat" || provider === "xai" || provider === "zhipu") return {
4271
4311
  ratio: ratioFromSize(normalized, "x"),
4272
4312
  quality: "standard"
4273
4313
  };
@@ -4309,9 +4349,12 @@ window.__ModuleLoader__.load({
4309
4349
  tabTasks: "任务",
4310
4350
  filterAllProviders: "全部提供商",
4311
4351
  filterGoogle: "Google Gemini",
4312
- filterOpenAI: "OpenAI / 中转站",
4352
+ filterOpenAI: "OpenAI",
4353
+ filterOpenAICompat: "OpenAI 兼容",
4313
4354
  filterSeedream: "字节 Seedream",
4314
4355
  filterDashScope: "阿里 DashScope",
4356
+ filterXAI: "xAI Grok",
4357
+ filterZhipu: "智谱 GLM",
4315
4358
  filterComfyUI: "本地 ComfyUI",
4316
4359
  filterAllModels: "全部模型",
4317
4360
  filterAllRatios: "全部比例",
@@ -4391,9 +4434,12 @@ window.__ModuleLoader__.load({
4391
4434
  tabTasks: "Tasks",
4392
4435
  filterAllProviders: "All Providers",
4393
4436
  filterGoogle: "Google Gemini",
4394
- filterOpenAI: "OpenAI / Relay",
4437
+ filterOpenAI: "OpenAI",
4438
+ filterOpenAICompat: "OpenAI-compatible",
4395
4439
  filterSeedream: "ByteDance Seedream",
4396
4440
  filterDashScope: "Aliyun DashScope",
4441
+ filterXAI: "xAI Grok",
4442
+ filterZhipu: "Zhipu GLM",
4397
4443
  filterComfyUI: "Local ComfyUI",
4398
4444
  filterAllModels: "All Models",
4399
4445
  filterAllRatios: "All Ratios",
@@ -5079,6 +5125,10 @@ window.__ModuleLoader__.load({
5079
5125
  value: "openai",
5080
5126
  children: t("filterOpenAI")
5081
5127
  }),
5128
+ (0, react_jsx_runtime.jsx)("option", {
5129
+ value: "openai-compat",
5130
+ children: t("filterOpenAICompat")
5131
+ }),
5082
5132
  (0, react_jsx_runtime.jsx)("option", {
5083
5133
  value: "seedream",
5084
5134
  children: t("filterSeedream")
@@ -5087,6 +5137,14 @@ window.__ModuleLoader__.load({
5087
5137
  value: "dashscope",
5088
5138
  children: t("filterDashScope")
5089
5139
  }),
5140
+ (0, react_jsx_runtime.jsx)("option", {
5141
+ value: "xai",
5142
+ children: t("filterXAI")
5143
+ }),
5144
+ (0, react_jsx_runtime.jsx)("option", {
5145
+ value: "zhipu",
5146
+ children: t("filterZhipu")
5147
+ }),
5090
5148
  (0, react_jsx_runtime.jsx)("option", {
5091
5149
  value: "comfyui",
5092
5150
  children: t("filterComfyUI")
@@ -5872,7 +5930,7 @@ window.__ModuleLoader__.load({
5872
5930
  @keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
5873
5931
  .dsh-ig-workbench-toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:rgba(15,23,42,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff;padding:8px 18px;border-radius:10px;font-size:12.5px;font-weight:500;box-shadow:0 12px 32px rgba(0,0,0,0.3);z-index:99999;animation:dshIgFade .15s ease-out;pointer-events:none}
5874
5932
  @keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
5875
- @media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button span{display:none}}
5933
+ @media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button,.dsh-ig-result-strip button{padding:0 7px;font-size:10px;gap:4px}.dsh-ig-result-actions>div,.dsh-ig-result-strip>div{gap:5px}}
5876
5934
  @media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
5877
5935
  @media(max-width:560px){.dsh-ig-workbench-heading>div{display:block}.dsh-ig-workbench-heading h1{font-size:15px}.dsh-ig-provider-quick select{min-width:128px;max-width:150px}.dsh-ig-canvas{padding:12px;min-height:300px}.dsh-ig-result-strip,.dsh-ig-result-actions{align-items:flex-start;flex-direction:column}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{width:100%}.dsh-ig-result-strip button,.dsh-ig-result-actions button{flex:1;justify-content:center}.dsh-ig-result-actions button span{display:inline}.dsh-ig-field-grid{grid-template-columns:1fr}.dsh-ig-result-meta span+span:before{display:none}}
5878
5936
  @media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
@@ -6137,7 +6195,7 @@ window.__ModuleLoader__.load({
6137
6195
  function isRevision(value) {
6138
6196
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
6139
6197
  const candidate = value;
6140
- return isAttachment(candidate.attachment) && (candidate.provider === "google" || candidate.provider === "openai" || candidate.provider === "seedream" || candidate.provider === "dashscope") && 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";
6198
+ 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";
6141
6199
  }
6142
6200
  function isAttachment(value) {
6143
6201
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -6145,33 +6203,388 @@ window.__ModuleLoader__.load({
6145
6203
  return typeof candidate.attachmentId === "string" && (candidate.mediaType === "image/png" || candidate.mediaType === "image/jpeg" || candidate.mediaType === "image/webp" || candidate.mediaType === "image/gif") && typeof candidate.bytes === "number" && typeof candidate.width === "number" && typeof candidate.height === "number";
6146
6204
  }
6147
6205
  //#endregion
6148
- //#region lib/types/client/index.js
6149
- const KEY_REF = {
6150
- google: "GEMINI_API_KEY",
6151
- openai: "OPENAI_API_KEY",
6152
- seedream: "ARK_API_KEY",
6153
- dashscope: "DASHSCOPE_API_KEY"
6206
+ //#region lib/types/client/provider-pill.js
6207
+ /**
6208
+ * Composer tool-row pill: switch the default image provider without leaving
6209
+ * the chat. Registered into the official 'conversation.input.right' slot; the
6210
+ * write path is the same `provider` settings field the settings card uses, so
6211
+ * the two views can never disagree.
6212
+ */
6213
+ const PILL_DICT = {
6214
+ zh: {
6215
+ pillLabel: "生图",
6216
+ menuTitle: "生图模型",
6217
+ keyConfigured: "Key 已配置",
6218
+ keyMissing: "未配置 Key",
6219
+ noKeyNeeded: "无需 Key",
6220
+ noWorkflow: "未导入工作流",
6221
+ readOnly: "图像设置为只读,无法在此切换",
6222
+ switchFailed: "切换失败"
6223
+ },
6224
+ en: {
6225
+ pillLabel: "Image",
6226
+ menuTitle: "Image model",
6227
+ keyConfigured: "Key configured",
6228
+ keyMissing: "No key",
6229
+ noKeyNeeded: "No key needed",
6230
+ noWorkflow: "No workflow",
6231
+ readOnly: "Image settings are read-only; switch them in the config source",
6232
+ switchFailed: "Switch failed"
6233
+ }
6234
+ };
6235
+ /** Short row labels; the settings card keeps the longer descriptive names. */
6236
+ const PILL_PROVIDER_LABELS = {
6237
+ google: "Gemini",
6238
+ openai: "OpenAI",
6239
+ "openai-compat": "OpenAI 兼容",
6240
+ seedream: "Seedream",
6241
+ dashscope: "DashScope",
6242
+ xai: "Grok",
6243
+ zhipu: "智谱 GLM",
6244
+ comfyui: "ComfyUI"
6154
6245
  };
6246
+ /** Whether the user opted into the composer pill; hidden (not merely inert) when off. */
6247
+ function pillVisible(value) {
6248
+ return value?.showProviderPill === true;
6249
+ }
6250
+ /** Model (or ComfyUI workflow) a provider row will use with the persisted settings. */
6251
+ function pillModelOf(provider, value) {
6252
+ if (provider === "comfyui") {
6253
+ const workflow = activeComfyUIWorkflow(value ?? {});
6254
+ return workflow === void 0 ? "" : workflow.name;
6255
+ }
6256
+ const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "openai-compat" ? value?.openaiCompatModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : provider === "xai" ? value?.xaiModel : value?.zhipuModel;
6257
+ return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
6258
+ }
6259
+ /** One fixed-position provider menu anchored to the pill button. */
6260
+ function ImageProviderPill(props) {
6261
+ const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
6262
+ const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
6263
+ const [open, setOpen] = (0, react.useState)(false);
6264
+ const [pending, setPending] = (0, react.useState)(void 0);
6265
+ const [error, setError] = (0, react.useState)("");
6266
+ const [dots, setDots] = (0, react.useState)({
6267
+ google: "unknown",
6268
+ openai: "unknown",
6269
+ "openai-compat": "unknown",
6270
+ seedream: "unknown",
6271
+ dashscope: "unknown",
6272
+ xai: "unknown",
6273
+ zhipu: "unknown"
6274
+ });
6275
+ const [keyTick, setKeyTick] = (0, react.useState)(0);
6276
+ const buttonRef = (0, react.useRef)(null);
6277
+ const [menuStyle, setMenuStyle] = (0, react.useState)(null);
6278
+ (0, react.useEffect)(() => props.scope.subscribe(() => {
6279
+ setSnapshot(props.scope.getSnapshot());
6280
+ }), [props.scope]);
6281
+ (0, react.useEffect)(() => props.locale?.subscribe?.(() => {
6282
+ setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
6283
+ }), [props.locale]);
6284
+ (0, react.useEffect)(() => props.credentialEvents?.listen(() => {
6285
+ setKeyTick((tick) => tick + 1);
6286
+ }), [props.credentialEvents]);
6287
+ (0, react.useEffect)(() => {
6288
+ let active = true;
6289
+ const refs = [];
6290
+ for (const provider of CLOUD_IMAGE_PROVIDERS) {
6291
+ const ref = cloudCredentialRef(provider);
6292
+ if (ref !== void 0) refs.push(ref);
6293
+ }
6294
+ props.credentials.describe(refs).then((response) => {
6295
+ if (!active) return;
6296
+ const next = {};
6297
+ for (const provider of CLOUD_IMAGE_PROVIDERS) {
6298
+ const info = response.ok ? response.value?.[cloudCredentialRef(provider)] : void 0;
6299
+ next[provider] = response.ok ? info?.configured ? "configured" : "missing" : "unknown";
6300
+ }
6301
+ setDots(next);
6302
+ }).catch(() => {
6303
+ if (!active) return;
6304
+ setDots({
6305
+ google: "unknown",
6306
+ openai: "unknown",
6307
+ "openai-compat": "unknown",
6308
+ seedream: "unknown",
6309
+ dashscope: "unknown",
6310
+ xai: "unknown",
6311
+ zhipu: "unknown"
6312
+ });
6313
+ });
6314
+ return () => {
6315
+ active = false;
6316
+ };
6317
+ }, [props.credentials, keyTick]);
6318
+ const t = (keyName) => (lang === "en" ? PILL_DICT.en : PILL_DICT.zh)[keyName];
6319
+ const current = snapshot.value?.provider ?? "google";
6320
+ const writable = snapshot.writable;
6321
+ (0, react.useEffect)(() => {
6322
+ if (!open) {
6323
+ setMenuStyle(null);
6324
+ return;
6325
+ }
6326
+ const rect = buttonRef.current?.getBoundingClientRect();
6327
+ if (rect !== void 0) setMenuStyle({
6328
+ left: rect.left,
6329
+ bottom: window.innerHeight - rect.top + 6
6330
+ });
6331
+ }, [open]);
6332
+ (0, react.useEffect)(() => {
6333
+ if (!open) return;
6334
+ const onPointerDown = (event) => {
6335
+ const target = event.target;
6336
+ if (target instanceof Node && buttonRef.current !== null && !buttonRef.current.contains(target) && !(target instanceof Element && target.closest(".dsh-ig-pill-menu") !== null)) setOpen(false);
6337
+ };
6338
+ const onKeyDown = (event) => {
6339
+ if (event.key === "Escape") setOpen(false);
6340
+ };
6341
+ const onReflow = () => {
6342
+ setOpen(false);
6343
+ };
6344
+ document.addEventListener("mousedown", onPointerDown, true);
6345
+ document.addEventListener("keydown", onKeyDown, true);
6346
+ window.addEventListener("resize", onReflow);
6347
+ window.addEventListener("scroll", onReflow, true);
6348
+ return () => {
6349
+ document.removeEventListener("mousedown", onPointerDown, true);
6350
+ document.removeEventListener("keydown", onKeyDown, true);
6351
+ window.removeEventListener("resize", onReflow);
6352
+ window.removeEventListener("scroll", onReflow, true);
6353
+ };
6354
+ }, [open]);
6355
+ if (!pillVisible(snapshot.value)) return null;
6356
+ /** Same write path as the settings card: one field, one disposer, no implicit side effects. */
6357
+ const select = (provider) => {
6358
+ if (pending !== void 0 || provider === current) {
6359
+ setOpen(false);
6360
+ return;
6361
+ }
6362
+ setError("");
6363
+ setPending(provider);
6364
+ props.scope.set("provider", provider).then(() => {
6365
+ setOpen(false);
6366
+ }).catch((cause) => {
6367
+ setError(cause instanceof Error ? cause.message : String(cause));
6368
+ }).finally(() => {
6369
+ setPending(void 0);
6370
+ });
6371
+ };
6372
+ const dotClassOf = (provider) => {
6373
+ if (provider === "comfyui") return "dsh-ig-pill-dot-neutral";
6374
+ const dot = dots[provider];
6375
+ return dot === "configured" ? "dsh-ig-pill-dot-ok" : dot === "missing" ? "dsh-ig-pill-dot-missing" : "dsh-ig-pill-dot-neutral";
6376
+ };
6377
+ const subtitleOf = (provider) => {
6378
+ if (provider === "comfyui") {
6379
+ const workflow = pillModelOf(provider, snapshot.value);
6380
+ return workflow.length > 0 ? `${t("noKeyNeeded")} · ${workflow}` : `${t("noKeyNeeded")} · ${t("noWorkflow")}`;
6381
+ }
6382
+ const dot = dots[provider];
6383
+ const model = pillModelOf(provider, snapshot.value);
6384
+ if (dot === "configured") return `${t("keyConfigured")} · ${model}`;
6385
+ if (dot === "missing") return `${t("keyMissing")} · ${model}`;
6386
+ return model;
6387
+ };
6388
+ return (0, react_jsx_runtime.jsxs)("div", {
6389
+ className: "dsh-ig-pill-root",
6390
+ children: [(0, react_jsx_runtime.jsxs)("button", {
6391
+ type: "button",
6392
+ ref: buttonRef,
6393
+ className: `dsh-ig-pill-button${open ? " dsh-ig-pill-button-open" : ""}`,
6394
+ "aria-haspopup": "menu",
6395
+ "aria-expanded": open,
6396
+ title: writable ? t("menuTitle") : t("readOnly"),
6397
+ disabled: !writable,
6398
+ onClick: () => {
6399
+ setOpen((value) => !value);
6400
+ },
6401
+ children: [
6402
+ (0, react_jsx_runtime.jsxs)("svg", {
6403
+ className: "dsh-ig-pill-icon",
6404
+ width: "13",
6405
+ height: "13",
6406
+ viewBox: "0 0 16 16",
6407
+ fill: "none",
6408
+ stroke: "currentColor",
6409
+ strokeWidth: "1.6",
6410
+ strokeLinecap: "round",
6411
+ strokeLinejoin: "round",
6412
+ "aria-hidden": "true",
6413
+ children: [
6414
+ (0, react_jsx_runtime.jsx)("rect", {
6415
+ x: "1.5",
6416
+ y: "2.5",
6417
+ width: "13",
6418
+ height: "11",
6419
+ rx: "2"
6420
+ }),
6421
+ (0, react_jsx_runtime.jsx)("circle", {
6422
+ cx: "5.5",
6423
+ cy: "6.5",
6424
+ r: "1.3"
6425
+ }),
6426
+ (0, react_jsx_runtime.jsx)("path", { d: "M14.5 10.5l-3.2-3.2-6.3 6.2" })
6427
+ ]
6428
+ }),
6429
+ (0, react_jsx_runtime.jsx)("span", {
6430
+ className: "dsh-ig-pill-provider",
6431
+ children: PILL_PROVIDER_LABELS[current]
6432
+ }),
6433
+ (0, react_jsx_runtime.jsx)("span", {
6434
+ className: `dsh-ig-pill-caret${open ? " dsh-ig-pill-caret-open" : ""}`,
6435
+ "aria-hidden": "true",
6436
+ children: (0, react_jsx_runtime.jsx)("svg", {
6437
+ width: "10",
6438
+ height: "10",
6439
+ viewBox: "0 0 16 16",
6440
+ fill: "none",
6441
+ stroke: "currentColor",
6442
+ strokeWidth: "2",
6443
+ strokeLinecap: "round",
6444
+ strokeLinejoin: "round",
6445
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 10l4-4 4 4" })
6446
+ })
6447
+ })
6448
+ ]
6449
+ }), open && menuStyle !== null ? (0, react_jsx_runtime.jsxs)("div", {
6450
+ className: "dsh-ig-pill-menu",
6451
+ role: "menu",
6452
+ "aria-label": t("menuTitle"),
6453
+ style: menuStyle,
6454
+ children: [
6455
+ (0, react_jsx_runtime.jsx)("div", {
6456
+ className: "dsh-ig-pill-menu-title",
6457
+ children: t("menuTitle")
6458
+ }),
6459
+ IMAGE_PROVIDERS.map((provider) => (0, react_jsx_runtime.jsxs)("button", {
6460
+ type: "button",
6461
+ role: "menuitemradio",
6462
+ "aria-checked": provider === current,
6463
+ className: `dsh-ig-pill-option${provider === current ? " dsh-ig-pill-option-active" : ""}`,
6464
+ disabled: pending !== void 0,
6465
+ onClick: () => {
6466
+ select(provider);
6467
+ },
6468
+ children: [
6469
+ (0, react_jsx_runtime.jsx)("span", {
6470
+ className: `dsh-ig-pill-dot ${dotClassOf(provider)}`,
6471
+ "aria-hidden": "true"
6472
+ }),
6473
+ (0, react_jsx_runtime.jsxs)("span", {
6474
+ className: "dsh-ig-pill-option-text",
6475
+ children: [(0, react_jsx_runtime.jsx)("span", {
6476
+ className: "dsh-ig-pill-option-name",
6477
+ children: PILL_PROVIDER_LABELS[provider]
6478
+ }), (0, react_jsx_runtime.jsx)("span", {
6479
+ className: "dsh-ig-pill-option-sub",
6480
+ children: subtitleOf(provider)
6481
+ })]
6482
+ }),
6483
+ provider === current ? (0, react_jsx_runtime.jsx)("span", {
6484
+ className: "dsh-ig-pill-check",
6485
+ "aria-hidden": "true",
6486
+ children: (0, react_jsx_runtime.jsx)("svg", {
6487
+ width: "13",
6488
+ height: "13",
6489
+ viewBox: "0 0 16 16",
6490
+ fill: "none",
6491
+ stroke: "currentColor",
6492
+ strokeWidth: "2.2",
6493
+ strokeLinecap: "round",
6494
+ strokeLinejoin: "round",
6495
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
6496
+ })
6497
+ }) : null
6498
+ ]
6499
+ }, provider)),
6500
+ error.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
6501
+ className: "dsh-ig-pill-error",
6502
+ role: "alert",
6503
+ children: [
6504
+ t("switchFailed"),
6505
+ ":",
6506
+ error
6507
+ ]
6508
+ }) : null
6509
+ ]
6510
+ }) : null]
6511
+ });
6512
+ }
6513
+ /** Styles for the pill and its fixed-position menu; injected by the client entry. */
6514
+ const PROVIDER_PILL_STYLE = `
6515
+ .dsh-ig-pill-root{position:relative;display:inline-flex;align-items:center;height:26px}
6516
+ .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}
6517
+ .dsh-ig-pill-button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#9ca3af);color:var(--dsw-alias-label-primary,#111827)}
6518
+ .dsh-ig-pill-button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:1px}
6519
+ .dsh-ig-pill-button:disabled{opacity:.5;cursor:not-allowed}
6520
+ .dsh-ig-pill-button-open{border-color:var(--dsw-alias-brand-primary,#4c78ff);color:var(--dsw-alias-label-primary,#111827)}
6521
+ .dsh-ig-pill-icon{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
6522
+ .dsh-ig-pill-provider{max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6523
+ .dsh-ig-pill-caret{display:inline-flex;flex:none;transition:transform .15s}
6524
+ .dsh-ig-pill-caret-open{transform:rotate(180deg)}
6525
+ .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}
6526
+ .dsh-ig-pill-menu-title{padding:6px 8px 4px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary,#6b7280)}
6527
+ .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}
6528
+ .dsh-ig-pill-option:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#f3f4f6)}
6529
+ .dsh-ig-pill-option:disabled{opacity:.6;cursor:default}
6530
+ .dsh-ig-pill-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}
6531
+ .dsh-ig-pill-option-active{background:var(--dsw-alias-brand-primary-soft,rgba(76,120,255,.10))}
6532
+ .dsh-ig-pill-dot{flex:none;width:7px;height:7px;border-radius:50%}
6533
+ .dsh-ig-pill-dot-ok{background:#22c55e}
6534
+ .dsh-ig-pill-dot-missing{background:var(--dsw-alias-border-l2,#d1d5db)}
6535
+ .dsh-ig-pill-dot-neutral{background:var(--dsw-alias-brand-primary,#4c78ff)}
6536
+ .dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
6537
+ .dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}
6538
+ .dsh-ig-pill-option-sub{font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6539
+ .dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
6540
+ .dsh-ig-pill-error{margin-top:4px;padding:6px 8px;border-radius:8px;background:rgba(239,68,68,.08);color:#ef4444;font-size:11.5px;line-height:1.4;word-break:break-all}
6541
+ `;
6542
+ //#endregion
6543
+ //#region lib/types/client/index.js
6155
6544
  const DICT = {
6156
6545
  zh: {
6157
6546
  title: "图像生成",
6158
- description: "选择厂商并配置生图模型。",
6159
- provider: "Provider",
6547
+ description: "配置各 Provider 的 Key 与模型,并选择默认 Provider。",
6548
+ defaultProvider: "默认 Provider",
6549
+ defaultProviderHint: "Agent 生图默认使用;Studio 与工具调用可临时指定其他 Provider。",
6550
+ settingsReadOnly: "设置由配置文件提供,只读;如需修改请编辑对应的配置来源。",
6160
6551
  providerGoogle: "Google Gemini",
6161
- providerOpenAI: "OpenAI / 中转站",
6552
+ providerOpenAI: "OpenAI",
6553
+ providerOpenAICompat: "OpenAI 兼容(中转站)",
6162
6554
  providerSeedream: "字节 Seedream",
6163
6555
  providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
6556
+ providerXAI: "xAI Grok Imagine",
6557
+ providerZhipu: "智谱 GLM-Image",
6164
6558
  providerComfyUI: "本地 ComfyUI",
6165
6559
  apiKeyLabel: "{provider} API Key",
6166
6560
  apiKeyPlaceholder: "留空即可保留已配置的 Key",
6167
6561
  apiKeyHint: "安全保存为 {key};页面不会读回明文。",
6562
+ keyReadOnly: "Key 由 {source} 提供且只读;请在该来源中修改。",
6563
+ badgeChecking: "检查中…",
6564
+ badgeConfigured: "Key 已配置",
6565
+ badgeMissing: "Key 未配置",
6566
+ badgeUnknown: "状态未知",
6567
+ comfyuiNoKey: "无需 API Key",
6568
+ testConnection: "测试连接",
6569
+ testing: "正在测试…",
6570
+ testOk: "连接成功",
6571
+ testFailed: "连接失败",
6572
+ testUnauthorized: "Key 无效或无权限",
6573
+ clearKey: "清除 Key",
6574
+ keyCleared: "已清除 Key",
6575
+ clearKeyFailed: "清除 Key 失败",
6576
+ saveKeyFailed: "保存 Key 失败",
6577
+ clearKeyUnsupported: "当前版本 DSH 不支持在此清除 Key,请到凭据管理中删除。",
6168
6578
  endpoint: "接口地址",
6169
6579
  reset: "重置",
6170
6580
  resetTitle: "重置为默认官方地址",
6171
6581
  endpointHintGoogle: "Google 官方地址或反代端点(全路径)。",
6172
- endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
6582
+ endpointHintOpenAI: "官方 api.openai.com 的 /v1 地址;中转站请使用下方「OpenAI 兼容」行。",
6583
+ endpointHintOpenAICompat: "中转站/自建服务的 OpenAI 兼容 /v1 地址(必填),例如 https://your-relay.example.com/v1。",
6173
6584
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
6174
6585
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
6586
+ endpointHintXAI: "xAI 官方 api.x.ai 的 /v1 地址。",
6587
+ endpointHintZhipu: "智谱开放平台 open.bigmodel.cn 的 /api/paas/v4 地址。",
6175
6588
  endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
6176
6589
  model: "模型",
6177
6590
  workflow: "API Workflow 工作流",
@@ -6188,17 +6601,23 @@ window.__ModuleLoader__.load({
6188
6601
  workflowDuplicateName: "工作流名称不能重复。",
6189
6602
  timeout: "生成超时(秒)",
6190
6603
  timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
6604
+ workspaceSection: "工作区",
6191
6605
  saveToWorkspace: "保存到工作区",
6192
6606
  saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
6193
6607
  folder: "工作区文件夹",
6194
6608
  folderHint: "相对当前会话工作区的子目录;留空表示工作区根目录。",
6609
+ uiSection: "界面",
6610
+ showPill: "在对话输入栏显示生图切换胶囊",
6611
+ showPillHint: "开启后,输入框工具行会显示一枚胶囊,随手切换默认生图 Provider,无需进入设置;默认关闭。",
6612
+ fetchModels: "拉取模型",
6613
+ fetchingModels: "拉取中…",
6614
+ fetchModelsHint: "点击「拉取模型」用当前 Key 获取可用生图模型列表;也可手动输入。",
6615
+ modelsFound: "找到 {n} 个生图模型,点击模型框选择",
6616
+ modelsNone: "未筛出生图模型,可手动输入模型名",
6195
6617
  saving: "保存中…",
6196
6618
  save: "保存",
6197
6619
  saved: "已保存",
6198
6620
  savedToPath: "已保存到",
6199
- checkingKey: "正在检查 API Key…",
6200
- keyConfigured: "已配置 API Key",
6201
- keyNotConfigured: "尚未配置 API Key",
6202
6621
  generating: "正在生成图片…",
6203
6622
  loading: "正在加载图片…",
6204
6623
  loadFailed: "图片读取失败 ({status})",
@@ -6223,23 +6642,47 @@ window.__ModuleLoader__.load({
6223
6642
  },
6224
6643
  en: {
6225
6644
  title: "Image Generation",
6226
- description: "Select provider and configure image generation models.",
6227
- provider: "Provider",
6645
+ description: "Configure each provider key and model, then pick the default provider.",
6646
+ defaultProvider: "Default provider",
6647
+ defaultProviderHint: "Used by the Agent by default; the Studio and tool calls can switch per call.",
6648
+ settingsReadOnly: "Settings come from a profile file and are read-only; edit that source to change them.",
6228
6649
  providerGoogle: "Google Gemini",
6229
- providerOpenAI: "OpenAI / Relay",
6650
+ providerOpenAI: "OpenAI",
6651
+ providerOpenAICompat: "OpenAI-compatible (relay)",
6230
6652
  providerSeedream: "ByteDance Seedream",
6231
6653
  providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
6654
+ providerXAI: "xAI Grok Imagine",
6655
+ providerZhipu: "Zhipu GLM-Image",
6232
6656
  providerComfyUI: "Local ComfyUI",
6233
6657
  apiKeyLabel: "{provider} API Key",
6234
6658
  apiKeyPlaceholder: "Leave empty to keep configured key",
6235
6659
  apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
6660
+ keyReadOnly: "Key is supplied read-only by {source}; update it there.",
6661
+ badgeChecking: "Checking…",
6662
+ badgeConfigured: "Key set",
6663
+ badgeMissing: "Key missing",
6664
+ badgeUnknown: "Unknown",
6665
+ comfyuiNoKey: "No API key needed",
6666
+ testConnection: "Test connection",
6667
+ testing: "Testing…",
6668
+ testOk: "Connection OK",
6669
+ testFailed: "Connection failed",
6670
+ testUnauthorized: "API key rejected",
6671
+ clearKey: "Clear key",
6672
+ keyCleared: "Key cleared",
6673
+ clearKeyFailed: "Failed to clear key",
6674
+ saveKeyFailed: "Failed to save the key",
6675
+ clearKeyUnsupported: "This DSH build cannot clear keys here; remove it from credential management instead.",
6236
6676
  endpoint: "Endpoint / Base URL",
6237
6677
  reset: "Reset",
6238
6678
  resetTitle: "Reset to official default URL",
6239
6679
  endpointHintGoogle: "Official Google endpoint or reverse proxy (full path).",
6240
- endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
6680
+ endpointHintOpenAI: "Official api.openai.com /v1 base URL; for relays use the \"OpenAI-compatible\" row below.",
6681
+ endpointHintOpenAICompat: "OpenAI-compatible /v1 base URL of your relay or self-hosted service (required), e.g. https://your-relay.example.com/v1.",
6241
6682
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
6242
6683
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
6684
+ endpointHintXAI: "Official xAI api.x.ai /v1 base URL.",
6685
+ endpointHintZhipu: "Zhipu open.bigmodel.cn /api/paas/v4 base URL.",
6243
6686
  endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
6244
6687
  model: "Model",
6245
6688
  workflow: "API Workflows",
@@ -6256,17 +6699,23 @@ window.__ModuleLoader__.load({
6256
6699
  workflowDuplicateName: "Workflow names must be unique.",
6257
6700
  timeout: "Generation timeout (seconds)",
6258
6701
  timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
6702
+ workspaceSection: "Workspace",
6259
6703
  saveToWorkspace: "Save to workspace",
6260
6704
  saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
6261
6705
  folder: "Workspace folder",
6262
6706
  folderHint: "Subdirectory of the session workspace; empty means the workspace root.",
6707
+ uiSection: "Interface",
6708
+ showPill: "Show the image provider pill in the chat input bar",
6709
+ showPillHint: "Adds a small pill to the composer tool row for switching the default image provider without opening settings; off by default.",
6710
+ fetchModels: "Fetch models",
6711
+ fetchingModels: "Fetching…",
6712
+ fetchModelsHint: "Click \"Fetch models\" to list image-capable models with the stored key; manual input still works.",
6713
+ modelsFound: "{n} image models found; open the model field to pick one",
6714
+ modelsNone: "No image models found; type the model name manually",
6263
6715
  saving: "Saving…",
6264
6716
  save: "Save",
6265
6717
  saved: "Saved",
6266
6718
  savedToPath: "Saved to",
6267
- checkingKey: "Checking API Key…",
6268
- keyConfigured: "API Key configured",
6269
- keyNotConfigured: "API Key not configured",
6270
6719
  generating: "Generating image…",
6271
6720
  loading: "Loading image…",
6272
6721
  loadFailed: "Failed to load image ({status})",
@@ -6322,6 +6771,7 @@ window.__ModuleLoader__.load({
6322
6771
  .dsh-ig-btn-reset{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
6323
6772
  .dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
6324
6773
  .dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
6774
+ .dsh-ig-hint-error{color:var(--dsw-alias-label-error,#d33)}
6325
6775
  .dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}
6326
6776
  .dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
6327
6777
  .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
@@ -6330,6 +6780,45 @@ window.__ModuleLoader__.load({
6330
6780
  .dsh-ig-save{appearance:none;border:0;border-radius:8px;padding:6px 16px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
6331
6781
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
6332
6782
 
6783
+ /* Provider list: one expandable row per provider, each saving independently. */
6784
+ .dsh-ig-providers{display:grid;gap:10px;margin-top:14px}
6785
+ .dsh-ig-provider-row{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;background:var(--dsw-alias-bg-layer-3,transparent);overflow:hidden;transition:border-color .16s}
6786
+ .dsh-ig-provider-row-open{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
6787
+ .dsh-ig-provider-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:11px 12px}
6788
+ .dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}
6789
+ .dsh-ig-provider-name{flex:1;min-width:0;font-size:13.5px;font-weight:550;color:var(--dsw-alias-label-primary,inherit)}
6790
+ .dsh-ig-provider-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s;display:inline-flex;align-items:center}
6791
+ .dsh-ig-provider-chevron-open{transform:rotate(180deg)}
6792
+ .dsh-ig-provider-body{border-top:1px solid var(--dsw-alias-border-l2,#eee);padding:2px 12px 14px}
6793
+ .dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;font-size:11.5px;line-height:1.6;padding:2px 9px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}
6794
+ .dsh-ig-badge-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
6795
+ .dsh-ig-badge-ok{border-color:rgba(34,197,94,.45);color:#15803d;background:rgba(34,197,94,.08)}
6796
+ .dsh-ig-badge-missing{border-color:rgba(239,68,68,.4);color:#b91c1c;background:rgba(239,68,68,.06)}
6797
+ .dsh-ig-badge-neutral{color:var(--dsw-alias-label-tertiary,#7b818b)}
6798
+ .dsh-ig-badge-checking .dsh-ig-badge-dot{animation:dsh-ig-pulse 1s ease-in-out infinite}
6799
+ @keyframes dsh-ig-pulse{50%{opacity:.25}}
6800
+
6801
+ /* Default provider radio pills. */
6802
+ .dsh-ig-radios{display:flex;flex-wrap:wrap;gap:8px}
6803
+ .dsh-ig-radio{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:999px;padding:5px 12px;cursor:pointer;font-size:12.5px;color:var(--dsw-alias-label-secondary,inherit);transition:border-color .15s,background .15s}
6804
+ .dsh-ig-radio:hover{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
6805
+ .dsh-ig-radio-checked{border-color:var(--dsw-alias-brand-primary,#4c78ff);background:rgba(76,120,255,.08);color:var(--dsw-alias-label-primary,inherit)}
6806
+ .dsh-ig-radio input[type=radio]{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}
6807
+
6808
+ /* Row-level actions: test connection, clear key, save. */
6809
+ .dsh-ig-row-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}
6810
+ .dsh-ig-row-buttons{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
6811
+ .dsh-ig-btn-secondary{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:6px 14px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s,opacity .15s}
6812
+ .dsh-ig-btn-secondary:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
6813
+ .dsh-ig-btn-secondary:disabled{opacity:.45;cursor:default}
6814
+ .dsh-ig-btn-danger{color:#b91c1c;border-color:rgba(239,68,68,.4)}
6815
+ .dsh-ig-btn-danger:hover:not(:disabled){background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.6)}
6816
+
6817
+ /* Workspace section within the settings card. */
6818
+ .dsh-ig-section{display:grid;gap:6px;margin-top:16px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
6819
+ .dsh-ig-section-title{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary,#7b818b);text-transform:uppercase}
6820
+ .dsh-ig-status-readonly{color:var(--dsw-alias-label-tertiary,#7b818b);font-style:italic}
6821
+
6333
6822
  .dsh-ig-result{display:grid;gap:10px;max-width:520px}
6334
6823
  .dsh-ig-promoted-results{display:grid;gap:16px}
6335
6824
  .dsh-ig-result-title{font-size:14px;font-weight:600}
@@ -6551,7 +7040,7 @@ window.__ModuleLoader__.load({
6551
7040
  ctx.effect(() => {
6552
7041
  const style = document.createElement("style");
6553
7042
  style.dataset.plugin = "dsh-image-gen";
6554
- style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}`;
7043
+ style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}\n${PROVIDER_PILL_STYLE}`;
6555
7044
  document.head.appendChild(style);
6556
7045
  return () => {
6557
7046
  style.remove();
@@ -6570,6 +7059,21 @@ window.__ModuleLoader__.load({
6570
7059
  inject: () => ({ locale })
6571
7060
  }, PromotedImageResultNode));
6572
7061
  });
7062
+ const credentialListeners = /* @__PURE__ */ new Set();
7063
+ const notifyCredentialsUpdated = () => {
7064
+ for (const listener of credentialListeners) listener();
7065
+ };
7066
+ ctx.effect(() => {
7067
+ const remote = ctx.get("remote");
7068
+ if (typeof remote?.$on !== "function") return () => {};
7069
+ return remote.$on("credentials/reference-updated", notifyCredentialsUpdated);
7070
+ }, "dsh-image-gen: credential events");
7071
+ const credentialEvents = { listen(callback) {
7072
+ credentialListeners.add(callback);
7073
+ return () => {
7074
+ credentialListeners.delete(callback);
7075
+ };
7076
+ } };
6573
7077
  const injectSettingsItem = (owner, credentials) => {
6574
7078
  const ownerRegister = owner.slots.register.bind(owner.slots);
6575
7079
  owner.slots.inject("settings.plugin.item", () => ownerRegister({
@@ -6578,18 +7082,38 @@ window.__ModuleLoader__.load({
6578
7082
  inject: () => ({
6579
7083
  scope,
6580
7084
  credentials,
6581
- locale
7085
+ locale,
7086
+ credentialEvents
6582
7087
  })
6583
7088
  }, ImageGenerationSettingsCard));
6584
7089
  };
7090
+ const injectComposerPill = (owner, credentials) => {
7091
+ const ownerRegister = owner.slots.register.bind(owner.slots);
7092
+ owner.slots.inject("conversation.input.right", () => ownerRegister({
7093
+ name: "conversation.input.right",
7094
+ id: "image-provider",
7095
+ order: 10,
7096
+ inject: () => ({
7097
+ scope,
7098
+ credentials,
7099
+ locale,
7100
+ credentialEvents
7101
+ })
7102
+ }, ImageProviderPill));
7103
+ };
6585
7104
  const remoteCredentials = asCredentialsRemote(ctx.get("remote.credentials"));
6586
7105
  const legacyCredentials = credentialsFromLegacyConnection(ctx.get("connection"));
6587
- if (remoteCredentials !== void 0) injectSettingsItem(ctx, remoteCredentials);
6588
- else if (legacyCredentials !== void 0) injectSettingsItem(ctx, legacyCredentials);
6589
- else ctx.inject(["remote.credentials"], (remoteCtx) => {
7106
+ if (remoteCredentials !== void 0) {
7107
+ injectSettingsItem(ctx, remoteCredentials);
7108
+ injectComposerPill(ctx, remoteCredentials);
7109
+ } else if (legacyCredentials !== void 0) {
7110
+ injectSettingsItem(ctx, legacyCredentials);
7111
+ injectComposerPill(ctx, legacyCredentials);
7112
+ } else ctx.inject(["remote.credentials"], (remoteCtx) => {
6590
7113
  const credentials = asCredentialsRemote(remoteCtx.get("remote.credentials"));
6591
7114
  if (credentials === void 0) throw new Error("dsh-image-gen: remote.credentials has an incompatible interface");
6592
7115
  injectSettingsItem(remoteCtx, credentials);
7116
+ injectComposerPill(remoteCtx, credentials);
6593
7117
  });
6594
7118
  ctx.slots.inject("tool.call.toolview", () => register({
6595
7119
  name: "tool.call.toolview",
@@ -6653,32 +7177,102 @@ window.__ModuleLoader__.load({
6653
7177
  }
6654
7178
  };
6655
7179
  }
6656
- /** Edit provider settings and its write-only API credential. */
7180
+ function emptyProviderRow() {
7181
+ return {
7182
+ expanded: false,
7183
+ model: "",
7184
+ baseURL: "",
7185
+ keyInput: "",
7186
+ keyStatus: "checking",
7187
+ keyInfo: void 0,
7188
+ testing: false,
7189
+ testResult: void 0,
7190
+ fetchingModels: false,
7191
+ modelOptions: [],
7192
+ modelFetchMessage: "",
7193
+ modelFetchIsError: false,
7194
+ saving: false,
7195
+ message: "",
7196
+ messageIsError: false,
7197
+ workflows: [],
7198
+ activeWorkflow: "",
7199
+ timeoutSeconds: DEFAULT_COMFYUI_TIMEOUT_MS / 1e3
7200
+ };
7201
+ }
7202
+ /** Settings field each cloud provider persists its model under. */
7203
+ const CLOUD_MODEL_FIELDS = {
7204
+ google: "googleModel",
7205
+ openai: "openaiModel",
7206
+ "openai-compat": "openaiCompatModel",
7207
+ seedream: "seedreamModel",
7208
+ dashscope: "dashscopeModel",
7209
+ xai: "xaiModel",
7210
+ zhipu: "zhipuModel"
7211
+ };
7212
+ /** Settings field each cloud provider persists its endpoint or base URL under. */
7213
+ const CLOUD_URL_FIELDS = {
7214
+ google: "googleEndpoint",
7215
+ openai: "openaiBaseURL",
7216
+ "openai-compat": "openaiCompatBaseURL",
7217
+ seedream: "seedreamBaseURL",
7218
+ dashscope: "dashscopeEndpoint",
7219
+ xai: "xaiBaseURL",
7220
+ zhipu: "zhipuBaseURL"
7221
+ };
7222
+ /** Dictionary key of each cloud provider's endpoint hint. */
7223
+ const CLOUD_HINT_KEYS = {
7224
+ google: "endpointHintGoogle",
7225
+ openai: "endpointHintOpenAI",
7226
+ "openai-compat": "endpointHintOpenAICompat",
7227
+ seedream: "endpointHintSeedream",
7228
+ dashscope: "endpointHintDashScope",
7229
+ xai: "endpointHintXAI",
7230
+ zhipu: "endpointHintZhipu"
7231
+ };
7232
+ /** Config field a cloud provider persists its model under. */
7233
+ function modelFieldOf(provider) {
7234
+ return CLOUD_MODEL_FIELDS[provider];
7235
+ }
7236
+ /** Config field a cloud provider persists its endpoint or base URL under. */
7237
+ function baseURLFieldOf(provider) {
7238
+ return CLOUD_URL_FIELDS[provider];
7239
+ }
7240
+ /** Providers whose settings row offers the "pull models" button. */
7241
+ function modelPullSupported(_provider) {
7242
+ return true;
7243
+ }
7244
+ /** Build one row per provider from persisted settings, including ComfyUI extras. */
7245
+ function rowsFromSettings(value) {
7246
+ const rows = {};
7247
+ for (const provider of IMAGE_PROVIDERS) rows[provider] = {
7248
+ ...emptyProviderRow(),
7249
+ model: modelOf(provider, value),
7250
+ baseURL: baseURLOf(provider, value),
7251
+ ...provider === "comfyui" ? {
7252
+ workflows: resolveComfyUIWorkflows(value ?? {}),
7253
+ activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
7254
+ timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
7255
+ } : {}
7256
+ };
7257
+ return rows;
7258
+ }
7259
+ /** Edit each provider independently, pick an explicit default, and verify keys inline. */
6657
7260
  function ImageGenerationSettingsCard(props) {
6658
7261
  const [open, setOpen] = (0, react.useState)(false);
6659
7262
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
6660
7263
  const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
6661
- const [provider, setProvider] = (0, react.useState)("google");
6662
- const [model, setModel] = (0, react.useState)("");
6663
- const [baseURL, setBaseURL] = (0, react.useState)("");
6664
- const [workflows, setWorkflows] = (0, react.useState)([]);
6665
- const [activeWorkflow, setActiveWorkflow] = (0, react.useState)("");
6666
- const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
6667
- const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
6668
- const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
6669
- const [key, setKey] = (0, react.useState)("");
6670
- const [configured, setConfigured] = (0, react.useState)();
6671
- const [saving, setSaving] = (0, react.useState)(false);
6672
- const [message, setMessage] = (0, react.useState)("");
6673
- const [messageIsError, setMessageIsError] = (0, react.useState)(false);
6674
- const reportMessage = (text) => {
6675
- setMessage(text);
6676
- setMessageIsError(false);
6677
- };
6678
- const reportError = (text) => {
6679
- setMessage(text);
6680
- setMessageIsError(true);
6681
- };
7264
+ const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
7265
+ const [providerMessage, setProviderMessage] = (0, react.useState)("");
7266
+ const [providerMessageIsError, setProviderMessageIsError] = (0, react.useState)(false);
7267
+ const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(() => props.scope.getSnapshot().value?.saveToWorkspace ?? true);
7268
+ const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)(() => props.scope.getSnapshot().value?.workspaceFolder ?? "dsh-image-gen");
7269
+ const [showPill, setShowPill] = (0, react.useState)(() => props.scope.getSnapshot().value?.showProviderPill === true);
7270
+ const [uiMessage, setUiMessage] = (0, react.useState)("");
7271
+ const [uiMessageIsError, setUiMessageIsError] = (0, react.useState)(false);
7272
+ const [workspaceMessage, setWorkspaceMessage] = (0, react.useState)("");
7273
+ const [workspaceMessageIsError, setWorkspaceMessageIsError] = (0, react.useState)(false);
7274
+ const [rows, setRows] = (0, react.useState)(() => rowsFromSettings(props.scope.getSnapshot().value));
7275
+ const [keyTick, setKeyTick] = (0, react.useState)(0);
6682
7276
  (0, react.useEffect)(() => props.scope.subscribe(() => {
6683
7277
  setSnapshot(props.scope.getSnapshot());
6684
7278
  }), [props.scope]);
@@ -6687,6 +7281,10 @@ window.__ModuleLoader__.load({
6687
7281
  setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
6688
7282
  });
6689
7283
  }, [props.locale]);
7284
+ const credentialEvents = props.credentialEvents;
7285
+ (0, react.useEffect)(() => credentialEvents?.listen(() => {
7286
+ setKeyTick((tick) => tick + 1);
7287
+ }), [credentialEvents]);
6690
7288
  const t = (keyName, params) => {
6691
7289
  let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
6692
7290
  if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
@@ -6695,46 +7293,103 @@ window.__ModuleLoader__.load({
6695
7293
  const providerLabels = {
6696
7294
  google: t("providerGoogle"),
6697
7295
  openai: t("providerOpenAI"),
7296
+ "openai-compat": t("providerOpenAICompat"),
6698
7297
  seedream: t("providerSeedream"),
6699
7298
  dashscope: t("providerDashScope"),
7299
+ xai: t("providerXAI"),
7300
+ zhipu: t("providerZhipu"),
6700
7301
  comfyui: t("providerComfyUI")
6701
7302
  };
6702
7303
  (0, react.useEffect)(() => {
6703
7304
  const value = snapshot.value;
6704
- const next = value?.provider ?? "google";
6705
- setProvider(next);
6706
- setModel(modelOf(next, value));
6707
- setBaseURL(baseURLOf(next, value));
6708
- setWorkflows(resolveComfyUIWorkflows(value ?? {}));
6709
- setActiveWorkflow(activeComfyUIWorkflow(value ?? {})?.name ?? "");
6710
- setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
7305
+ setDefaultProvider(value?.provider ?? "google");
6711
7306
  setSaveToWorkspace(value?.saveToWorkspace ?? true);
6712
7307
  setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
7308
+ setShowPill(value?.showProviderPill === true);
7309
+ setRows((current) => {
7310
+ const next = {};
7311
+ for (const provider of IMAGE_PROVIDERS) next[provider] = {
7312
+ ...current[provider],
7313
+ model: modelOf(provider, value),
7314
+ baseURL: baseURLOf(provider, value),
7315
+ ...provider === "comfyui" ? {
7316
+ workflows: resolveComfyUIWorkflows(value ?? {}),
7317
+ activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
7318
+ timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
7319
+ } : {}
7320
+ };
7321
+ return next;
7322
+ });
6713
7323
  }, [snapshot]);
7324
+ const credentials = props.credentials;
6714
7325
  (0, react.useEffect)(() => {
6715
- const keyRef = KEY_REF[provider];
6716
- if (keyRef === void 0) {
6717
- setConfigured(void 0);
6718
- return;
6719
- }
6720
7326
  let active = true;
6721
- props.credentials.describe([keyRef]).then((response) => {
6722
- if (active) setConfigured(response.ok ? response.value?.[keyRef]?.configured ?? false : void 0);
6723
- }).catch(() => {
6724
- if (active) setConfigured(void 0);
6725
- });
7327
+ for (const provider of CLOUD_IMAGE_PROVIDERS) {
7328
+ const keyRef = cloudCredentialRef(provider);
7329
+ if (keyRef === void 0) continue;
7330
+ setRows((current) => ({
7331
+ ...current,
7332
+ [provider]: {
7333
+ ...current[provider],
7334
+ keyStatus: "checking"
7335
+ }
7336
+ }));
7337
+ credentials.describe([keyRef]).then((response) => {
7338
+ if (!active) return;
7339
+ const info = response.ok ? response.value?.[keyRef] : void 0;
7340
+ setRows((current) => ({
7341
+ ...current,
7342
+ [provider]: {
7343
+ ...current[provider],
7344
+ keyStatus: response.ok ? info?.configured ? "configured" : "missing" : "unknown",
7345
+ keyInfo: info
7346
+ }
7347
+ }));
7348
+ }).catch(() => {
7349
+ if (!active) return;
7350
+ setRows((current) => ({
7351
+ ...current,
7352
+ [provider]: {
7353
+ ...current[provider],
7354
+ keyStatus: "unknown"
7355
+ }
7356
+ }));
7357
+ });
7358
+ }
6726
7359
  return () => {
6727
7360
  active = false;
6728
7361
  };
6729
- }, [props.credentials, provider]);
6730
- const save = async (event) => {
6731
- event.preventDefault();
6732
- setSaving(true);
6733
- setMessage("");
7362
+ }, [credentials, keyTick]);
7363
+ const updateRow = (provider, patch) => {
7364
+ setRows((current) => ({
7365
+ ...current,
7366
+ [provider]: {
7367
+ ...current[provider],
7368
+ ...patch
7369
+ }
7370
+ }));
7371
+ };
7372
+ /** The only action that ever changes the default provider; saving a key never does. */
7373
+ const selectDefaultProvider = (provider) => {
7374
+ setDefaultProvider(provider);
7375
+ setProviderMessage("");
7376
+ setProviderMessageIsError(false);
7377
+ props.scope.set("provider", provider).catch((cause) => {
7378
+ setDefaultProvider(snapshot.value?.provider ?? "google");
7379
+ setProviderMessage(cause instanceof Error ? cause.message : String(cause));
7380
+ setProviderMessageIsError(true);
7381
+ });
7382
+ };
7383
+ const saveProviderRow = async (provider) => {
7384
+ const row = rows[provider];
7385
+ updateRow(provider, {
7386
+ saving: true,
7387
+ message: "",
7388
+ messageIsError: false
7389
+ });
6734
7390
  try {
6735
- await props.scope.set("provider", provider);
6736
7391
  if (provider === "comfyui") {
6737
- const entries = workflows.map((entry) => ({
7392
+ const entries = row.workflows.map((entry) => ({
6738
7393
  name: entry.name.trim(),
6739
7394
  json: entry.json,
6740
7395
  presetPrompt: (entry.presetPrompt ?? "").trim()
@@ -6744,98 +7399,702 @@ window.__ModuleLoader__.load({
6744
7399
  validateComfyUIWorkflowJson(entry.json);
6745
7400
  }
6746
7401
  if (new Set(entries.map((entry) => entry.name)).size !== entries.length) throw new Error(t("workflowDuplicateName"));
6747
- const activeEntry = entries.find((entry) => entry.name === activeWorkflow) ?? entries[0];
6748
- await props.scope.set("comfyuiBaseURL", baseURL);
7402
+ const activeEntry = entries.find((entry) => entry.name === row.activeWorkflow) ?? entries[0];
7403
+ await props.scope.set("comfyuiBaseURL", row.baseURL);
6749
7404
  await props.scope.set("comfyuiWorkflows", entries);
6750
7405
  await props.scope.set("comfyuiActiveWorkflow", activeEntry === void 0 ? "" : activeEntry.name);
6751
7406
  await props.scope.set("comfyuiWorkflowJson", activeEntry === void 0 ? "" : activeEntry.json);
6752
7407
  await props.scope.set("comfyuiWorkflowName", activeEntry === void 0 ? "" : activeEntry.name);
6753
- await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
7408
+ await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(row.timeoutSeconds)) * 1e3);
6754
7409
  } else {
6755
- await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
6756
- await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
6757
- }
6758
- await props.scope.set("saveToWorkspace", saveToWorkspace);
6759
- await props.scope.set("workspaceFolder", workspaceFolder.trim());
6760
- if (key.trim().length > 0) {
6761
- const keyRef = KEY_REF[provider];
6762
- if (keyRef === void 0) throw new Error("ComfyUI does not use an API key in this version");
6763
- const response = await props.credentials.set(keyRef, key.trim());
6764
- if (!response.ok) throw new Error(response.error?.message ?? "Failed to save API key");
6765
- setKey("");
6766
- setConfigured(true);
7410
+ await props.scope.set(modelFieldOf(provider), row.model);
7411
+ await props.scope.set(baseURLFieldOf(provider), row.baseURL);
7412
+ if (row.keyInput.trim().length > 0) {
7413
+ const keyRef = cloudCredentialRef(provider);
7414
+ if (keyRef === void 0) throw new Error(t("comfyuiNoKey"));
7415
+ const response = await props.credentials.set(keyRef, row.keyInput.trim());
7416
+ if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
7417
+ updateRow(provider, {
7418
+ keyInput: "",
7419
+ keyStatus: "configured"
7420
+ });
7421
+ }
6767
7422
  }
6768
- reportMessage(t("saved"));
7423
+ updateRow(provider, {
7424
+ message: t("saved"),
7425
+ messageIsError: false
7426
+ });
7427
+ } catch (cause) {
7428
+ updateRow(provider, {
7429
+ message: cause instanceof Error ? cause.message : String(cause),
7430
+ messageIsError: true
7431
+ });
7432
+ } finally {
7433
+ updateRow(provider, { saving: false });
7434
+ }
7435
+ };
7436
+ /** Probe through the host route so the browser side never touches credential values. */
7437
+ const testConnection = async (provider) => {
7438
+ updateRow(provider, {
7439
+ testing: true,
7440
+ testResult: void 0,
7441
+ message: "",
7442
+ messageIsError: false
7443
+ });
7444
+ try {
7445
+ const response = await fetch(TEST_CONNECTION_ROUTE, {
7446
+ method: "POST",
7447
+ headers: { "content-type": "application/json" },
7448
+ body: JSON.stringify({ provider })
7449
+ });
7450
+ if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
7451
+ updateRow(provider, { testResult: await response.json() });
7452
+ } catch (cause) {
7453
+ updateRow(provider, { testResult: {
7454
+ ok: false,
7455
+ reason: "error",
7456
+ message: cause instanceof Error ? cause.message : String(cause)
7457
+ } });
7458
+ } finally {
7459
+ updateRow(provider, { testing: false });
7460
+ }
7461
+ };
7462
+ /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
7463
+ const fetchProviderModels = async (provider) => {
7464
+ updateRow(provider, {
7465
+ fetchingModels: true,
7466
+ modelFetchMessage: "",
7467
+ modelFetchIsError: false
7468
+ });
7469
+ try {
7470
+ const response = await fetch(TEST_CONNECTION_ROUTE, {
7471
+ method: "POST",
7472
+ headers: { "content-type": "application/json" },
7473
+ body: JSON.stringify({
7474
+ provider,
7475
+ action: "models"
7476
+ })
7477
+ });
7478
+ if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
7479
+ const payload = await response.json();
7480
+ if (payload.ok && Array.isArray(payload.models)) {
7481
+ const models = payload.models.filter((id) => typeof id === "string");
7482
+ updateRow(provider, {
7483
+ modelOptions: models,
7484
+ modelFetchMessage: models.length > 0 ? t("modelsFound", { n: String(models.length) }) : t("modelsNone"),
7485
+ modelFetchIsError: models.length === 0
7486
+ });
7487
+ } else if (payload.reason === "missing-key") updateRow(provider, {
7488
+ modelFetchMessage: t("badgeMissing"),
7489
+ modelFetchIsError: true
7490
+ });
7491
+ else if (payload.reason === "unauthorized") updateRow(provider, {
7492
+ modelFetchMessage: t("testUnauthorized"),
7493
+ modelFetchIsError: true
7494
+ });
7495
+ else updateRow(provider, {
7496
+ modelFetchMessage: payload.message ?? t("testFailed"),
7497
+ modelFetchIsError: true
7498
+ });
7499
+ } catch (cause) {
7500
+ updateRow(provider, {
7501
+ modelFetchMessage: cause instanceof Error ? cause.message : String(cause),
7502
+ modelFetchIsError: true
7503
+ });
7504
+ } finally {
7505
+ updateRow(provider, { fetchingModels: false });
7506
+ }
7507
+ };
7508
+ const clearProviderKey = async (provider) => {
7509
+ const keyRef = cloudCredentialRef(provider);
7510
+ if (keyRef === void 0) return;
7511
+ if (typeof props.credentials.unset !== "function") {
7512
+ updateRow(provider, {
7513
+ message: t("clearKeyUnsupported"),
7514
+ messageIsError: true
7515
+ });
7516
+ return;
7517
+ }
7518
+ updateRow(provider, {
7519
+ saving: true,
7520
+ message: "",
7521
+ messageIsError: false
7522
+ });
7523
+ try {
7524
+ const response = await props.credentials.unset(keyRef);
7525
+ if (!response.ok) throw new Error(response.error?.message ?? t("clearKeyFailed"));
7526
+ updateRow(provider, {
7527
+ keyStatus: "missing",
7528
+ keyInput: "",
7529
+ testResult: void 0,
7530
+ message: t("keyCleared"),
7531
+ messageIsError: false
7532
+ });
6769
7533
  } catch (cause) {
6770
- reportError(cause instanceof Error ? cause.message : String(cause));
7534
+ updateRow(provider, {
7535
+ message: cause instanceof Error ? cause.message : String(cause),
7536
+ messageIsError: true
7537
+ });
6771
7538
  } finally {
6772
- setSaving(false);
7539
+ updateRow(provider, { saving: false });
6773
7540
  }
6774
7541
  };
6775
- const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
6776
- const workflowStatus = activeWorkflow.length > 0 ? t("workflowImported", { name: activeWorkflow }) : t("workflowMissing");
7542
+ const testResultText = (result) => {
7543
+ if (result === void 0) return "";
7544
+ if (result.ok) return t("testOk");
7545
+ if (result.reason === "missing-key") return t("badgeMissing");
7546
+ if (result.reason === "unauthorized") return t("testUnauthorized");
7547
+ return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${result.message}` : ""}`;
7548
+ };
7549
+ const badgeOf = (provider) => {
7550
+ if (provider === "comfyui") return {
7551
+ text: t("comfyuiNoKey"),
7552
+ className: "dsh-ig-badge dsh-ig-badge-neutral"
7553
+ };
7554
+ const status = rows[provider].keyStatus;
7555
+ if (status === "checking") return {
7556
+ text: t("badgeChecking"),
7557
+ className: "dsh-ig-badge dsh-ig-badge-neutral dsh-ig-badge-checking"
7558
+ };
7559
+ if (status === "configured") return {
7560
+ text: t("badgeConfigured"),
7561
+ className: "dsh-ig-badge dsh-ig-badge-ok"
7562
+ };
7563
+ if (status === "missing") return {
7564
+ text: t("badgeMissing"),
7565
+ className: "dsh-ig-badge dsh-ig-badge-missing"
7566
+ };
7567
+ return {
7568
+ text: t("badgeUnknown"),
7569
+ className: "dsh-ig-badge dsh-ig-badge-neutral"
7570
+ };
7571
+ };
6777
7572
  const importWorkflow = async (event) => {
6778
7573
  const file = event.target.files?.[0];
6779
7574
  event.target.value = "";
6780
7575
  if (file === void 0) return;
6781
- reportMessage("");
7576
+ updateRow("comfyui", {
7577
+ message: "",
7578
+ messageIsError: false
7579
+ });
6782
7580
  try {
6783
7581
  if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
6784
7582
  const json = await file.text();
6785
7583
  validateComfyUIWorkflowJson(json);
6786
- const name = uniqueComfyUIWorkflowName(file.name, workflows.map((entry) => entry.name));
6787
- setWorkflows((current) => [...current, {
6788
- name,
6789
- json
6790
- }]);
6791
- setActiveWorkflow((current) => current.length > 0 ? current : name);
6792
- reportMessage(t("workflowImported", { name }));
7584
+ const name = uniqueComfyUIWorkflowName(file.name, rows.comfyui.workflows.map((entry) => entry.name));
7585
+ setRows((current) => ({
7586
+ ...current,
7587
+ comfyui: {
7588
+ ...current.comfyui,
7589
+ workflows: [...current.comfyui.workflows, {
7590
+ name,
7591
+ json
7592
+ }],
7593
+ activeWorkflow: current.comfyui.activeWorkflow.length > 0 ? current.comfyui.activeWorkflow : name
7594
+ }
7595
+ }));
7596
+ updateRow("comfyui", {
7597
+ message: t("workflowImported", { name }),
7598
+ messageIsError: false
7599
+ });
6793
7600
  } catch (cause) {
6794
- reportError(cause instanceof Error ? cause.message : String(cause));
7601
+ updateRow("comfyui", {
7602
+ message: cause instanceof Error ? cause.message : String(cause),
7603
+ messageIsError: true
7604
+ });
6795
7605
  }
6796
7606
  };
6797
7607
  /** Renaming the active entry keeps the active selection following its new name. */
6798
7608
  const renameWorkflow = (index, name) => {
6799
- const previous = workflows[index];
6800
- setWorkflows((current) => current.map((entry, position) => position === index ? {
6801
- ...entry,
6802
- name
6803
- } : entry));
6804
- if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(name);
7609
+ setRows((current) => {
7610
+ const previous = current.comfyui.workflows[index];
7611
+ const workflows = current.comfyui.workflows.map((entry, position) => position === index ? {
7612
+ ...entry,
7613
+ name
7614
+ } : entry);
7615
+ const activeWorkflow = previous !== void 0 && previous.name === current.comfyui.activeWorkflow ? name : current.comfyui.activeWorkflow;
7616
+ return {
7617
+ ...current,
7618
+ comfyui: {
7619
+ ...current.comfyui,
7620
+ workflows,
7621
+ activeWorkflow
7622
+ }
7623
+ };
7624
+ });
6805
7625
  };
6806
7626
  /** Removing the active entry moves the selection to the first remaining workflow. */
6807
7627
  const removeWorkflow = (index) => {
6808
- const previous = workflows[index];
6809
- const next = workflows.filter((_entry, position) => position !== index);
6810
- setWorkflows(next);
6811
- if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(next[0]?.name ?? "");
7628
+ setRows((current) => {
7629
+ const previous = current.comfyui.workflows[index];
7630
+ const workflows = current.comfyui.workflows.filter((_entry, position) => position !== index);
7631
+ const activeWorkflow = previous !== void 0 && previous.name === current.comfyui.activeWorkflow ? workflows[0]?.name ?? "" : current.comfyui.activeWorkflow;
7632
+ return {
7633
+ ...current,
7634
+ comfyui: {
7635
+ ...current.comfyui,
7636
+ workflows,
7637
+ activeWorkflow
7638
+ }
7639
+ };
7640
+ });
6812
7641
  };
6813
7642
  /** Editing one entry's preset leaves the rest of the entry untouched. */
6814
7643
  const setWorkflowPreset = (index, presetPrompt) => {
6815
- setWorkflows((current) => current.map((entry, position) => position === index ? {
6816
- ...entry,
6817
- presetPrompt
6818
- } : entry));
7644
+ setRows((current) => {
7645
+ const workflows = current.comfyui.workflows.map((entry, position) => position === index ? {
7646
+ ...entry,
7647
+ presetPrompt
7648
+ } : entry);
7649
+ return {
7650
+ ...current,
7651
+ comfyui: {
7652
+ ...current.comfyui,
7653
+ workflows
7654
+ }
7655
+ };
7656
+ });
6819
7657
  };
6820
- return (0, react_jsx_runtime.jsxs)("li", {
6821
- className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
6822
- children: [(0, react_jsx_runtime.jsxs)("button", {
6823
- type: "button",
6824
- className: "dsh-ig-head",
6825
- "aria-expanded": open,
6826
- onClick: () => {
6827
- setOpen((value) => !value);
6828
- },
6829
- children: [(0, react_jsx_runtime.jsxs)("span", {
6830
- className: "dsh-ig-head-text",
6831
- children: [(0, react_jsx_runtime.jsx)("span", {
6832
- className: "dsh-ig-title",
6833
- children: t("title")
6834
- }), (0, react_jsx_runtime.jsx)("span", {
6835
- className: "dsh-ig-desc",
6836
- children: t("description")
6837
- })]
6838
- }), (0, react_jsx_runtime.jsx)("span", {
7658
+ /** Workspace checkbox persists immediately, like every other toggle on this card. */
7659
+ const toggleSaveToWorkspace = (next) => {
7660
+ setSaveToWorkspace(next);
7661
+ setWorkspaceMessage("");
7662
+ setWorkspaceMessageIsError(false);
7663
+ props.scope.set("saveToWorkspace", next).catch((cause) => {
7664
+ setSaveToWorkspace(snapshot.value?.saveToWorkspace ?? true);
7665
+ setWorkspaceMessage(cause instanceof Error ? cause.message : String(cause));
7666
+ setWorkspaceMessageIsError(true);
7667
+ });
7668
+ };
7669
+ /** Folder input persists on Enter or blur; no save button needed. */
7670
+ const commitWorkspaceFolder = () => {
7671
+ const next = workspaceFolder.trim();
7672
+ if (next === (snapshot.value?.workspaceFolder ?? "dsh-image-gen")) return;
7673
+ setWorkspaceMessage("");
7674
+ setWorkspaceMessageIsError(false);
7675
+ props.scope.set("workspaceFolder", next).then(() => {
7676
+ setWorkspaceMessage(t("saved"));
7677
+ }).catch((cause) => {
7678
+ setWorkspaceFolder(snapshot.value?.workspaceFolder ?? "dsh-image-gen");
7679
+ setWorkspaceMessage(cause instanceof Error ? cause.message : String(cause));
7680
+ setWorkspaceMessageIsError(true);
7681
+ });
7682
+ };
7683
+ /** The pill toggle saves immediately, like the default-provider radio. */
7684
+ const toggleShowPill = (next) => {
7685
+ setShowPill(next);
7686
+ setUiMessage("");
7687
+ setUiMessageIsError(false);
7688
+ props.scope.set("showProviderPill", next).catch((cause) => {
7689
+ setShowPill(snapshot.value?.showProviderPill === true);
7690
+ setUiMessage(cause instanceof Error ? cause.message : String(cause));
7691
+ setUiMessageIsError(true);
7692
+ });
7693
+ };
7694
+ const renderCloudBody = (provider) => {
7695
+ const row = rows[provider];
7696
+ const keyRef = cloudCredentialRef(provider) ?? "";
7697
+ const keyReadOnly = row.keyInfo?.writable === false;
7698
+ return (0, react_jsx_runtime.jsx)("div", {
7699
+ className: "dsh-ig-provider-body",
7700
+ children: (0, react_jsx_runtime.jsxs)("form", {
7701
+ onSubmit: (event) => {
7702
+ event.preventDefault();
7703
+ saveProviderRow(provider);
7704
+ },
7705
+ children: [
7706
+ (0, react_jsx_runtime.jsxs)("label", {
7707
+ className: "dsh-ig-field",
7708
+ children: [
7709
+ (0, react_jsx_runtime.jsx)("span", {
7710
+ className: "dsh-ig-label",
7711
+ children: t("apiKeyLabel", { provider: providerLabels[provider] })
7712
+ }),
7713
+ (0, react_jsx_runtime.jsx)("input", {
7714
+ className: "dsh-ig-input",
7715
+ type: "password",
7716
+ autoComplete: "off",
7717
+ value: row.keyInput,
7718
+ onChange: (event) => {
7719
+ updateRow(provider, { keyInput: event.target.value });
7720
+ },
7721
+ placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
7722
+ disabled: !snapshot.writable || keyReadOnly
7723
+ }),
7724
+ (0, react_jsx_runtime.jsx)("span", {
7725
+ className: "dsh-ig-hint",
7726
+ children: keyReadOnly ? t("keyReadOnly", { source: row.keyInfo?.source ?? "" }) : t("apiKeyHint", { key: keyRef })
7727
+ })
7728
+ ]
7729
+ }),
7730
+ (0, react_jsx_runtime.jsxs)("label", {
7731
+ className: "dsh-ig-field",
7732
+ children: [
7733
+ (0, react_jsx_runtime.jsx)("span", {
7734
+ className: "dsh-ig-label",
7735
+ children: t("endpoint")
7736
+ }),
7737
+ (0, react_jsx_runtime.jsxs)("div", {
7738
+ className: "dsh-ig-input-group",
7739
+ children: [(0, react_jsx_runtime.jsx)("input", {
7740
+ className: "dsh-ig-input",
7741
+ type: "url",
7742
+ value: row.baseURL,
7743
+ onChange: (event) => {
7744
+ updateRow(provider, { baseURL: event.target.value });
7745
+ },
7746
+ required: true,
7747
+ disabled: !snapshot.writable
7748
+ }), provider !== "openai-compat" ? (0, react_jsx_runtime.jsx)("button", {
7749
+ type: "button",
7750
+ className: "dsh-ig-btn-reset",
7751
+ title: t("resetTitle"),
7752
+ onClick: () => {
7753
+ updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
7754
+ },
7755
+ disabled: !snapshot.writable,
7756
+ children: t("reset")
7757
+ }) : null]
7758
+ }),
7759
+ (0, react_jsx_runtime.jsx)("span", {
7760
+ className: "dsh-ig-hint",
7761
+ children: t(CLOUD_HINT_KEYS[provider])
7762
+ })
7763
+ ]
7764
+ }),
7765
+ (0, react_jsx_runtime.jsxs)("label", {
7766
+ className: "dsh-ig-field",
7767
+ children: [
7768
+ (0, react_jsx_runtime.jsx)("span", {
7769
+ className: "dsh-ig-label",
7770
+ children: t("model")
7771
+ }),
7772
+ modelPullSupported(provider) ? (0, react_jsx_runtime.jsxs)("div", {
7773
+ className: "dsh-ig-input-group",
7774
+ children: [
7775
+ (0, react_jsx_runtime.jsx)("input", {
7776
+ className: "dsh-ig-input",
7777
+ value: row.model,
7778
+ onChange: (event) => {
7779
+ updateRow(provider, { model: event.target.value });
7780
+ },
7781
+ list: `dsh-ig-${provider}-model-options`,
7782
+ required: provider !== "openai-compat",
7783
+ disabled: !snapshot.writable
7784
+ }),
7785
+ (0, react_jsx_runtime.jsx)("datalist", {
7786
+ id: `dsh-ig-${provider}-model-options`,
7787
+ children: row.modelOptions.map((id) => (0, react_jsx_runtime.jsx)("option", { value: id }, id))
7788
+ }),
7789
+ (0, react_jsx_runtime.jsx)("button", {
7790
+ type: "button",
7791
+ className: "dsh-ig-btn-secondary",
7792
+ disabled: row.fetchingModels || !snapshot.writable,
7793
+ onClick: () => {
7794
+ fetchProviderModels(provider);
7795
+ },
7796
+ children: row.fetchingModels ? t("fetchingModels") : t("fetchModels")
7797
+ })
7798
+ ]
7799
+ }) : (0, react_jsx_runtime.jsx)("input", {
7800
+ className: "dsh-ig-input",
7801
+ value: row.model,
7802
+ onChange: (event) => {
7803
+ updateRow(provider, { model: event.target.value });
7804
+ },
7805
+ required: true,
7806
+ disabled: !snapshot.writable
7807
+ }),
7808
+ modelPullSupported(provider) ? row.modelFetchMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
7809
+ className: `dsh-ig-hint${row.modelFetchIsError ? " dsh-ig-hint-error" : ""}`,
7810
+ role: "status",
7811
+ children: row.modelFetchMessage
7812
+ }) : (0, react_jsx_runtime.jsx)("span", {
7813
+ className: "dsh-ig-hint",
7814
+ children: t("fetchModelsHint")
7815
+ }) : null
7816
+ ]
7817
+ }),
7818
+ (0, react_jsx_runtime.jsxs)("div", {
7819
+ className: "dsh-ig-row-actions",
7820
+ children: [(0, react_jsx_runtime.jsx)("p", {
7821
+ className: `dsh-ig-status${row.messageIsError ? " dsh-ig-status-error" : ""}`,
7822
+ role: "status",
7823
+ children: row.message || testResultText(row.testResult)
7824
+ }), (0, react_jsx_runtime.jsxs)("span", {
7825
+ className: "dsh-ig-row-buttons",
7826
+ children: [
7827
+ (0, react_jsx_runtime.jsx)("button", {
7828
+ type: "button",
7829
+ className: "dsh-ig-btn-secondary",
7830
+ disabled: row.testing,
7831
+ onClick: () => {
7832
+ testConnection(provider);
7833
+ },
7834
+ children: row.testing ? t("testing") : t("testConnection")
7835
+ }),
7836
+ row.keyStatus === "configured" && !keyReadOnly ? (0, react_jsx_runtime.jsx)("button", {
7837
+ type: "button",
7838
+ className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
7839
+ disabled: row.saving,
7840
+ onClick: () => {
7841
+ clearProviderKey(provider);
7842
+ },
7843
+ children: t("clearKey")
7844
+ }) : null,
7845
+ (0, react_jsx_runtime.jsx)("button", {
7846
+ className: "dsh-ig-save",
7847
+ type: "submit",
7848
+ disabled: row.saving || !snapshot.writable,
7849
+ children: row.saving ? t("saving") : t("save")
7850
+ })
7851
+ ]
7852
+ })]
7853
+ })
7854
+ ]
7855
+ })
7856
+ });
7857
+ };
7858
+ const renderComfyUIBody = () => {
7859
+ const row = rows.comfyui;
7860
+ return (0, react_jsx_runtime.jsx)("div", {
7861
+ className: "dsh-ig-provider-body",
7862
+ children: (0, react_jsx_runtime.jsxs)("form", {
7863
+ onSubmit: (event) => {
7864
+ event.preventDefault();
7865
+ saveProviderRow("comfyui");
7866
+ },
7867
+ children: [
7868
+ (0, react_jsx_runtime.jsxs)("label", {
7869
+ className: "dsh-ig-field",
7870
+ children: [
7871
+ (0, react_jsx_runtime.jsx)("span", {
7872
+ className: "dsh-ig-label",
7873
+ children: t("endpoint")
7874
+ }),
7875
+ (0, react_jsx_runtime.jsxs)("div", {
7876
+ className: "dsh-ig-input-group",
7877
+ children: [(0, react_jsx_runtime.jsx)("input", {
7878
+ className: "dsh-ig-input",
7879
+ type: "url",
7880
+ value: row.baseURL,
7881
+ onChange: (event) => {
7882
+ updateRow("comfyui", { baseURL: event.target.value });
7883
+ },
7884
+ required: true,
7885
+ disabled: !snapshot.writable
7886
+ }), (0, react_jsx_runtime.jsx)("button", {
7887
+ type: "button",
7888
+ className: "dsh-ig-btn-reset",
7889
+ title: t("resetTitle"),
7890
+ onClick: () => {
7891
+ updateRow("comfyui", { baseURL: DEFAULT_BASE_URLS.comfyui });
7892
+ },
7893
+ disabled: !snapshot.writable,
7894
+ children: t("reset")
7895
+ })]
7896
+ }),
7897
+ (0, react_jsx_runtime.jsx)("span", {
7898
+ className: "dsh-ig-hint",
7899
+ children: t("endpointHintComfyUI")
7900
+ })
7901
+ ]
7902
+ }),
7903
+ (0, react_jsx_runtime.jsxs)("div", {
7904
+ className: "dsh-ig-field",
7905
+ children: [
7906
+ (0, react_jsx_runtime.jsx)("span", {
7907
+ className: "dsh-ig-label",
7908
+ children: t("workflow")
7909
+ }),
7910
+ (0, react_jsx_runtime.jsxs)("div", {
7911
+ className: "dsh-ig-file-row",
7912
+ children: [(0, react_jsx_runtime.jsxs)("label", {
7913
+ className: "dsh-ig-file-button",
7914
+ children: [(0, react_jsx_runtime.jsx)("input", {
7915
+ className: "dsh-ig-file-input",
7916
+ type: "file",
7917
+ accept: ".json,application/json",
7918
+ onChange: (event) => {
7919
+ importWorkflow(event);
7920
+ }
7921
+ }), t("workflowImport")]
7922
+ }), row.workflows.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
7923
+ className: "dsh-ig-file-name",
7924
+ children: t("workflowMissing")
7925
+ }) : null]
7926
+ }),
7927
+ row.workflows.length > 0 ? (0, react_jsx_runtime.jsx)("ul", {
7928
+ className: "dsh-ig-workflow-list",
7929
+ children: row.workflows.map((entry, index) => (0, react_jsx_runtime.jsxs)("li", {
7930
+ className: "dsh-ig-workflow-row",
7931
+ children: [(0, react_jsx_runtime.jsxs)("div", {
7932
+ className: "dsh-ig-workflow-main",
7933
+ children: [
7934
+ (0, react_jsx_runtime.jsx)("label", {
7935
+ className: "dsh-ig-workflow-active",
7936
+ title: t("workflowActiveTitle"),
7937
+ children: (0, react_jsx_runtime.jsx)("input", {
7938
+ type: "radio",
7939
+ name: "dsh-ig-active-workflow",
7940
+ "aria-label": t("workflowActiveTitle"),
7941
+ checked: entry.name === row.activeWorkflow,
7942
+ onChange: () => {
7943
+ updateRow("comfyui", { activeWorkflow: entry.name });
7944
+ }
7945
+ })
7946
+ }),
7947
+ (0, react_jsx_runtime.jsx)("input", {
7948
+ className: "dsh-ig-input dsh-ig-workflow-name",
7949
+ value: entry.name,
7950
+ title: entry.name,
7951
+ onChange: (event) => {
7952
+ renameWorkflow(index, event.target.value);
7953
+ }
7954
+ }),
7955
+ (0, react_jsx_runtime.jsx)("button", {
7956
+ type: "button",
7957
+ className: "dsh-ig-btn-reset",
7958
+ onClick: () => {
7959
+ removeWorkflow(index);
7960
+ },
7961
+ children: t("workflowRemove")
7962
+ })
7963
+ ]
7964
+ }), (0, react_jsx_runtime.jsx)("input", {
7965
+ className: "dsh-ig-input dsh-ig-workflow-preset",
7966
+ value: entry.presetPrompt ?? "",
7967
+ placeholder: t("workflowPresetPlaceholder"),
7968
+ title: t("workflowPresetTitle"),
7969
+ onChange: (event) => {
7970
+ setWorkflowPreset(index, event.target.value);
7971
+ }
7972
+ })]
7973
+ }, String(index)))
7974
+ }) : null,
7975
+ (0, react_jsx_runtime.jsx)("span", {
7976
+ className: "dsh-ig-hint",
7977
+ children: t("workflowHint")
7978
+ })
7979
+ ]
7980
+ }),
7981
+ (0, react_jsx_runtime.jsxs)("label", {
7982
+ className: "dsh-ig-field",
7983
+ children: [
7984
+ (0, react_jsx_runtime.jsx)("span", {
7985
+ className: "dsh-ig-label",
7986
+ children: t("timeout")
7987
+ }),
7988
+ (0, react_jsx_runtime.jsx)("input", {
7989
+ className: "dsh-ig-input",
7990
+ type: "number",
7991
+ min: "1",
7992
+ max: "3600",
7993
+ step: "1",
7994
+ value: row.timeoutSeconds,
7995
+ onChange: (event) => {
7996
+ updateRow("comfyui", { timeoutSeconds: Number(event.target.value) });
7997
+ },
7998
+ required: true,
7999
+ disabled: !snapshot.writable
8000
+ }),
8001
+ (0, react_jsx_runtime.jsx)("span", {
8002
+ className: "dsh-ig-hint",
8003
+ children: t("timeoutHint")
8004
+ })
8005
+ ]
8006
+ }),
8007
+ (0, react_jsx_runtime.jsxs)("div", {
8008
+ className: "dsh-ig-row-actions",
8009
+ children: [(0, react_jsx_runtime.jsx)("p", {
8010
+ className: `dsh-ig-status${row.messageIsError ? " dsh-ig-status-error" : ""}`,
8011
+ role: "status",
8012
+ children: row.message || testResultText(row.testResult)
8013
+ }), (0, react_jsx_runtime.jsxs)("span", {
8014
+ className: "dsh-ig-row-buttons",
8015
+ children: [(0, react_jsx_runtime.jsx)("button", {
8016
+ type: "button",
8017
+ className: "dsh-ig-btn-secondary",
8018
+ disabled: row.testing,
8019
+ onClick: () => {
8020
+ testConnection("comfyui");
8021
+ },
8022
+ children: row.testing ? t("testing") : t("testConnection")
8023
+ }), (0, react_jsx_runtime.jsx)("button", {
8024
+ className: "dsh-ig-save",
8025
+ type: "submit",
8026
+ disabled: row.saving || !snapshot.writable || row.workflows.length === 0,
8027
+ children: row.saving ? t("saving") : t("save")
8028
+ })]
8029
+ })]
8030
+ })
8031
+ ]
8032
+ })
8033
+ });
8034
+ };
8035
+ const renderProviderRow = (provider) => {
8036
+ const row = rows[provider];
8037
+ const badge = badgeOf(provider);
8038
+ return (0, react_jsx_runtime.jsxs)("div", {
8039
+ className: `dsh-ig-provider-row ${row.expanded ? "dsh-ig-provider-row-open" : ""}`,
8040
+ children: [(0, react_jsx_runtime.jsxs)("button", {
8041
+ type: "button",
8042
+ className: "dsh-ig-provider-head",
8043
+ "aria-expanded": row.expanded,
8044
+ onClick: () => {
8045
+ updateRow(provider, { expanded: !row.expanded });
8046
+ },
8047
+ children: [
8048
+ (0, react_jsx_runtime.jsx)("span", {
8049
+ className: "dsh-ig-provider-name",
8050
+ children: providerLabels[provider]
8051
+ }),
8052
+ (0, react_jsx_runtime.jsxs)("span", {
8053
+ className: badge.className,
8054
+ title: badge.text,
8055
+ children: [(0, react_jsx_runtime.jsx)("span", {
8056
+ className: "dsh-ig-badge-dot",
8057
+ "aria-hidden": "true"
8058
+ }), badge.text]
8059
+ }),
8060
+ (0, react_jsx_runtime.jsx)("span", {
8061
+ className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
8062
+ "aria-hidden": "true",
8063
+ children: (0, react_jsx_runtime.jsx)("svg", {
8064
+ width: "12",
8065
+ height: "12",
8066
+ viewBox: "0 0 16 16",
8067
+ fill: "none",
8068
+ stroke: "currentColor",
8069
+ strokeWidth: "2",
8070
+ strokeLinecap: "round",
8071
+ strokeLinejoin: "round",
8072
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
8073
+ })
8074
+ })
8075
+ ]
8076
+ }), row.expanded ? provider === "comfyui" ? renderComfyUIBody() : renderCloudBody(provider) : null]
8077
+ }, provider);
8078
+ };
8079
+ return (0, react_jsx_runtime.jsxs)("li", {
8080
+ className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
8081
+ children: [(0, react_jsx_runtime.jsxs)("button", {
8082
+ type: "button",
8083
+ className: "dsh-ig-head",
8084
+ "aria-expanded": open,
8085
+ onClick: () => {
8086
+ setOpen((value) => !value);
8087
+ },
8088
+ children: [(0, react_jsx_runtime.jsxs)("span", {
8089
+ className: "dsh-ig-head-text",
8090
+ children: [(0, react_jsx_runtime.jsx)("span", {
8091
+ className: "dsh-ig-title",
8092
+ children: t("title")
8093
+ }), (0, react_jsx_runtime.jsx)("span", {
8094
+ className: "dsh-ig-desc",
8095
+ children: t("description")
8096
+ })]
8097
+ }), (0, react_jsx_runtime.jsx)("span", {
6839
8098
  className: `dsh-ig-chevron ${open ? "dsh-ig-chevron-open" : ""}`,
6840
8099
  "aria-hidden": "true",
6841
8100
  children: (0, react_jsx_runtime.jsx)("svg", {
@@ -6850,280 +8109,147 @@ window.__ModuleLoader__.load({
6850
8109
  children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
6851
8110
  })
6852
8111
  })]
6853
- }), open ? (0, react_jsx_runtime.jsxs)("form", {
8112
+ }), open ? (0, react_jsx_runtime.jsxs)("div", {
6854
8113
  className: "dsh-ig-body",
6855
- onSubmit: (event) => {
6856
- save(event);
6857
- },
6858
8114
  children: [
6859
- (0, react_jsx_runtime.jsxs)("label", {
6860
- className: "dsh-ig-field",
6861
- children: [
6862
- (0, react_jsx_runtime.jsx)("span", {
6863
- className: "dsh-ig-label",
6864
- children: t("provider")
6865
- }),
6866
- (0, react_jsx_runtime.jsxs)("select", {
6867
- className: "dsh-ig-input",
6868
- value: provider,
6869
- onChange: (event) => {
6870
- const next = event.target.value;
6871
- setProvider(next);
6872
- setModel(modelOf(next, snapshot.value));
6873
- setBaseURL(baseURLOf(next, snapshot.value));
6874
- setKey("");
6875
- },
6876
- children: [
6877
- (0, react_jsx_runtime.jsx)("option", {
6878
- value: "google",
6879
- children: t("providerGoogle")
6880
- }),
6881
- (0, react_jsx_runtime.jsx)("option", {
6882
- value: "openai",
6883
- children: t("providerOpenAI")
6884
- }),
6885
- (0, react_jsx_runtime.jsx)("option", {
6886
- value: "seedream",
6887
- children: t("providerSeedream")
6888
- }),
6889
- (0, react_jsx_runtime.jsx)("option", {
6890
- value: "dashscope",
6891
- children: t("providerDashScope")
6892
- }),
6893
- (0, react_jsx_runtime.jsx)("option", {
6894
- value: "comfyui",
6895
- children: t("providerComfyUI")
6896
- })
6897
- ]
6898
- }),
6899
- (0, react_jsx_runtime.jsx)("span", {
6900
- className: "dsh-ig-hint",
6901
- children: providerLabels[provider]
6902
- })
6903
- ]
6904
- }),
6905
- provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
6906
- className: "dsh-ig-field",
6907
- children: [
6908
- (0, react_jsx_runtime.jsx)("span", {
6909
- className: "dsh-ig-label",
6910
- children: t("apiKeyLabel", { provider: providerLabels[provider] })
6911
- }),
6912
- (0, react_jsx_runtime.jsx)("input", {
6913
- className: "dsh-ig-input",
6914
- type: "password",
6915
- autoComplete: "off",
6916
- value: key,
6917
- onChange: (event) => {
6918
- setKey(event.target.value);
6919
- },
6920
- placeholder: configured ? t("apiKeyPlaceholder") : ""
6921
- }),
6922
- (0, react_jsx_runtime.jsx)("span", {
6923
- className: "dsh-ig-hint",
6924
- children: t("apiKeyHint", { key: KEY_REF[provider] ?? "" })
6925
- })
6926
- ]
8115
+ !snapshot.writable ? (0, react_jsx_runtime.jsx)("p", {
8116
+ className: "dsh-ig-status dsh-ig-status-readonly",
8117
+ role: "note",
8118
+ children: t("settingsReadOnly")
6927
8119
  }) : null,
6928
- (0, react_jsx_runtime.jsxs)("label", {
8120
+ (0, react_jsx_runtime.jsxs)("div", {
6929
8121
  className: "dsh-ig-field",
6930
8122
  children: [
6931
8123
  (0, react_jsx_runtime.jsx)("span", {
6932
8124
  className: "dsh-ig-label",
6933
- children: t("endpoint")
8125
+ children: t("defaultProvider")
6934
8126
  }),
6935
- (0, react_jsx_runtime.jsxs)("div", {
6936
- className: "dsh-ig-input-group",
6937
- children: [(0, react_jsx_runtime.jsx)("input", {
6938
- className: "dsh-ig-input",
6939
- type: "url",
6940
- value: baseURL,
6941
- onChange: (event) => {
6942
- setBaseURL(event.target.value);
6943
- },
6944
- required: true
6945
- }), (0, react_jsx_runtime.jsx)("button", {
6946
- type: "button",
6947
- className: "dsh-ig-btn-reset",
6948
- title: t("resetTitle"),
6949
- onClick: () => {
6950
- setBaseURL(DEFAULT_BASE_URLS[provider]);
6951
- },
6952
- children: t("reset")
6953
- })]
8127
+ (0, react_jsx_runtime.jsx)("div", {
8128
+ className: "dsh-ig-radios",
8129
+ role: "radiogroup",
8130
+ "aria-label": t("defaultProvider"),
8131
+ children: IMAGE_PROVIDERS.map((provider) => (0, react_jsx_runtime.jsxs)("label", {
8132
+ className: `dsh-ig-radio${defaultProvider === provider ? " dsh-ig-radio-checked" : ""}`,
8133
+ children: [(0, react_jsx_runtime.jsx)("input", {
8134
+ type: "radio",
8135
+ name: "dsh-ig-default-provider",
8136
+ checked: defaultProvider === provider,
8137
+ onChange: () => {
8138
+ selectDefaultProvider(provider);
8139
+ },
8140
+ disabled: !snapshot.writable
8141
+ }), providerLabels[provider]]
8142
+ }, provider))
6954
8143
  }),
6955
8144
  (0, react_jsx_runtime.jsx)("span", {
6956
8145
  className: "dsh-ig-hint",
6957
- children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : provider === "dashscope" ? t("endpointHintDashScope") : t("endpointHintComfyUI")
6958
- })
8146
+ children: t("defaultProviderHint")
8147
+ }),
8148
+ providerMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
8149
+ className: `dsh-ig-status${providerMessageIsError ? " dsh-ig-status-error" : ""}`,
8150
+ role: "status",
8151
+ children: providerMessage
8152
+ }) : null
6959
8153
  ]
6960
8154
  }),
6961
- provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
6962
- className: "dsh-ig-field",
6963
- children: [(0, react_jsx_runtime.jsx)("span", {
6964
- className: "dsh-ig-label",
6965
- children: t("model")
6966
- }), (0, react_jsx_runtime.jsx)("input", {
6967
- className: "dsh-ig-input",
6968
- value: model,
6969
- onChange: (event) => {
6970
- setModel(event.target.value);
6971
- },
6972
- required: true
6973
- })]
6974
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
6975
- className: "dsh-ig-field",
8155
+ (0, react_jsx_runtime.jsx)("div", {
8156
+ className: "dsh-ig-providers",
8157
+ children: IMAGE_PROVIDERS.map((provider) => renderProviderRow(provider))
8158
+ }),
8159
+ (0, react_jsx_runtime.jsxs)("div", {
8160
+ className: "dsh-ig-section",
6976
8161
  children: [
6977
8162
  (0, react_jsx_runtime.jsx)("span", {
6978
- className: "dsh-ig-label",
6979
- children: t("workflow")
8163
+ className: "dsh-ig-section-title",
8164
+ children: t("workspaceSection")
6980
8165
  }),
6981
8166
  (0, react_jsx_runtime.jsxs)("div", {
6982
- className: "dsh-ig-file-row",
8167
+ className: "dsh-ig-field",
6983
8168
  children: [(0, react_jsx_runtime.jsxs)("label", {
6984
- className: "dsh-ig-file-button",
8169
+ className: "dsh-ig-check-row",
6985
8170
  children: [(0, react_jsx_runtime.jsx)("input", {
6986
- className: "dsh-ig-file-input",
6987
- type: "file",
6988
- accept: ".json,application/json",
8171
+ type: "checkbox",
8172
+ checked: saveToWorkspace,
6989
8173
  onChange: (event) => {
6990
- importWorkflow(event);
6991
- }
6992
- }), t("workflowImport")]
6993
- }), workflows.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
6994
- className: "dsh-ig-file-name",
6995
- children: t("workflowMissing")
6996
- }) : null]
8174
+ toggleSaveToWorkspace(event.target.checked);
8175
+ },
8176
+ disabled: !snapshot.writable
8177
+ }), (0, react_jsx_runtime.jsx)("span", {
8178
+ className: "dsh-ig-label",
8179
+ children: t("saveToWorkspace")
8180
+ })]
8181
+ }), (0, react_jsx_runtime.jsx)("span", {
8182
+ className: "dsh-ig-hint",
8183
+ children: t("saveToWorkspaceHint")
8184
+ })]
6997
8185
  }),
6998
- workflows.length > 0 ? (0, react_jsx_runtime.jsx)("ul", {
6999
- className: "dsh-ig-workflow-list",
7000
- children: workflows.map((entry, index) => (0, react_jsx_runtime.jsxs)("li", {
7001
- className: "dsh-ig-workflow-row",
7002
- children: [(0, react_jsx_runtime.jsxs)("div", {
7003
- className: "dsh-ig-workflow-main",
7004
- children: [
7005
- (0, react_jsx_runtime.jsx)("label", {
7006
- className: "dsh-ig-workflow-active",
7007
- title: t("workflowActiveTitle"),
7008
- children: (0, react_jsx_runtime.jsx)("input", {
7009
- type: "radio",
7010
- name: "dsh-ig-active-workflow",
7011
- "aria-label": t("workflowActiveTitle"),
7012
- checked: entry.name === activeWorkflow,
7013
- onChange: () => {
7014
- setActiveWorkflow(entry.name);
7015
- }
7016
- })
7017
- }),
7018
- (0, react_jsx_runtime.jsx)("input", {
7019
- className: "dsh-ig-input dsh-ig-workflow-name",
7020
- value: entry.name,
7021
- title: entry.name,
7022
- onChange: (event) => {
7023
- renameWorkflow(index, event.target.value);
7024
- }
7025
- }),
7026
- (0, react_jsx_runtime.jsx)("button", {
7027
- type: "button",
7028
- className: "dsh-ig-btn-reset",
7029
- onClick: () => {
7030
- removeWorkflow(index);
7031
- },
7032
- children: t("workflowRemove")
7033
- })
7034
- ]
7035
- }), (0, react_jsx_runtime.jsx)("input", {
7036
- className: "dsh-ig-input dsh-ig-workflow-preset",
7037
- value: entry.presetPrompt ?? "",
7038
- placeholder: t("workflowPresetPlaceholder"),
7039
- title: t("workflowPresetTitle"),
8186
+ saveToWorkspace ? (0, react_jsx_runtime.jsxs)("label", {
8187
+ className: "dsh-ig-field",
8188
+ children: [
8189
+ (0, react_jsx_runtime.jsx)("span", {
8190
+ className: "dsh-ig-label",
8191
+ children: t("folder")
8192
+ }),
8193
+ (0, react_jsx_runtime.jsx)("input", {
8194
+ className: "dsh-ig-input",
8195
+ value: workspaceFolder,
7040
8196
  onChange: (event) => {
7041
- setWorkflowPreset(index, event.target.value);
7042
- }
7043
- })]
7044
- }, String(index)))
8197
+ setWorkspaceFolder(event.target.value);
8198
+ },
8199
+ onBlur: () => {
8200
+ commitWorkspaceFolder();
8201
+ },
8202
+ onKeyDown: (event) => {
8203
+ if (event.key === "Enter") commitWorkspaceFolder();
8204
+ },
8205
+ placeholder: "dsh-image-gen",
8206
+ disabled: !snapshot.writable
8207
+ }),
8208
+ (0, react_jsx_runtime.jsx)("span", {
8209
+ className: "dsh-ig-hint",
8210
+ children: t("folderHint")
8211
+ })
8212
+ ]
7045
8213
  }) : null,
7046
- (0, react_jsx_runtime.jsx)("span", {
7047
- className: "dsh-ig-hint",
7048
- children: t("workflowHint")
7049
- })
7050
- ]
7051
- }), (0, react_jsx_runtime.jsxs)("label", {
7052
- className: "dsh-ig-field",
7053
- children: [
7054
- (0, react_jsx_runtime.jsx)("span", {
7055
- className: "dsh-ig-label",
7056
- children: t("timeout")
7057
- }),
7058
- (0, react_jsx_runtime.jsx)("input", {
7059
- className: "dsh-ig-input",
7060
- type: "number",
7061
- min: "1",
7062
- max: "3600",
7063
- step: "1",
7064
- value: timeoutSeconds,
7065
- onChange: (event) => {
7066
- setTimeoutSeconds(Number(event.target.value));
7067
- },
7068
- required: true
7069
- }),
7070
- (0, react_jsx_runtime.jsx)("span", {
7071
- className: "dsh-ig-hint",
7072
- children: t("timeoutHint")
7073
- })
8214
+ workspaceMessage.length > 0 ? (0, react_jsx_runtime.jsx)("p", {
8215
+ className: `dsh-ig-status${workspaceMessageIsError ? " dsh-ig-status-error" : ""}`,
8216
+ role: "status",
8217
+ children: workspaceMessage
8218
+ }) : null
7074
8219
  ]
7075
- })] }),
7076
- (0, react_jsx_runtime.jsxs)("div", {
7077
- className: "dsh-ig-field",
7078
- children: [(0, react_jsx_runtime.jsxs)("label", {
7079
- className: "dsh-ig-check-row",
7080
- children: [(0, react_jsx_runtime.jsx)("input", {
7081
- type: "checkbox",
7082
- checked: saveToWorkspace,
7083
- onChange: (event) => {
7084
- setSaveToWorkspace(event.target.checked);
7085
- }
7086
- }), (0, react_jsx_runtime.jsx)("span", {
7087
- className: "dsh-ig-label",
7088
- children: t("saveToWorkspace")
7089
- })]
7090
- }), (0, react_jsx_runtime.jsx)("span", {
7091
- className: "dsh-ig-hint",
7092
- children: t("saveToWorkspaceHint")
7093
- })]
7094
8220
  }),
7095
- saveToWorkspace ? (0, react_jsx_runtime.jsxs)("label", {
7096
- className: "dsh-ig-field",
7097
- children: [
7098
- (0, react_jsx_runtime.jsx)("span", {
7099
- className: "dsh-ig-label",
7100
- children: t("folder")
7101
- }),
7102
- (0, react_jsx_runtime.jsx)("input", {
7103
- className: "dsh-ig-input",
7104
- value: workspaceFolder,
7105
- onChange: (event) => {
7106
- setWorkspaceFolder(event.target.value);
7107
- },
7108
- placeholder: "dsh-image-gen"
7109
- }),
7110
- (0, react_jsx_runtime.jsx)("span", {
7111
- className: "dsh-ig-hint",
7112
- children: t("folderHint")
7113
- })
7114
- ]
7115
- }) : null,
7116
8221
  (0, react_jsx_runtime.jsxs)("div", {
7117
- className: "dsh-ig-actions",
7118
- children: [(0, react_jsx_runtime.jsx)("p", {
7119
- className: `dsh-ig-status${messageIsError ? " dsh-ig-status-error" : ""}`,
7120
- role: "status",
7121
- children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
7122
- }), (0, react_jsx_runtime.jsx)("button", {
7123
- className: "dsh-ig-save",
7124
- type: "submit",
7125
- disabled: saving || !snapshot.writable || provider === "comfyui" && workflows.length === 0,
7126
- children: saving ? t("saving") : t("save")
8222
+ className: "dsh-ig-section",
8223
+ children: [(0, react_jsx_runtime.jsx)("span", {
8224
+ className: "dsh-ig-section-title",
8225
+ children: t("uiSection")
8226
+ }), (0, react_jsx_runtime.jsxs)("div", {
8227
+ className: "dsh-ig-field",
8228
+ children: [
8229
+ (0, react_jsx_runtime.jsxs)("label", {
8230
+ className: "dsh-ig-check-row",
8231
+ children: [(0, react_jsx_runtime.jsx)("input", {
8232
+ type: "checkbox",
8233
+ checked: showPill,
8234
+ onChange: (event) => {
8235
+ toggleShowPill(event.target.checked);
8236
+ },
8237
+ disabled: !snapshot.writable
8238
+ }), (0, react_jsx_runtime.jsx)("span", {
8239
+ className: "dsh-ig-label",
8240
+ children: t("showPill")
8241
+ })]
8242
+ }),
8243
+ (0, react_jsx_runtime.jsx)("span", {
8244
+ className: "dsh-ig-hint",
8245
+ children: t("showPillHint")
8246
+ }),
8247
+ uiMessage.length > 0 ? (0, react_jsx_runtime.jsx)("p", {
8248
+ className: `dsh-ig-status${uiMessageIsError ? " dsh-ig-status-error" : ""}`,
8249
+ role: "status",
8250
+ children: uiMessage
8251
+ }) : null
8252
+ ]
7127
8253
  })]
7128
8254
  })
7129
8255
  ]
@@ -7674,11 +8800,11 @@ window.__ModuleLoader__.load({
7674
8800
  };
7675
8801
  }
7676
8802
  function modelOf(provider, value) {
7677
- const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : activeComfyUIWorkflow(value ?? {})?.name;
8803
+ const stored = provider === "comfyui" ? activeComfyUIWorkflow(value ?? {})?.name : value?.[CLOUD_MODEL_FIELDS[provider]];
7678
8804
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
7679
8805
  }
7680
8806
  function baseURLOf(provider, value) {
7681
- const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : provider === "dashscope" ? value?.dashscopeEndpoint : value?.comfyuiBaseURL;
8807
+ const stored = provider === "comfyui" ? value?.comfyuiBaseURL : value?.[CLOUD_URL_FIELDS[provider]];
7682
8808
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
7683
8809
  }
7684
8810
  //#endregion