dsh-image-gen 0.2.1 → 0.2.3

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,19 +17,58 @@ window.__ModuleLoader__.load({
17
17
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
18
18
  const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
19
19
  const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
20
+ const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
21
+ const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
20
22
  const DEFAULT_MODELS = {
21
23
  google: "gemini-3.1-flash-image",
22
24
  openai: "gpt-image-2",
23
25
  seedream: "doubao-seedream-5-0-260128",
24
- dashscope: "qwen-image-3.0"
26
+ dashscope: "qwen-image-3.0",
27
+ comfyui: "API workflow"
25
28
  };
26
29
  const DEFAULT_BASE_URLS = {
27
30
  google: DEFAULT_GOOGLE_ENDPOINT,
28
31
  openai: DEFAULT_OPENAI_BASE_URL,
29
32
  seedream: DEFAULT_SEEDREAM_BASE_URL,
30
- dashscope: DEFAULT_DASHSCOPE_ENDPOINT
33
+ dashscope: DEFAULT_DASHSCOPE_ENDPOINT,
34
+ comfyui: DEFAULT_COMFYUI_BASE_URL
31
35
  };
32
36
  //#endregion
37
+ //#region lib/types/comfyui-workflow.js
38
+ /** Pure ComfyUI API-workflow validation and placeholder injection. */
39
+ const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
40
+ /** Validate imported JSON without exposing workflow graph details to callers. */
41
+ function validateComfyUIWorkflowJson(workflowJson) {
42
+ parseWorkflow(workflowJson);
43
+ }
44
+ function parseWorkflow(workflowJson) {
45
+ if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
46
+ if (new TextEncoder().encode(workflowJson).byteLength > 5242880) throw new Error("ComfyUI workflow file must be no larger than 5 MB");
47
+ let value;
48
+ try {
49
+ value = JSON.parse(workflowJson);
50
+ } catch {
51
+ throw new Error("ComfyUI workflow file is not valid JSON");
52
+ }
53
+ if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
54
+ if (!Object.values(value).some((node) => {
55
+ const inputs = record(node)?.inputs;
56
+ return inputs !== void 0 && containsPromptPlaceholder(inputs);
57
+ })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
58
+ return value;
59
+ }
60
+ function containsPromptPlaceholder(value) {
61
+ if (typeof value === "string") return value.includes(COMFYUI_PROMPT_PLACEHOLDER);
62
+ if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
63
+ return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
64
+ }
65
+ function isRecord(value) {
66
+ return typeof value === "object" && value !== null && !Array.isArray(value);
67
+ }
68
+ function record(value) {
69
+ return isRecord(value) ? value : void 0;
70
+ }
71
+ //#endregion
33
72
  //#region lib/types/client/gallery-store.js
34
73
  const DB_NAME = "dsh_image_gen_db";
35
74
  const DB_VERSION = 2;
@@ -889,8 +928,20 @@ window.__ModuleLoader__.load({
889
928
  }
890
929
  }
891
930
  //#endregion
931
+ //#region lib/types/client/image-ref.js
932
+ /**
933
+ * Extract the verified durable image attachment from a completed tool result block.
934
+ * Supports legacy rc.2 `resultView.content` and modern DSH `block.content`.
935
+ */
936
+ function imageRef(block) {
937
+ if (!("kind" in block)) return void 0;
938
+ const fromResultView = block.resultView?.card === "generic" ? block.resultView.content?.find((item) => item.type === "image") : void 0;
939
+ if (fromResultView?.type === "image") return fromResultView.attachment;
940
+ const fromBlockContent = Array.isArray(block.content) ? block.content.find((item) => item.type === "image") : void 0;
941
+ if (fromBlockContent?.type === "image") return fromBlockContent.attachment;
942
+ }
943
+ //#endregion
892
944
  //#region lib/types/client/index.js
893
- /** Web settings and generated-image cards contributed by the Bundle. */
894
945
  const KEY_REF = {
895
946
  google: "GEMINI_API_KEY",
896
947
  openai: "OPENAI_API_KEY",
@@ -906,6 +957,7 @@ window.__ModuleLoader__.load({
906
957
  providerOpenAI: "OpenAI / 中转站",
907
958
  providerSeedream: "字节 Seedream",
908
959
  providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
960
+ providerComfyUI: "本地 ComfyUI",
909
961
  apiKeyLabel: "{provider} API Key",
910
962
  apiKeyPlaceholder: "留空即可保留已配置的 Key",
911
963
  apiKeyHint: "安全保存为 {key};页面不会读回明文。",
@@ -916,7 +968,17 @@ window.__ModuleLoader__.load({
916
968
  endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
917
969
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
918
970
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
971
+ endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
919
972
  model: "模型",
973
+ workflow: "API Workflow",
974
+ workflowImport: "导入 JSON 文件",
975
+ workflowReplace: "替换 JSON 文件",
976
+ workflowMissing: "尚未导入工作流",
977
+ workflowImported: "已导入 {name}",
978
+ workflowHint: "请从 ComfyUI 导出 API Format JSON,并在提示词输入位置写入 {{prompt}};可用 {{seed}} 作为随机种子。",
979
+ workflowTooLarge: "工作流文件不能超过 5 MB。",
980
+ timeout: "生成超时(秒)",
981
+ timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
920
982
  saveToWorkspace: "保存到工作区",
921
983
  saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
922
984
  folder: "工作区文件夹",
@@ -946,6 +1008,7 @@ window.__ModuleLoader__.load({
946
1008
  providerOpenAI: "OpenAI / Relay",
947
1009
  providerSeedream: "ByteDance Seedream",
948
1010
  providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
1011
+ providerComfyUI: "Local ComfyUI",
949
1012
  apiKeyLabel: "{provider} API Key",
950
1013
  apiKeyPlaceholder: "Leave empty to keep configured key",
951
1014
  apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
@@ -956,7 +1019,17 @@ window.__ModuleLoader__.load({
956
1019
  endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
957
1020
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
958
1021
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
1022
+ endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
959
1023
  model: "Model",
1024
+ workflow: "API Workflow",
1025
+ workflowImport: "Import JSON file",
1026
+ workflowReplace: "Replace JSON file",
1027
+ workflowMissing: "No workflow imported",
1028
+ workflowImported: "Imported {name}",
1029
+ workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input. {{seed}} is available for a random seed.",
1030
+ workflowTooLarge: "Workflow files must be no larger than 5 MB.",
1031
+ timeout: "Generation timeout (seconds)",
1032
+ timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
960
1033
  saveToWorkspace: "Save to workspace",
961
1034
  saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
962
1035
  folder: "Workspace folder",
@@ -996,6 +1069,12 @@ window.__ModuleLoader__.load({
996
1069
  .dsh-ig-input{box-sizing:border-box;width:100%;padding:8px 12px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}
997
1070
  .dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}
998
1071
  .dsh-ig-input-group{display:flex;gap:8px;align-items:center}
1072
+ .dsh-ig-file-row{display:flex;align-items:center;gap:10px;min-width:0}
1073
+ .dsh-ig-file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;clip-path:inset(50%)}
1074
+ .dsh-ig-file-button{appearance:none;flex: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-size:13px;cursor:pointer;transition:background .15s,border-color .15s}
1075
+ .dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
1076
+ .dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}
1077
+ .dsh-ig-file-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit);font-size:12px}
999
1078
  .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}
1000
1079
  .dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
1001
1080
  .dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
@@ -1085,7 +1164,6 @@ window.__ModuleLoader__.load({
1085
1164
  ];
1086
1165
  /** Mount the settings card, generated-image card, and native conversation gallery view. */
1087
1166
  function apply(ctx) {
1088
- const { api } = ctx.get("connection");
1089
1167
  const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
1090
1168
  const locale = ctx.get("locale");
1091
1169
  ctx.effect(() => {
@@ -1098,15 +1176,27 @@ window.__ModuleLoader__.load({
1098
1176
  };
1099
1177
  }, "dsh-image-gen: styles");
1100
1178
  const register = ctx.slots.register.bind(ctx.slots);
1101
- ctx.slots.inject("settings.plugin.item", () => register({
1102
- name: "settings.plugin.item",
1103
- key: IMAGE_GENERATION_NAMESPACE,
1104
- inject: () => ({
1105
- scope,
1106
- credentials: api.credentials,
1107
- locale
1108
- })
1109
- }, ImageGenerationSettingsCard));
1179
+ const injectSettingsItem = (owner, credentials) => {
1180
+ const ownerRegister = owner.slots.register.bind(owner.slots);
1181
+ owner.slots.inject("settings.plugin.item", () => ownerRegister({
1182
+ name: "settings.plugin.item",
1183
+ key: IMAGE_GENERATION_NAMESPACE,
1184
+ inject: () => ({
1185
+ scope,
1186
+ credentials,
1187
+ locale
1188
+ })
1189
+ }, ImageGenerationSettingsCard));
1190
+ };
1191
+ const remoteCredentials = asCredentialsRemote(ctx.get("remote.credentials"));
1192
+ const legacyCredentials = credentialsFromLegacyConnection(ctx.get("connection"));
1193
+ if (remoteCredentials !== void 0) injectSettingsItem(ctx, remoteCredentials);
1194
+ else if (legacyCredentials !== void 0) injectSettingsItem(ctx, legacyCredentials);
1195
+ else ctx.inject(["remote.credentials"], (remoteCtx) => {
1196
+ const credentials = asCredentialsRemote(remoteCtx.get("remote.credentials"));
1197
+ if (credentials === void 0) throw new Error("dsh-image-gen: remote.credentials has an incompatible interface");
1198
+ injectSettingsItem(remoteCtx, credentials);
1199
+ });
1110
1200
  ctx.slots.inject("tool.call.toolview", () => register({
1111
1201
  name: "tool.call.toolview",
1112
1202
  key: "generate_image",
@@ -1127,6 +1217,36 @@ window.__ModuleLoader__.load({
1127
1217
  inject: () => ({ locale })
1128
1218
  }, GalleryViewTab));
1129
1219
  }
1220
+ function asCredentialsRemote(value) {
1221
+ if (value === null || typeof value !== "object") return void 0;
1222
+ const candidate = value;
1223
+ return typeof candidate.describe === "function" && typeof candidate.set === "function" ? candidate : void 0;
1224
+ }
1225
+ function credentialsFromLegacyConnection(value) {
1226
+ if (value === null || typeof value !== "object") return void 0;
1227
+ const credentials = value.api?.credentials;
1228
+ if (credentials === void 0 || typeof credentials.describe !== "function" || typeof credentials.set !== "function") return void 0;
1229
+ const legacy = credentials;
1230
+ return {
1231
+ async describe(refs) {
1232
+ const response = await legacy.describe({ refs });
1233
+ return response.result.ok ? {
1234
+ ok: true,
1235
+ value: response.result.value.credentials
1236
+ } : { ok: false };
1237
+ },
1238
+ async set(ref, credentialValue) {
1239
+ const response = await legacy.set({
1240
+ ref,
1241
+ value: credentialValue
1242
+ });
1243
+ return response.result.ok ? { ok: true } : {
1244
+ ok: false,
1245
+ error: response.result.error
1246
+ };
1247
+ }
1248
+ };
1249
+ }
1130
1250
  /** Edit provider settings and its write-only API credential. */
1131
1251
  function ImageGenerationSettingsCard(props) {
1132
1252
  const [open, setOpen] = (0, react.useState)(false);
@@ -1135,6 +1255,9 @@ window.__ModuleLoader__.load({
1135
1255
  const [provider, setProvider] = (0, react.useState)("google");
1136
1256
  const [model, setModel] = (0, react.useState)("");
1137
1257
  const [baseURL, setBaseURL] = (0, react.useState)("");
1258
+ const [workflowJson, setWorkflowJson] = (0, react.useState)("");
1259
+ const [workflowName, setWorkflowName] = (0, react.useState)("");
1260
+ const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
1138
1261
  const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
1139
1262
  const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
1140
1263
  const [key, setKey] = (0, react.useState)("");
@@ -1158,7 +1281,8 @@ window.__ModuleLoader__.load({
1158
1281
  google: t("providerGoogle"),
1159
1282
  openai: t("providerOpenAI"),
1160
1283
  seedream: t("providerSeedream"),
1161
- dashscope: t("providerDashScope")
1284
+ dashscope: t("providerDashScope"),
1285
+ comfyui: t("providerComfyUI")
1162
1286
  };
1163
1287
  (0, react.useEffect)(() => {
1164
1288
  const value = snapshot.value;
@@ -1166,13 +1290,21 @@ window.__ModuleLoader__.load({
1166
1290
  setProvider(next);
1167
1291
  setModel(modelOf(next, value));
1168
1292
  setBaseURL(baseURLOf(next, value));
1293
+ setWorkflowJson(value?.comfyuiWorkflowJson ?? "");
1294
+ setWorkflowName(value?.comfyuiWorkflowName ?? "");
1295
+ setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
1169
1296
  setSaveToWorkspace(value?.saveToWorkspace ?? true);
1170
1297
  setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
1171
1298
  }, [snapshot]);
1172
1299
  (0, react.useEffect)(() => {
1300
+ const keyRef = KEY_REF[provider];
1301
+ if (keyRef === void 0) {
1302
+ setConfigured(void 0);
1303
+ return;
1304
+ }
1173
1305
  let active = true;
1174
- props.credentials.describe({ refs: [KEY_REF[provider]] }).then((response) => {
1175
- if (active) setConfigured(response.result.ok ? response.result.value.credentials[KEY_REF[provider]]?.configured ?? false : void 0);
1306
+ props.credentials.describe([keyRef]).then((response) => {
1307
+ if (active) setConfigured(response.ok ? response.value?.[keyRef]?.configured ?? false : void 0);
1176
1308
  }).catch(() => {
1177
1309
  if (active) setConfigured(void 0);
1178
1310
  });
@@ -1186,16 +1318,23 @@ window.__ModuleLoader__.load({
1186
1318
  setMessage("");
1187
1319
  try {
1188
1320
  await props.scope.set("provider", provider);
1189
- await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
1190
- await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
1321
+ if (provider === "comfyui") {
1322
+ validateComfyUIWorkflowJson(workflowJson);
1323
+ await props.scope.set("comfyuiBaseURL", baseURL);
1324
+ await props.scope.set("comfyuiWorkflowJson", workflowJson);
1325
+ await props.scope.set("comfyuiWorkflowName", workflowName);
1326
+ await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
1327
+ } else {
1328
+ await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
1329
+ await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
1330
+ }
1191
1331
  await props.scope.set("saveToWorkspace", saveToWorkspace);
1192
1332
  await props.scope.set("workspaceFolder", workspaceFolder.trim());
1193
1333
  if (key.trim().length > 0) {
1194
- const response = await props.credentials.set({
1195
- ref: KEY_REF[provider],
1196
- value: key.trim()
1197
- });
1198
- if (!response.result.ok) throw new Error(response.result.error.message);
1334
+ const keyRef = KEY_REF[provider];
1335
+ if (keyRef === void 0) throw new Error("ComfyUI does not use an API key in this version");
1336
+ const response = await props.credentials.set(keyRef, key.trim());
1337
+ if (!response.ok) throw new Error(response.error?.message ?? "Failed to save API key");
1199
1338
  setKey("");
1200
1339
  setConfigured(true);
1201
1340
  }
@@ -1207,6 +1346,23 @@ window.__ModuleLoader__.load({
1207
1346
  }
1208
1347
  };
1209
1348
  const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
1349
+ const workflowStatus = workflowName.length > 0 ? t("workflowImported", { name: workflowName }) : t("workflowMissing");
1350
+ const importWorkflow = async (event) => {
1351
+ const file = event.target.files?.[0];
1352
+ event.target.value = "";
1353
+ if (file === void 0) return;
1354
+ setMessage("");
1355
+ try {
1356
+ if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
1357
+ const json = await file.text();
1358
+ validateComfyUIWorkflowJson(json);
1359
+ setWorkflowJson(json);
1360
+ setWorkflowName(file.name);
1361
+ setMessage(t("workflowImported", { name: file.name }));
1362
+ } catch (cause) {
1363
+ setMessage(cause instanceof Error ? cause.message : String(cause));
1364
+ }
1365
+ };
1210
1366
  return (0, react_jsx_runtime.jsxs)("li", {
1211
1367
  className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
1212
1368
  children: [(0, react_jsx_runtime.jsxs)("button", {
@@ -1279,6 +1435,10 @@ window.__ModuleLoader__.load({
1279
1435
  (0, react_jsx_runtime.jsx)("option", {
1280
1436
  value: "dashscope",
1281
1437
  children: t("providerDashScope")
1438
+ }),
1439
+ (0, react_jsx_runtime.jsx)("option", {
1440
+ value: "comfyui",
1441
+ children: t("providerComfyUI")
1282
1442
  })
1283
1443
  ]
1284
1444
  }),
@@ -1288,7 +1448,7 @@ window.__ModuleLoader__.load({
1288
1448
  })
1289
1449
  ]
1290
1450
  }),
1291
- (0, react_jsx_runtime.jsxs)("label", {
1451
+ provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
1292
1452
  className: "dsh-ig-field",
1293
1453
  children: [
1294
1454
  (0, react_jsx_runtime.jsx)("span", {
@@ -1307,10 +1467,10 @@ window.__ModuleLoader__.load({
1307
1467
  }),
1308
1468
  (0, react_jsx_runtime.jsx)("span", {
1309
1469
  className: "dsh-ig-hint",
1310
- children: t("apiKeyHint", { key: KEY_REF[provider] })
1470
+ children: t("apiKeyHint", { key: KEY_REF[provider] ?? "" })
1311
1471
  })
1312
1472
  ]
1313
- }),
1473
+ }) : null,
1314
1474
  (0, react_jsx_runtime.jsxs)("label", {
1315
1475
  className: "dsh-ig-field",
1316
1476
  children: [
@@ -1340,11 +1500,11 @@ window.__ModuleLoader__.load({
1340
1500
  }),
1341
1501
  (0, react_jsx_runtime.jsx)("span", {
1342
1502
  className: "dsh-ig-hint",
1343
- children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : t("endpointHintDashScope")
1503
+ children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : provider === "dashscope" ? t("endpointHintDashScope") : t("endpointHintComfyUI")
1344
1504
  })
1345
1505
  ]
1346
1506
  }),
1347
- (0, react_jsx_runtime.jsxs)("label", {
1507
+ provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
1348
1508
  className: "dsh-ig-field",
1349
1509
  children: [(0, react_jsx_runtime.jsx)("span", {
1350
1510
  className: "dsh-ig-label",
@@ -1357,7 +1517,61 @@ window.__ModuleLoader__.load({
1357
1517
  },
1358
1518
  required: true
1359
1519
  })]
1360
- }),
1520
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
1521
+ className: "dsh-ig-field",
1522
+ children: [
1523
+ (0, react_jsx_runtime.jsx)("span", {
1524
+ className: "dsh-ig-label",
1525
+ children: t("workflow")
1526
+ }),
1527
+ (0, react_jsx_runtime.jsxs)("div", {
1528
+ className: "dsh-ig-file-row",
1529
+ children: [(0, react_jsx_runtime.jsxs)("label", {
1530
+ className: "dsh-ig-file-button",
1531
+ children: [(0, react_jsx_runtime.jsx)("input", {
1532
+ className: "dsh-ig-file-input",
1533
+ type: "file",
1534
+ accept: ".json,application/json",
1535
+ onChange: (event) => {
1536
+ importWorkflow(event);
1537
+ }
1538
+ }), workflowName.length > 0 ? t("workflowReplace") : t("workflowImport")]
1539
+ }), (0, react_jsx_runtime.jsx)("span", {
1540
+ className: "dsh-ig-file-name",
1541
+ title: workflowName,
1542
+ children: workflowName || t("workflowMissing")
1543
+ })]
1544
+ }),
1545
+ (0, react_jsx_runtime.jsx)("span", {
1546
+ className: "dsh-ig-hint",
1547
+ children: t("workflowHint")
1548
+ })
1549
+ ]
1550
+ }), (0, react_jsx_runtime.jsxs)("label", {
1551
+ className: "dsh-ig-field",
1552
+ children: [
1553
+ (0, react_jsx_runtime.jsx)("span", {
1554
+ className: "dsh-ig-label",
1555
+ children: t("timeout")
1556
+ }),
1557
+ (0, react_jsx_runtime.jsx)("input", {
1558
+ className: "dsh-ig-input",
1559
+ type: "number",
1560
+ min: "1",
1561
+ max: "3600",
1562
+ step: "1",
1563
+ value: timeoutSeconds,
1564
+ onChange: (event) => {
1565
+ setTimeoutSeconds(Number(event.target.value));
1566
+ },
1567
+ required: true
1568
+ }),
1569
+ (0, react_jsx_runtime.jsx)("span", {
1570
+ className: "dsh-ig-hint",
1571
+ children: t("timeoutHint")
1572
+ })
1573
+ ]
1574
+ })] }),
1361
1575
  (0, react_jsx_runtime.jsxs)("div", {
1362
1576
  className: "dsh-ig-field",
1363
1577
  children: [(0, react_jsx_runtime.jsxs)("label", {
@@ -1403,11 +1617,11 @@ window.__ModuleLoader__.load({
1403
1617
  children: [(0, react_jsx_runtime.jsx)("p", {
1404
1618
  className: "dsh-ig-status",
1405
1619
  role: "status",
1406
- children: message || keyStatus
1620
+ children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
1407
1621
  }), (0, react_jsx_runtime.jsx)("button", {
1408
1622
  className: "dsh-ig-save",
1409
1623
  type: "submit",
1410
- disabled: saving || !snapshot.writable,
1624
+ disabled: saving || !snapshot.writable || provider === "comfyui" && workflowJson.length === 0,
1411
1625
  children: saving ? t("saving") : t("save")
1412
1626
  })]
1413
1627
  })
@@ -1656,18 +1870,13 @@ window.__ModuleLoader__.load({
1656
1870
  });
1657
1871
  }
1658
1872
  function modelOf(provider, value) {
1659
- const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : value?.dashscopeModel;
1873
+ const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : value?.comfyuiWorkflowName;
1660
1874
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
1661
1875
  }
1662
1876
  function baseURLOf(provider, value) {
1663
- const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : value?.dashscopeEndpoint;
1877
+ const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : provider === "dashscope" ? value?.dashscopeEndpoint : value?.comfyuiBaseURL;
1664
1878
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
1665
1879
  }
1666
- function imageRef(block) {
1667
- if (!("kind" in block) || block.resultView?.card !== "generic") return void 0;
1668
- const image = block.resultView.content?.find((item) => item.type === "image");
1669
- return image?.type === "image" ? image.attachment : void 0;
1670
- }
1671
1880
  /** The workspace file path a completed image call saved, when the result meta carries one. */
1672
1881
  function imageSavedTo(block) {
1673
1882
  if (!("kind" in block)) return void 0;