dsh-image-gen 0.9.2 → 0.10.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
@@ -75,7 +75,7 @@ window.__ModuleLoader__.load({
75
75
  //#region lib/types/watermark.js
76
76
  /** Model names alone do not establish the protocol of an unknown relay. */
77
77
  function officialContract(config, mode) {
78
- const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? config.openaiCompatModel;
78
+ const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? (mode === "edit" ? config.openaiCompatEditModel?.trim() || config.openaiCompatModel : config.openaiCompatModel);
79
79
  if (typeof model !== "string") return void 0;
80
80
  try {
81
81
  const url = new URL(config.openaiCompatBaseURL ?? "");
@@ -87,6 +87,10 @@ window.__ModuleLoader__.load({
87
87
  }
88
88
  /** Known official edit contracts take precedence over relay-specific format settings. */
89
89
  function compatEditFormat(config) {
90
+ try {
91
+ const url = new URL(config.openaiCompatBaseURL ?? "");
92
+ if (url.protocol === "https:" && !url.port && !url.search && !url.hash && (url.hostname === "api.siliconflow.cn" || url.hostname === "api.siliconflow.com") && url.pathname.replace(/\/+$/, "") === "/v1") return "siliconflowJson";
93
+ } catch {}
90
94
  const contract = officialContract(config, "edit");
91
95
  if (contract === "sensenova") return "jsonImageUrlArray";
92
96
  if (contract === "qwen") return "qwenJson";
@@ -156,17 +160,101 @@ window.__ModuleLoader__.load({
156
160
  "xai",
157
161
  "zhipu"
158
162
  ];
163
+ /** Normalize the relay model list while keeping the legacy default model selectable. */
164
+ function resolveOpenAICompatModels(config) {
165
+ const defaultModel = config.openaiCompatModel?.trim() ?? "";
166
+ const models = [...new Set((config.openaiCompatModels ?? []).map((model) => model.trim()).filter(Boolean))];
167
+ if (defaultModel.length > 0 && !models.includes(defaultModel)) models.unshift(defaultModel);
168
+ return models;
169
+ }
170
+ const DEFAULT_OPENAI_COMPAT_CHANNEL_ID = "default";
171
+ function openAICompatKeyRef(channelId) {
172
+ return channelId === "default" ? OPENAI_COMPAT_API_KEY_ENV : `${OPENAI_COMPAT_API_KEY_ENV}_${channelId.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}`;
173
+ }
174
+ /** The legacy flat fields remain the first channel; additional entries are fully independent. */
175
+ function resolveOpenAICompatChannels(source) {
176
+ const legacy = {
177
+ id: DEFAULT_OPENAI_COMPAT_CHANNEL_ID,
178
+ label: source.openaiCompatLabel?.trim() || "OpenAI 兼容",
179
+ keyRef: OPENAI_COMPAT_API_KEY_ENV,
180
+ baseURL: source.openaiCompatBaseURL ?? "",
181
+ model: source.openaiCompatModel ?? "",
182
+ models: [...source.openaiCompatModels ?? []],
183
+ editModel: source.openaiCompatEditModel ?? "",
184
+ quality: source.openaiCompatQuality ?? "",
185
+ qualityOptions: [...source.openaiCompatQualityOptions ?? []],
186
+ extraBody: source.openaiCompatExtraBody ?? {},
187
+ editSizeMode: source.openaiCompatEditSizeMode ?? "auto",
188
+ editFormat: source.openaiCompatEditFormat ?? "multipart",
189
+ editExtra: source.openaiCompatEditExtra ?? {},
190
+ sizes: source.openaiCompatSizes ?? {}
191
+ };
192
+ const channels = [legacy];
193
+ const seen = /* @__PURE__ */ new Set([legacy.id]);
194
+ for (const [index, entry] of (source.openaiCompatChannels ?? []).entries()) {
195
+ const id = typeof entry?.id === "string" ? entry.id.trim() : "";
196
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(id) || seen.has(id)) continue;
197
+ seen.add(id);
198
+ const storedKeyRef = typeof entry.keyRef === "string" ? entry.keyRef.trim() : "";
199
+ channels.push({
200
+ id,
201
+ label: typeof entry.label === "string" && entry.label.trim().length > 0 ? entry.label.trim() : `OpenAI 兼容 ${index + 2}`,
202
+ keyRef: /^DSH_IMAGE_GEN_OPENAI_COMPAT_KEY_[A-Z0-9_]+$/.test(storedKeyRef) ? storedKeyRef : openAICompatKeyRef(id),
203
+ baseURL: typeof entry.baseURL === "string" ? entry.baseURL : "",
204
+ model: typeof entry.model === "string" ? entry.model : "",
205
+ models: Array.isArray(entry.models) ? entry.models.filter((model) => typeof model === "string") : [],
206
+ editModel: typeof entry.editModel === "string" ? entry.editModel : "",
207
+ quality: typeof entry.quality === "string" ? entry.quality : "",
208
+ qualityOptions: Array.isArray(entry.qualityOptions) ? entry.qualityOptions.filter((quality) => typeof quality === "string") : [],
209
+ extraBody: entry.extraBody ?? {},
210
+ editSizeMode: entry.editSizeMode === "selected" ? "selected" : "auto",
211
+ editFormat: entry.editFormat ?? "multipart",
212
+ editExtra: entry.editExtra ?? {},
213
+ sizes: entry.sizes ?? {}
214
+ });
215
+ }
216
+ return channels;
217
+ }
218
+ function resolveOpenAICompatChannel(source, channelId) {
219
+ const channels = resolveOpenAICompatChannels(source);
220
+ const explicit = channelId?.trim() ?? "";
221
+ if (explicit.length > 0) {
222
+ const selected = channels.find((channel) => channel.id === explicit);
223
+ if (selected === void 0) throw new Error(`Unknown OpenAI-compatible channel "${explicit}". Available channels: ${channels.map((channel) => channel.id).join(", ")}.`);
224
+ return selected;
225
+ }
226
+ const active = source.openaiCompatActiveChannel?.trim() ?? "";
227
+ return channels.find((channel) => channel.id === active) ?? channels[0];
228
+ }
159
229
  /**
160
230
  * Providers the browser workbench can drive: the BYOK cloud set plus the
161
231
  * logged-in subscription channels. ComfyUI stays out; it has its own workflow
162
232
  * pipeline and no shared request shape.
163
233
  */
164
234
  const STUDIO_PROVIDERS = [...CLOUD_IMAGE_PROVIDERS, ...SUBSCRIPTION_PROVIDERS];
235
+ function studioTargetKey(provider, channel) {
236
+ return channel === void 0 ? provider : `${provider}:${channel}`;
237
+ }
238
+ /**
239
+ * Credential references resolved through the DSH Credentials service (BYOK).
240
+ * These are POSIX-style reference names, not environment variables: the host
241
+ * layers the process environment and its managed store behind them, so a name
242
+ * like OPENAI_API_KEY is shared with any other plugin resolving the same ref.
243
+ */
244
+ const GOOGLE_API_KEY_ENV = "GEMINI_API_KEY";
245
+ /** OpenAI Platform credential reference. */
246
+ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
247
+ /**
248
+ * OpenAI-compatible relay credential reference. Deliberately distinct from
249
+ * OPENAI_API_KEY so an official key and a relay key can coexist without
250
+ * overwriting each other.
251
+ */
252
+ const OPENAI_COMPAT_API_KEY_ENV = "DSH_IMAGE_GEN_OPENAI_COMPAT_KEY";
165
253
  /** The credential reference each cloud provider's API key is stored under. */
166
254
  const CLOUD_CREDENTIAL_REFS = {
167
- google: "GEMINI_API_KEY",
168
- openai: "OPENAI_API_KEY",
169
- "openai-compat": "DSH_IMAGE_GEN_OPENAI_COMPAT_KEY",
255
+ google: GOOGLE_API_KEY_ENV,
256
+ openai: OPENAI_API_KEY_ENV,
257
+ "openai-compat": OPENAI_COMPAT_API_KEY_ENV,
170
258
  seedream: "ARK_API_KEY",
171
259
  dashscope: "DASHSCOPE_API_KEY",
172
260
  xai: "XAI_API_KEY",
@@ -181,6 +269,20 @@ window.__ModuleLoader__.load({
181
269
  "off",
182
270
  "on"
183
271
  ];
272
+ /** A model picker/request must use the same operation-specific default. */
273
+ function studioModelForMode(profile, mode) {
274
+ return mode === "edit" ? profile.editModel ?? profile.model : profile.model;
275
+ }
276
+ function studioModelOptionsForMode(profile, mode) {
277
+ if (mode === "edit" && profile.editModel !== void 0) return [{
278
+ value: profile.editModel,
279
+ label: profile.editModel
280
+ }];
281
+ return profile.modelOptions ?? [{
282
+ value: profile.model,
283
+ label: profile.model
284
+ }];
285
+ }
184
286
  function studioWatermarkCapability(profile, mode) {
185
287
  return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
186
288
  status: "unknown",
@@ -2096,9 +2198,9 @@ window.__ModuleLoader__.load({
2096
2198
  //#endregion
2097
2199
  //#region lib/types/client/multi-model-compare.js
2098
2200
  /** Map one shared output intent to settings accepted by every target model. */
2099
- function buildComparisonTargets(profiles, selectedProviders, ratio, quality, output) {
2100
- const selected = new Set(selectedProviders);
2101
- return profiles.filter((profile) => profile.configured && selected.has(profile.provider)).map((profile) => {
2201
+ function buildComparisonTargets(profiles, selectedTargets, ratio, quality, output) {
2202
+ const selected = new Set(selectedTargets);
2203
+ return profiles.filter((profile) => profile.configured && selected.has(studioTargetKey(profile.provider, profile.channel))).map((profile) => {
2102
2204
  const targetRatio = profile.ratioOptions.some((option) => option.value === ratio) ? ratio : profile.defaultRatio;
2103
2205
  const generationOptions = generationQualityOptions(profile);
2104
2206
  const qualityIntent = output === void 0 ? quality : profile.provider === "openai" || profile.provider === "chatgpt-sub" || profile.provider === "zhipu" ? output.generationQuality : output.resolution;
@@ -2137,10 +2239,11 @@ window.__ModuleLoader__.load({
2137
2239
  return [...options.values()];
2138
2240
  }
2139
2241
  /** Start with two models, not every configured API, to avoid surprise spend. */
2140
- function initialComparisonProviders(profiles, activeProvider) {
2242
+ function initialComparisonProviders(profiles, activeProvider, activeChannel) {
2141
2243
  const configured = profiles.filter((profile) => profile.configured);
2142
- const active = configured.find((profile) => profile.provider === activeProvider);
2143
- return (active === void 0 ? configured : [active, ...configured.filter((profile) => profile.provider !== activeProvider)]).slice(0, 2).map((profile) => profile.provider);
2244
+ const active = configured.find((profile) => studioTargetKey(profile.provider, profile.channel) === studioTargetKey(activeProvider, activeChannel)) ?? configured.find((profile) => profile.provider === activeProvider);
2245
+ const activeKey = active === void 0 ? void 0 : studioTargetKey(active.provider, active.channel);
2246
+ return (active === void 0 ? configured : [active, ...configured.filter((profile) => studioTargetKey(profile.provider, profile.channel) !== activeKey)]).slice(0, 2).map((profile) => studioTargetKey(profile.provider, profile.channel));
2144
2247
  }
2145
2248
  const RATIO_SORT_ORDER = [
2146
2249
  "auto",
@@ -49149,7 +49252,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49149
49252
  should not mutate them or their content. Rather, you create new
49150
49253
  instances whenever needed. The API tries to make this easy.
49151
49254
  */
49152
- var Fragment$47 = class Fragment$47 {
49255
+ var Fragment$48 = class Fragment$48 {
49153
49256
  /**
49154
49257
  @internal
49155
49258
  */
@@ -49210,7 +49313,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49210
49313
  i = 1;
49211
49314
  }
49212
49315
  for (; i < other.content.length; i++) content.push(other.content[i]);
49213
- return new Fragment$47(content, this.size + other.size);
49316
+ return new Fragment$48(content, this.size + other.size);
49214
49317
  }
49215
49318
  /**
49216
49319
  Cut out the sub-fragment between the two given positions.
@@ -49230,15 +49333,15 @@ To remove the watermark, please purchase a license at tldraw.dev.
49230
49333
  }
49231
49334
  pos = end;
49232
49335
  }
49233
- return new Fragment$47(result, size);
49336
+ return new Fragment$48(result, size);
49234
49337
  }
49235
49338
  /**
49236
49339
  @internal
49237
49340
  */
49238
49341
  cutByIndex(from, to) {
49239
- if (from == to) return Fragment$47.empty;
49342
+ if (from == to) return Fragment$48.empty;
49240
49343
  if (from == 0 && to == this.content.length) return this;
49241
- return new Fragment$47(this.content.slice(from, to));
49344
+ return new Fragment$48(this.content.slice(from, to));
49242
49345
  }
49243
49346
  /**
49244
49347
  Create a new fragment in which the node at the given index is
@@ -49250,21 +49353,21 @@ To remove the watermark, please purchase a license at tldraw.dev.
49250
49353
  let copy = this.content.slice();
49251
49354
  let size = this.size + node.nodeSize - current.nodeSize;
49252
49355
  copy[index] = node;
49253
- return new Fragment$47(copy, size);
49356
+ return new Fragment$48(copy, size);
49254
49357
  }
49255
49358
  /**
49256
49359
  Create a new fragment by prepending the given node to this
49257
49360
  fragment.
49258
49361
  */
49259
49362
  addToStart(node) {
49260
- return new Fragment$47([node].concat(this.content), this.size + node.nodeSize);
49363
+ return new Fragment$48([node].concat(this.content), this.size + node.nodeSize);
49261
49364
  }
49262
49365
  /**
49263
49366
  Create a new fragment by appending the given node to this
49264
49367
  fragment.
49265
49368
  */
49266
49369
  addToEnd(node) {
49267
- return new Fragment$47(this.content.concat(node), this.size + node.nodeSize);
49370
+ return new Fragment$48(this.content.concat(node), this.size + node.nodeSize);
49268
49371
  }
49269
49372
  /**
49270
49373
  Compare this fragment to another one.
@@ -49374,16 +49477,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
49374
49477
  Deserialize a fragment from its JSON representation.
49375
49478
  */
49376
49479
  static fromJSON(schema, value) {
49377
- if (!value) return Fragment$47.empty;
49480
+ if (!value) return Fragment$48.empty;
49378
49481
  if (!Array.isArray(value)) throw new RangeError("Invalid input for Fragment.fromJSON");
49379
- return Fragment$47.fromArray(value.map(schema.nodeFromJSON));
49482
+ return Fragment$48.fromArray(value.map(schema.nodeFromJSON));
49380
49483
  }
49381
49484
  /**
49382
49485
  Build a fragment from an array of nodes. Ensures that adjacent
49383
49486
  text nodes with the same marks are joined together.
49384
49487
  */
49385
49488
  static fromArray(array) {
49386
- if (!array.length) return Fragment$47.empty;
49489
+ if (!array.length) return Fragment$48.empty;
49387
49490
  let joined, size = 0;
49388
49491
  for (let i = 0; i < array.length; i++) {
49389
49492
  let node = array[i];
@@ -49393,7 +49496,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49393
49496
  joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text);
49394
49497
  } else if (joined) joined.push(node);
49395
49498
  }
49396
- return new Fragment$47(joined || array, size);
49499
+ return new Fragment$48(joined || array, size);
49397
49500
  }
49398
49501
  /**
49399
49502
  Create a fragment from something that can be interpreted as a
@@ -49402,10 +49505,10 @@ To remove the watermark, please purchase a license at tldraw.dev.
49402
49505
  fragment containing those nodes.
49403
49506
  */
49404
49507
  static from(nodes) {
49405
- if (!nodes) return Fragment$47.empty;
49406
- if (nodes instanceof Fragment$47) return nodes;
49508
+ if (!nodes) return Fragment$48.empty;
49509
+ if (nodes instanceof Fragment$48) return nodes;
49407
49510
  if (Array.isArray(nodes)) return this.fromArray(nodes);
49408
- if (nodes.attrs) return new Fragment$47([nodes], nodes.nodeSize);
49511
+ if (nodes.attrs) return new Fragment$48([nodes], nodes.nodeSize);
49409
49512
  throw new RangeError("Can not convert " + nodes + " to a Fragment" + (nodes.nodesBetween ? " (looks like multiple versions of prosemirror-model were loaded)" : ""));
49410
49513
  }
49411
49514
  };
@@ -49414,7 +49517,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49414
49517
  contain anything (rather than allocating a new empty fragment for
49415
49518
  each leaf node).
49416
49519
  */
49417
- Fragment$47.empty = new Fragment$47([], 0);
49520
+ Fragment$48.empty = new Fragment$48([], 0);
49418
49521
  const found = {
49419
49522
  index: 0,
49420
49523
  offset: 0
@@ -49627,7 +49730,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49627
49730
  if (!json) return Slice.empty;
49628
49731
  let openStart = json.openStart || 0, openEnd = json.openEnd || 0;
49629
49732
  if (typeof openStart != "number" || typeof openEnd != "number") throw new RangeError("Invalid input for Slice.fromJSON");
49630
- return new Slice(Fragment$47.fromJSON(schema, json.content), openStart, openEnd);
49733
+ return new Slice(Fragment$48.fromJSON(schema, json.content), openStart, openEnd);
49631
49734
  }
49632
49735
  /**
49633
49736
  Create a slice from a fragment by taking the maximum possible
@@ -49643,7 +49746,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49643
49746
  /**
49644
49747
  The empty slice.
49645
49748
  */
49646
- Slice.empty = new Slice(Fragment$47.empty, 0, 0);
49749
+ Slice.empty = new Slice(Fragment$48.empty, 0, 0);
49647
49750
  function removeRange(content, from, to) {
49648
49751
  let { index, offset } = content.findIndex(from), child = content.maybeChild(index);
49649
49752
  let { index: indexTo, offset: offsetTo } = content.findIndex(to);
@@ -49727,19 +49830,19 @@ To remove the watermark, please purchase a license at tldraw.dev.
49727
49830
  if (openEnd) addNode(close(openEnd, replaceTwoWay($end, $to, depth + 1)), content);
49728
49831
  }
49729
49832
  addRange($to, null, depth, content);
49730
- return new Fragment$47(content);
49833
+ return new Fragment$48(content);
49731
49834
  }
49732
49835
  function replaceTwoWay($from, $to, depth) {
49733
49836
  let content = [];
49734
49837
  addRange(null, $from, depth, content);
49735
49838
  if ($from.depth > depth) addNode(close(joinable$1($from, $to, depth + 1), replaceTwoWay($from, $to, depth + 1)), content);
49736
49839
  addRange($to, null, depth, content);
49737
- return new Fragment$47(content);
49840
+ return new Fragment$48(content);
49738
49841
  }
49739
49842
  function prepareSliceForReplace(slice, $along) {
49740
49843
  let extra = $along.depth - slice.openStart;
49741
49844
  let node = $along.node(extra).copy(slice.content);
49742
- for (let i = extra - 1; i >= 0; i--) node = $along.node(i).copy(Fragment$47.from(node));
49845
+ for (let i = extra - 1; i >= 0; i--) node = $along.node(i).copy(Fragment$48.from(node));
49743
49846
  return {
49744
49847
  start: node.resolveNoCache(slice.openStart + extra),
49745
49848
  end: node.resolveNoCache(node.content.size - slice.openEnd - extra)
@@ -50079,7 +50182,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50079
50182
  this.type = type;
50080
50183
  this.attrs = attrs;
50081
50184
  this.marks = marks;
50082
- this.content = content || Fragment$47.empty;
50185
+ this.content = content || Fragment$48.empty;
50083
50186
  }
50084
50187
  /**
50085
50188
  The array of this node's child nodes.
@@ -50388,7 +50491,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50388
50491
  can optionally pass `start` and `end` indices into the
50389
50492
  replacement fragment.
50390
50493
  */
50391
- canReplace(from, to, replacement = Fragment$47.empty, start = 0, end = replacement.childCount) {
50494
+ canReplace(from, to, replacement = Fragment$48.empty, start = 0, end = replacement.childCount) {
50392
50495
  let one = this.contentMatchAt(from).matchFragment(replacement, start, end);
50393
50496
  let two = one && one.matchFragment(this.content, to);
50394
50497
  if (!two || !two.validEnd) return false;
@@ -50458,7 +50561,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50458
50561
  if (typeof json.text != "string") throw new RangeError("Invalid text node in JSON");
50459
50562
  return schema.text(json.text, marks);
50460
50563
  }
50461
- let content = Fragment$47.fromJSON(schema, json.content);
50564
+ let content = Fragment$48.fromJSON(schema, json.content);
50462
50565
  let node = schema.nodeType(json.type).create(json.attrs, content, marks);
50463
50566
  node.type.checkAttrs(node.attrs);
50464
50567
  return node;
@@ -50597,7 +50700,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50597
50700
  let seen = [this];
50598
50701
  function search(match, types) {
50599
50702
  let finished = match.matchFragment(after, startIndex);
50600
- if (finished && (!toEnd || finished.validEnd)) return Fragment$47.from(types.map((tp) => tp.createAndFill()));
50703
+ if (finished && (!toEnd || finished.validEnd)) return Fragment$48.from(types.map((tp) => tp.createAndFill()));
50601
50704
  for (let i = 0; i < match.next.length; i++) {
50602
50705
  let { type, next } = match.next[i];
50603
50706
  if (!(type.isText || type.hasRequiredAttrs()) && seen.indexOf(next) == -1) {
@@ -51037,7 +51140,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51037
51140
  */
51038
51141
  create(attrs = null, content, marks) {
51039
51142
  if (this.isText) throw new Error("NodeType.create can't construct text nodes");
51040
- return new Node$2(this, this.computeAttrs(attrs), Fragment$47.from(content), Mark$1.setFrom(marks));
51143
+ return new Node$2(this, this.computeAttrs(attrs), Fragment$48.from(content), Mark$1.setFrom(marks));
51041
51144
  }
51042
51145
  /**
51043
51146
  Like [`create`](https://prosemirror.net/docs/ref/#model.NodeType.create), but check the given content
@@ -51045,7 +51148,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51045
51148
  if it doesn't match.
51046
51149
  */
51047
51150
  createChecked(attrs = null, content, marks) {
51048
- content = Fragment$47.from(content);
51151
+ content = Fragment$48.from(content);
51049
51152
  this.checkContent(content);
51050
51153
  return new Node$2(this, this.computeAttrs(attrs), content, Mark$1.setFrom(marks));
51051
51154
  }
@@ -51059,14 +51162,14 @@ To remove the watermark, please purchase a license at tldraw.dev.
51059
51162
  */
51060
51163
  createAndFill(attrs = null, content, marks) {
51061
51164
  attrs = this.computeAttrs(attrs);
51062
- content = Fragment$47.from(content);
51165
+ content = Fragment$48.from(content);
51063
51166
  if (content.size) {
51064
51167
  let before = this.contentMatch.fillBefore(content);
51065
51168
  if (!before) return null;
51066
51169
  content = before.append(content);
51067
51170
  }
51068
51171
  let matched = this.contentMatch.matchFragment(content);
51069
- let after = matched && matched.fillBefore(Fragment$47.empty, true);
51172
+ let after = matched && matched.fillBefore(Fragment$48.empty, true);
51070
51173
  if (!after) return null;
51071
51174
  return new Node$2(this, attrs, content.append(after), Mark$1.setFrom(marks));
51072
51175
  }
@@ -51518,7 +51621,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51518
51621
  findWrapping(node) {
51519
51622
  if (!this.match) {
51520
51623
  if (!this.type) return [];
51521
- let fill = this.type.contentMatch.fillBefore(Fragment$47.from(node));
51624
+ let fill = this.type.contentMatch.fillBefore(Fragment$48.from(node));
51522
51625
  if (fill) this.match = this.type.contentMatch.matchFragment(fill);
51523
51626
  else {
51524
51627
  let start = this.type.contentMatch, wrap;
@@ -51539,8 +51642,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
51539
51642
  else this.content[this.content.length - 1] = text.withText(text.text.slice(0, text.text.length - m[0].length));
51540
51643
  }
51541
51644
  }
51542
- let content = Fragment$47.from(this.content);
51543
- if (!openEnd && this.match) content = content.append(this.match.fillBefore(Fragment$47.empty, true));
51645
+ let content = Fragment$48.from(this.content);
51646
+ if (!openEnd && this.match) content = content.append(this.match.fillBefore(Fragment$48.empty, true));
51544
51647
  return this.type ? this.type.create(this.attrs, content, this.marks) : content;
51545
51648
  }
51546
51649
  inlineContext(node) {
@@ -52467,7 +52570,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52467
52570
  if (child.isInline) child = f(child, parent, i);
52468
52571
  mapped.push(child);
52469
52572
  }
52470
- return Fragment$47.fromArray(mapped);
52573
+ return Fragment$48.fromArray(mapped);
52471
52574
  }
52472
52575
  /**
52473
52576
  Add a mark to all inline content between two positions.
@@ -52585,7 +52688,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52585
52688
  let node = doc.nodeAt(this.pos);
52586
52689
  if (!node) return StepResult.fail("No node at mark step's position");
52587
52690
  let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks));
52588
- return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$47.from(updated), 0, node.isLeaf ? 0 : 1));
52691
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$48.from(updated), 0, node.isLeaf ? 0 : 1));
52589
52692
  }
52590
52693
  invert(doc) {
52591
52694
  let node = doc.nodeAt(this.pos);
@@ -52634,7 +52737,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52634
52737
  let node = doc.nodeAt(this.pos);
52635
52738
  if (!node) return StepResult.fail("No node at mark step's position");
52636
52739
  let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks));
52637
- return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$47.from(updated), 0, node.isLeaf ? 0 : 1));
52740
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$48.from(updated), 0, node.isLeaf ? 0 : 1));
52638
52741
  }
52639
52742
  invert(doc) {
52640
52743
  let node = doc.nodeAt(this.pos);
@@ -52898,7 +53001,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52898
53001
  if (clearNewlines && child.isText && parentType.whitespace != "pre") {
52899
53002
  let m, newline = /\r?\n|\r/g, slice;
52900
53003
  while (m = newline.exec(child.text)) {
52901
- if (!slice) slice = new Slice(Fragment$47.from(parentType.schema.text(" ", parentType.allowedMarks(child.marks))), 0, 0);
53004
+ if (!slice) slice = new Slice(Fragment$48.from(parentType.schema.text(" ", parentType.allowedMarks(child.marks))), 0, 0);
52902
53005
  replSteps.push(new ReplaceStep(cur + m.index, cur + m.index + m[0].length, slice));
52903
53006
  }
52904
53007
  }
@@ -52906,7 +53009,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52906
53009
  cur = end;
52907
53010
  }
52908
53011
  if (!match.validEnd) {
52909
- let fill = match.fillBefore(Fragment$47.empty, true);
53012
+ let fill = match.fillBefore(Fragment$48.empty, true);
52910
53013
  tr.replace(cur, cur, new Slice(fill, 0, 0));
52911
53014
  }
52912
53015
  for (let i = replSteps.length - 1; i >= 0; i--) tr.step(replSteps[i]);
@@ -52935,16 +53038,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
52935
53038
  let { $from, $to, depth } = range;
52936
53039
  let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1);
52937
53040
  let start = gapStart, end = gapEnd;
52938
- let before = Fragment$47.empty, openStart = 0;
53041
+ let before = Fragment$48.empty, openStart = 0;
52939
53042
  for (let d = depth, splitting = false; d > target; d--) if (splitting || $from.index(d) > 0) {
52940
53043
  splitting = true;
52941
- before = Fragment$47.from($from.node(d).copy(before));
53044
+ before = Fragment$48.from($from.node(d).copy(before));
52942
53045
  openStart++;
52943
53046
  } else start--;
52944
- let after = Fragment$47.empty, openEnd = 0;
53047
+ let after = Fragment$48.empty, openEnd = 0;
52945
53048
  for (let d = depth, splitting = false; d > target; d--) if (splitting || $to.after(d + 1) < $to.end(d)) {
52946
53049
  splitting = true;
52947
- after = Fragment$47.from($to.node(d).copy(after));
53050
+ after = Fragment$48.from($to.node(d).copy(after));
52948
53051
  openEnd++;
52949
53052
  } else end++;
52950
53053
  tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd, new Slice(before.append(after), openStart, openEnd), before.size - openStart, true));
@@ -52990,13 +53093,13 @@ To remove the watermark, please purchase a license at tldraw.dev.
52990
53093
  return inside;
52991
53094
  }
52992
53095
  function wrap(tr, range, wrappers) {
52993
- let content = Fragment$47.empty;
53096
+ let content = Fragment$48.empty;
52994
53097
  for (let i = wrappers.length - 1; i >= 0; i--) {
52995
53098
  if (content.size) {
52996
53099
  let match = wrappers[i].type.contentMatch.matchFragment(content);
52997
53100
  if (!match || !match.validEnd) throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper");
52998
53101
  }
52999
- content = Fragment$47.from(wrappers[i].type.create(wrappers[i].attrs, content));
53102
+ content = Fragment$48.from(wrappers[i].type.create(wrappers[i].attrs, content));
53000
53103
  }
53001
53104
  let start = range.start, end = range.end;
53002
53105
  tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true));
@@ -53017,7 +53120,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53017
53120
  clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, void 0, convertNewlines === null);
53018
53121
  let mapping = tr.mapping.slice(mapFrom);
53019
53122
  let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
53020
- tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment$47.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true));
53123
+ tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment$48.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true));
53021
53124
  if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom);
53022
53125
  return false;
53023
53126
  }
@@ -53057,7 +53160,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53057
53160
  let newNode = type.create(attrs, null, marks || node.marks);
53058
53161
  if (node.isLeaf) return tr.replaceWith(pos, pos + node.nodeSize, newNode);
53059
53162
  if (!type.validContent(node.content)) throw new RangeError("Invalid content for node type " + type.name);
53060
- tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new Slice(Fragment$47.from(newNode), 0, 0), 1, true));
53163
+ tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new Slice(Fragment$48.from(newNode), 0, 0), 1, true));
53061
53164
  }
53062
53165
  /**
53063
53166
  Check whether splitting at the given position is allowed.
@@ -53080,11 +53183,11 @@ To remove the watermark, please purchase a license at tldraw.dev.
53080
53183
  return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type);
53081
53184
  }
53082
53185
  function split(tr, pos, depth = 1, typesAfter) {
53083
- let $pos = tr.doc.resolve(pos), before = Fragment$47.empty, after = Fragment$47.empty;
53186
+ let $pos = tr.doc.resolve(pos), before = Fragment$48.empty, after = Fragment$48.empty;
53084
53187
  for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
53085
- before = Fragment$47.from($pos.node(d).copy(before));
53188
+ before = Fragment$48.from($pos.node(d).copy(before));
53086
53189
  let typeAfter = typesAfter && typesAfter[i];
53087
- after = Fragment$47.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after));
53190
+ after = Fragment$48.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after));
53088
53191
  }
53089
53192
  tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true));
53090
53193
  }
@@ -53227,7 +53330,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53227
53330
  this.$to = $to;
53228
53331
  this.unplaced = unplaced;
53229
53332
  this.frontier = [];
53230
- this.placed = Fragment$47.empty;
53333
+ this.placed = Fragment$48.empty;
53231
53334
  for (let i = 0; i <= $from.depth; i++) {
53232
53335
  let node = $from.node(i);
53233
53336
  this.frontier.push({
@@ -53235,7 +53338,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53235
53338
  match: node.contentMatchAt($from.indexAfter(i))
53236
53339
  });
53237
53340
  }
53238
- for (let i = $from.depth; i > 0; i--) this.placed = Fragment$47.from($from.node(i).copy(this.placed));
53341
+ for (let i = $from.depth; i > 0; i--) this.placed = Fragment$48.from($from.node(i).copy(this.placed));
53239
53342
  }
53240
53343
  get depth() {
53241
53344
  return this.frontier.length - 1;
@@ -53280,7 +53383,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53280
53383
  let first = fragment.firstChild;
53281
53384
  for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) {
53282
53385
  let { type, match } = this.frontier[frontierDepth], wrap, inject = null;
53283
- if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(Fragment$47.from(first), false)) : parent && type.compatibleContent(parent.type))) return {
53386
+ if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(Fragment$48.from(first), false)) : parent && type.compatibleContent(parent.type))) return {
53284
53387
  sliceDepth,
53285
53388
  frontierDepth,
53286
53389
  parent,
@@ -53334,7 +53437,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53334
53437
  }
53335
53438
  let toEnd = taken == fragment.childCount;
53336
53439
  if (!toEnd) openEndCount = -1;
53337
- this.placed = addToFragment(this.placed, frontierDepth, Fragment$47.from(add));
53440
+ this.placed = addToFragment(this.placed, frontierDepth, Fragment$48.from(add));
53338
53441
  this.frontier[frontierDepth].match = match;
53339
53442
  if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1) this.closeFrontierNode();
53340
53443
  for (let i = 0, cur = fragment; i < openEndCount; i++) {
@@ -53388,14 +53491,14 @@ To remove the watermark, please purchase a license at tldraw.dev.
53388
53491
  openFrontierNode(type, attrs = null, content) {
53389
53492
  let top = this.frontier[this.depth];
53390
53493
  top.match = top.match.matchType(type);
53391
- this.placed = addToFragment(this.placed, this.depth, Fragment$47.from(type.create(attrs, content)));
53494
+ this.placed = addToFragment(this.placed, this.depth, Fragment$48.from(type.create(attrs, content)));
53392
53495
  this.frontier.push({
53393
53496
  type,
53394
53497
  match: type.contentMatch
53395
53498
  });
53396
53499
  }
53397
53500
  closeFrontierNode() {
53398
- let add = this.frontier.pop().match.fillBefore(Fragment$47.empty, true);
53501
+ let add = this.frontier.pop().match.fillBefore(Fragment$48.empty, true);
53399
53502
  if (add.childCount) this.placed = addToFragment(this.placed, this.frontier.length, add);
53400
53503
  }
53401
53504
  };
@@ -53417,7 +53520,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53417
53520
  if (openStart > 1) frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0));
53418
53521
  if (openStart > 0) {
53419
53522
  frag = node.type.contentMatch.fillBefore(frag).append(frag);
53420
- if (openEnd <= 0) frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment$47.empty, true));
53523
+ if (openEnd <= 0) frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment$48.empty, true));
53421
53524
  }
53422
53525
  return node.copy(frag);
53423
53526
  }
@@ -53500,7 +53603,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53500
53603
  if (depth > newOpen) {
53501
53604
  let match = parent.contentMatchAt(0);
53502
53605
  let start = match.fillBefore(fragment).append(fragment);
53503
- fragment = start.append(match.matchFragment(start).fillBefore(Fragment$47.empty, true));
53606
+ fragment = start.append(match.matchFragment(start).fillBefore(Fragment$48.empty, true));
53504
53607
  }
53505
53608
  return fragment;
53506
53609
  }
@@ -53509,7 +53612,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53509
53612
  let point = insertPoint(tr.doc, from, node.type);
53510
53613
  if (point != null) from = to = point;
53511
53614
  }
53512
- tr.replaceRange(from, to, new Slice(Fragment$47.from(node), 0, 0));
53615
+ tr.replaceRange(from, to, new Slice(Fragment$48.from(node), 0, 0));
53513
53616
  }
53514
53617
  function deleteRange$1(tr, from, to) {
53515
53618
  let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to);
@@ -53562,7 +53665,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53562
53665
  for (let name in node.attrs) attrs[name] = node.attrs[name];
53563
53666
  attrs[this.attr] = this.value;
53564
53667
  let updated = node.type.create(attrs, null, node.marks);
53565
- return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$47.from(updated), 0, node.isLeaf ? 0 : 1));
53668
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment$48.from(updated), 0, node.isLeaf ? 0 : 1));
53566
53669
  }
53567
53670
  getMap() {
53568
53671
  return StepMap.empty;
@@ -53745,7 +53848,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53745
53848
  fragment, node, or array of nodes.
53746
53849
  */
53747
53850
  replaceWith(from, to, content) {
53748
- return this.replace(from, to, new Slice(Fragment$47.from(content), 0, 0));
53851
+ return this.replace(from, to, new Slice(Fragment$48.from(content), 0, 0));
53749
53852
  }
53750
53853
  /**
53751
53854
  Delete the content between the given positions.
@@ -54250,7 +54353,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
54250
54353
  return new NodeSelection($pos);
54251
54354
  }
54252
54355
  content() {
54253
- return new Slice(Fragment$47.from(this.node), 0, 0);
54356
+ return new Slice(Fragment$48.from(this.node), 0, 0);
54254
54357
  }
54255
54358
  eq(other) {
54256
54359
  return other instanceof NodeSelection && other.anchor == this.anchor;
@@ -55293,9 +55396,9 @@ To remove the watermark, please purchase a license at tldraw.dev.
55293
55396
  let canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1);
55294
55397
  if (canDelAfter && (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && match.matchType(conn[0] || after.type).validEnd) {
55295
55398
  if (dispatch) {
55296
- let end = $cut.pos + after.nodeSize, wrap = Fragment$47.empty;
55297
- for (let i = conn.length - 1; i >= 0; i--) wrap = Fragment$47.from(conn[i].create(null, wrap));
55298
- wrap = Fragment$47.from(before.copy(wrap));
55399
+ let end = $cut.pos + after.nodeSize, wrap = Fragment$48.empty;
55400
+ for (let i = conn.length - 1; i >= 0; i--) wrap = Fragment$48.from(conn[i].create(null, wrap));
55401
+ wrap = Fragment$48.from(before.copy(wrap));
55299
55402
  let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true));
55300
55403
  let $joinAt = tr.doc.resolve(end + 2 * conn.length);
55301
55404
  if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type && canJoin(tr.doc, $joinAt.pos)) tr.join($joinAt.pos);
@@ -55320,8 +55423,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55320
55423
  for (; !afterText.isTextblock; afterText = afterText.firstChild) afterDepth++;
55321
55424
  if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
55322
55425
  if (dispatch) {
55323
- let end = Fragment$47.empty;
55324
- for (let i = wrap.length - 1; i >= 0; i--) end = Fragment$47.from(wrap[i].copy(end));
55426
+ let end = Fragment$48.empty;
55427
+ for (let i = wrap.length - 1; i >= 0; i--) end = Fragment$48.from(wrap[i].copy(end));
55325
55428
  dispatch(state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new Slice(end, wrap.length, 0), 0, true)).scrollIntoView());
55326
55429
  }
55327
55430
  return true;
@@ -55487,8 +55590,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55487
55590
  return true;
55488
55591
  }
55489
55592
  function doWrapInList(tr, range, wrappers, joinBefore, listType) {
55490
- let content = Fragment$47.empty;
55491
- for (let i = wrappers.length - 1; i >= 0; i--) content = Fragment$47.from(wrappers[i].type.create(wrappers[i].attrs, content));
55593
+ let content = Fragment$48.empty;
55594
+ for (let i = wrappers.length - 1; i >= 0; i--) content = Fragment$48.from(wrappers[i].type.create(wrappers[i].attrs, content));
55492
55595
  tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
55493
55596
  let found = 0;
55494
55597
  for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type == listType) found = i + 1;
@@ -55520,7 +55623,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
55520
55623
  function liftToOuterList(state, dispatch, itemType, range) {
55521
55624
  let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth);
55522
55625
  if (end < endOfList) {
55523
- tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList, new Slice(Fragment$47.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
55626
+ tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList, new Slice(Fragment$48.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
55524
55627
  range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
55525
55628
  }
55526
55629
  const target = liftTarget(range);
@@ -55541,9 +55644,9 @@ To remove the watermark, please purchase a license at tldraw.dev.
55541
55644
  if (tr.mapping.map(range.end) != range.start + $start.nodeAfter.nodeSize) return false;
55542
55645
  let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
55543
55646
  let parent = $start.node(-1), indexBefore = $start.index(-1);
55544
- if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, item.content.append(atEnd ? Fragment$47.empty : Fragment$47.from(list)))) return false;
55647
+ if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, item.content.append(atEnd ? Fragment$48.empty : Fragment$48.from(list)))) return false;
55545
55648
  let start = $start.pos, end = start + item.nodeSize;
55546
- tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment$47.empty : Fragment$47.from(list.copy(Fragment$47.empty))).append(atEnd ? Fragment$47.empty : Fragment$47.from(list.copy(Fragment$47.empty))), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
55649
+ tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment$48.empty : Fragment$48.from(list.copy(Fragment$48.empty))).append(atEnd ? Fragment$48.empty : Fragment$48.from(list.copy(Fragment$48.empty))), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
55547
55650
  dispatch(tr.scrollIntoView());
55548
55651
  return true;
55549
55652
  }
@@ -55562,8 +55665,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55562
55665
  if (nodeBefore.type != itemType) return false;
55563
55666
  if (dispatch) {
55564
55667
  let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
55565
- let inner = Fragment$47.from(nestedBefore ? itemType.create() : null);
55566
- let slice = new Slice(Fragment$47.from(itemType.create(null, Fragment$47.from(parent.type.create(null, inner)))), nestedBefore ? 3 : 1, 0);
55668
+ let inner = Fragment$48.from(nestedBefore ? itemType.create() : null);
55669
+ let slice = new Slice(Fragment$48.from(itemType.create(null, Fragment$48.from(parent.type.create(null, inner)))), nestedBefore ? 3 : 1, 0);
55567
55670
  let before = range.start, after = range.end;
55568
55671
  dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true)).scrollIntoView());
55569
55672
  }
@@ -56657,7 +56760,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
56657
56760
  if (!rule.contentElement) {
56658
56761
  let found = addedNodes && addedNodes.find((n) => n.nodeType == 1 && addedNodes.indexOf(n.parentNode) < 0 && this.dom.contains(n));
56659
56762
  if (found) rule.contentElement = found;
56660
- else rule.getContent = () => Fragment$47.empty;
56763
+ else rule.getContent = () => Fragment$48.empty;
56661
56764
  }
56662
56765
  }
56663
56766
  return rule;
@@ -57767,7 +57870,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
57767
57870
  text = f(text, inCode || plainText, view);
57768
57871
  });
57769
57872
  if (inCode) {
57770
- slice = new Slice(Fragment$47.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0);
57873
+ slice = new Slice(Fragment$48.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0);
57771
57874
  view.someProp("transformPasted", (f) => {
57772
57875
  slice = f(slice, view, true);
57773
57876
  });
@@ -57841,32 +57944,32 @@ To remove the watermark, please purchase a license at tldraw.dev.
57841
57944
  lastWrap = wrap;
57842
57945
  }
57843
57946
  });
57844
- if (result) return Fragment$47.from(result);
57947
+ if (result) return Fragment$48.from(result);
57845
57948
  }
57846
57949
  return fragment;
57847
57950
  }
57848
57951
  function withWrappers(node, wrap, from = 0) {
57849
- for (let i = wrap.length - 1; i >= from; i--) node = wrap[i].create(null, Fragment$47.from(node));
57952
+ for (let i = wrap.length - 1; i >= from; i--) node = wrap[i].create(null, Fragment$48.from(node));
57850
57953
  return node;
57851
57954
  }
57852
57955
  function addToSibling(wrap, lastWrap, node, sibling, depth) {
57853
57956
  if (depth < wrap.length && depth < lastWrap.length && wrap[depth] == lastWrap[depth]) {
57854
57957
  let inner = addToSibling(wrap, lastWrap, node, sibling.lastChild, depth + 1);
57855
57958
  if (inner) return sibling.copy(sibling.content.replaceChild(sibling.childCount - 1, inner));
57856
- if (sibling.contentMatchAt(sibling.childCount).matchType(depth == wrap.length - 1 ? node.type : wrap[depth + 1])) return sibling.copy(sibling.content.append(Fragment$47.from(withWrappers(node, wrap, depth + 1))));
57959
+ if (sibling.contentMatchAt(sibling.childCount).matchType(depth == wrap.length - 1 ? node.type : wrap[depth + 1])) return sibling.copy(sibling.content.append(Fragment$48.from(withWrappers(node, wrap, depth + 1))));
57857
57960
  }
57858
57961
  }
57859
57962
  function closeRight(node, depth) {
57860
57963
  if (depth == 0) return node;
57861
57964
  let fragment = node.content.replaceChild(node.childCount - 1, closeRight(node.lastChild, depth - 1));
57862
- let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment$47.empty, true);
57965
+ let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment$48.empty, true);
57863
57966
  return node.copy(fragment.append(fill));
57864
57967
  }
57865
57968
  function closeRange(fragment, side, from, to, depth, openEnd) {
57866
57969
  let node = side < 0 ? fragment.firstChild : fragment.lastChild, inner = node.content;
57867
57970
  if (fragment.childCount > 1) openEnd = 0;
57868
57971
  if (depth < to - 1) inner = closeRange(inner, side, from, to, depth + 1, openEnd);
57869
- if (depth >= from) inner = side < 0 ? node.contentMatchAt(0).fillBefore(inner, openEnd <= depth).append(inner) : inner.append(node.contentMatchAt(node.childCount).fillBefore(Fragment$47.empty, true));
57972
+ if (depth >= from) inner = side < 0 ? node.contentMatchAt(0).fillBefore(inner, openEnd <= depth).append(inner) : inner.append(node.contentMatchAt(node.childCount).fillBefore(Fragment$48.empty, true));
57870
57973
  return fragment.replaceChild(side < 0 ? 0 : fragment.childCount - 1, node.copy(inner));
57871
57974
  }
57872
57975
  function closeSlice(slice, openStart, openEnd) {
@@ -57952,7 +58055,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
57952
58055
  } catch (e) {
57953
58056
  break;
57954
58057
  }
57955
- content = Fragment$47.from(type.create(array[i + 1], content));
58058
+ content = Fragment$48.from(type.create(array[i + 1], content));
57956
58059
  openStart++;
57957
58060
  openEnd++;
57958
58061
  }
@@ -59678,7 +59781,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
59678
59781
  } else return null;
59679
59782
  let updated = [];
59680
59783
  for (let i = 0; i < prev.childCount; i++) updated.push(update(prev.child(i)));
59681
- if (Fragment$47.from(updated).eq(cur)) return {
59784
+ if (Fragment$48.from(updated).eq(cur)) return {
59682
59785
  mark,
59683
59786
  type
59684
59787
  };
@@ -60948,7 +61051,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
60948
61051
  };
60949
61052
  const isTextContent = typeof content === "string";
60950
61053
  if (isJSONContent) try {
60951
- if (Array.isArray(content) && content.length > 0) return Fragment$47.fromArray(content.map((item) => schema.nodeFromJSON(item)));
61054
+ if (Array.isArray(content) && content.length > 0) return Fragment$48.fromArray(content.map((item) => schema.nodeFromJSON(item)));
60952
61055
  const node = schema.nodeFromJSON(content);
60953
61056
  if (options.errorOnInvalidContent) node.check();
60954
61057
  return node;
@@ -61085,7 +61188,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
61085
61188
  else newContent = value;
61086
61189
  tr.insertText(newContent, from, to);
61087
61190
  } else {
61088
- newContent = Fragment$47.from(nodes);
61191
+ newContent = Fragment$48.from(nodes);
61089
61192
  const $from = tr.doc.resolve(from);
61090
61193
  const $fromNode = $from.node();
61091
61194
  const fromSelectionAtStart = $from.parentOffset === 0;
@@ -62501,16 +62604,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
62501
62604
  if ($from.depth === 2 || $from.node(-3).type !== type || $from.index(-2) !== $from.node(-2).childCount - 1) return false;
62502
62605
  if (dispatch) {
62503
62606
  var _type$contentMatch$de;
62504
- let wrap = Fragment$47.empty;
62607
+ let wrap = Fragment$48.empty;
62505
62608
  const depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
62506
- for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d -= 1) wrap = Fragment$47.from($from.node(d).copy(wrap));
62609
+ for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d -= 1) wrap = Fragment$48.from($from.node(d).copy(wrap));
62507
62610
  const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1 : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
62508
62611
  const newNextTypeAttributes = {
62509
62612
  ...getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs),
62510
62613
  ...overrideAttrs
62511
62614
  };
62512
62615
  const nextType = ((_type$contentMatch$de = type.contentMatch.defaultType) === null || _type$contentMatch$de === void 0 ? void 0 : _type$contentMatch$de.createAndFill(newNextTypeAttributes)) || void 0;
62513
- wrap = wrap.append(Fragment$47.from(type.createAndFill(null, nextType) || void 0));
62616
+ wrap = wrap.append(Fragment$48.from(type.createAndFill(null, nextType) || void 0));
62514
62617
  const start = $from.before($from.depth - (depthBefore - 1));
62515
62618
  tr.replace(start, $from.after(-depthAfter), new Slice(wrap, 4 - depthBefore, 0));
62516
62619
  let sel = -1;
@@ -63904,7 +64007,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
63904
64007
  if (!!simulatedInputMeta) setTimeout(() => {
63905
64008
  let { text } = simulatedInputMeta;
63906
64009
  if (typeof text === "string") text = text;
63907
- else text = getHTMLFromFragment(Fragment$47.from(text), state.schema);
64010
+ else text = getHTMLFromFragment(Fragment$48.from(text), state.schema);
63908
64011
  const { from } = simulatedInputMeta;
63909
64012
  const to = from + text.length;
63910
64013
  run$1({
@@ -64209,7 +64312,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
64209
64312
  if (isSimulatedPaste) {
64210
64313
  let { text } = simulatedPasteMeta;
64211
64314
  if (typeof text === "string") text = text;
64212
- else text = getHTMLFromFragment(Fragment$47.from(text), state.schema);
64315
+ else text = getHTMLFromFragment(Fragment$48.from(text), state.schema);
64213
64316
  const { from } = simulatedPasteMeta;
64214
64317
  const to = from + text.length;
64215
64318
  const pasteEvt = createClipboardPasteEvent(text);
@@ -64941,7 +65044,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
64941
65044
  } catch {
64942
65045
  return;
64943
65046
  }
64944
- if (content instanceof Fragment$47) return;
65047
+ if (content instanceof Fragment$48) return;
64945
65048
  console.warn("[tiptap warn]: prosemirror-model is loaded more than once. Wrapping and splitting nodes will fail. Deduplicate it in your lock file, or alias it to a single copy in your bundler.");
64946
65049
  }
64947
65050
  var NodePos = class NodePos {
@@ -71713,7 +71816,7 @@ img.ProseMirror-separator {
71713
71816
  const tr = state.tr;
71714
71817
  tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize);
71715
71818
  const mappedInsertPos = tr.mapping.map(insertPos);
71716
- tr.insert(mappedInsertPos, Fragment$47.from(items));
71819
+ tr.insert(mappedInsertPos, Fragment$48.from(items));
71717
71820
  tr.setSelection(selection.map(tr.doc, tr.mapping));
71718
71821
  if (dispatch) dispatch(tr);
71719
71822
  return true;
@@ -72126,13 +72229,13 @@ img.ProseMirror-separator {
72126
72229
  const lastItem = previous.lastChild;
72127
72230
  if (!lastItem || lastItem.type.name !== name) return false;
72128
72231
  const block = $from.parent;
72129
- if (!lastItem.canReplace(lastItem.childCount, lastItem.childCount, Fragment$47.from(block))) return false;
72232
+ if (!lastItem.canReplace(lastItem.childCount, lastItem.childCount, Fragment$48.from(block))) return false;
72130
72233
  const blockStart = $from.before();
72131
72234
  const blockEnd = $from.after();
72132
72235
  const insideLastItemEnd = blockStart - 2;
72133
72236
  return editor.commands.command(({ tr, dispatch }) => {
72134
72237
  if (dispatch) {
72135
- tr.delete(blockStart, blockEnd).insert(insideLastItemEnd, Fragment$47.from(block));
72238
+ tr.delete(blockStart, blockEnd).insert(insideLastItemEnd, Fragment$48.from(block));
72136
72239
  tr.setSelection(TextSelection.create(tr.doc, insideLastItemEnd + 1));
72137
72240
  tr.scrollIntoView();
72138
72241
  }
@@ -73509,8 +73612,8 @@ img.ProseMirror-separator {
73509
73612
  let { $from } = view.state.selection;
73510
73613
  let insert = $from.parent.contentMatchAt($from.index()).findWrapping(view.state.schema.nodes.text);
73511
73614
  if (!insert) return false;
73512
- let frag = Fragment$47.empty;
73513
- for (let i = insert.length - 1; i >= 0; i--) frag = Fragment$47.from(insert[i].createAndFill(null, frag));
73615
+ let frag = Fragment$48.empty;
73616
+ for (let i = insert.length - 1; i >= 0; i--) frag = Fragment$48.from(insert[i].createAndFill(null, frag));
73514
73617
  let tr = view.state.tr.replace($from.pos, $from.pos, new Slice(frag, 0, 0));
73515
73618
  tr.setSelection(TextSelection.near(tr.doc.resolve($from.pos + 1)));
73516
73619
  view.dispatch(tr);
@@ -117520,6 +117623,7 @@ img.ProseMirror-separator {
117520
117623
  const [selected, setSelected] = (0, react.useState)(null);
117521
117624
  const [mode, setMode] = (0, react.useState)("generate");
117522
117625
  const [provider, setProvider] = (0, react.useState)("google");
117626
+ const [channel, setChannel] = (0, react.useState)(void 0);
117523
117627
  const [model, setModel] = (0, react.useState)("");
117524
117628
  const [ratio, setRatio] = (0, react.useState)("1:1");
117525
117629
  const [quality, setQuality] = (0, react.useState)("1K");
@@ -117533,7 +117637,7 @@ img.ProseMirror-separator {
117533
117637
  const [selectedBatchIds, setSelectedBatchIds] = (0, react.useState)([]);
117534
117638
  const [batchKind, setBatchKind] = (0, react.useState)(null);
117535
117639
  const [comparisonEnabled, setComparisonEnabled] = (0, react.useState)(false);
117536
- const [comparisonProviders, setComparisonProviders] = (0, react.useState)([]);
117640
+ const [comparisonProviderKeys, setComparisonProviderKeys] = (0, react.useState)([]);
117537
117641
  const [isGenerating, setIsGenerating] = (0, react.useState)(false);
117538
117642
  const [error, setError] = (0, react.useState)(null);
117539
117643
  const [notice, setNotice] = (0, react.useState)(null);
@@ -117608,7 +117712,7 @@ img.ProseMirror-separator {
117608
117712
  observer.disconnect();
117609
117713
  };
117610
117714
  }, []);
117611
- const maxReferences = comparisonEnabled ? comparisonProviders.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
117715
+ const maxReferences = comparisonEnabled ? comparisonProviderKeys.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
117612
117716
  const referencesRef = (0, react.useRef)(references);
117613
117717
  referencesRef.current = references;
117614
117718
  const t = (key, values) => {
@@ -117628,10 +117732,12 @@ img.ProseMirror-separator {
117628
117732
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
117629
117733
  const formStateRef = (0, react.useRef)({
117630
117734
  provider,
117735
+ channel,
117631
117736
  model
117632
117737
  });
117633
117738
  formStateRef.current = {
117634
117739
  provider,
117740
+ channel,
117635
117741
  model
117636
117742
  };
117637
117743
  const loadConfig = (0, react.useCallback)(async (options) => {
@@ -117650,16 +117756,19 @@ img.ProseMirror-separator {
117650
117756
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
117651
117757
  setConfig(payload);
117652
117758
  if (silent) {
117653
- const { provider: currentProvider, model: currentModel } = formStateRef.current;
117654
- const activeProfile = payload.providers.find((item) => item.provider === currentProvider);
117655
- if (activeProfile !== void 0 && activeProfile.model !== currentModel) setModel(activeProfile.model);
117656
- setComparisonProviders((current) => current.filter((selectedProvider) => payload.providers.some((item) => item.provider === selectedProvider && item.configured)));
117759
+ const { provider: currentProvider, channel: currentChannel, model: currentModel } = formStateRef.current;
117760
+ const activeProfile = payload.providers.find((item) => item.provider === currentProvider && item.channel === currentChannel);
117761
+ if (activeProfile === void 0) {
117762
+ const fallback = payload.providers.find((item) => item.provider === payload.activeProvider && item.channel === payload.activeChannel) ?? payload.providers[0];
117763
+ if (fallback !== void 0) applyProvider(fallback);
117764
+ } else if (!studioModelOptionsForMode(activeProfile, "generate").some((option) => option.value === currentModel)) setModel(activeProfile.model);
117765
+ setComparisonProviderKeys((current) => current.filter((selectedKey) => payload.providers.some((item) => studioTargetKey(item.provider, item.channel) === selectedKey && item.configured)));
117657
117766
  return;
117658
117767
  }
117659
- const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
117768
+ const initial = payload.providers.find((item) => item.provider === payload.activeProvider && item.channel === payload.activeChannel) ?? payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
117660
117769
  if (initial !== void 0) {
117661
117770
  applyProvider(initial);
117662
- setComparisonProviders(initialComparisonProviders(payload.providers, initial.provider));
117771
+ setComparisonProviderKeys(initialComparisonProviders(payload.providers, initial.provider, initial.channel));
117663
117772
  }
117664
117773
  } catch (fetchError) {
117665
117774
  if (!silent && !controller.signal.aborted) setConfigError(messageOf(fetchError));
@@ -117761,13 +117870,18 @@ img.ProseMirror-separator {
117761
117870
  window.removeEventListener("mouseup", handleMouseUp);
117762
117871
  };
117763
117872
  }, []);
117764
- const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
117873
+ const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider && item.channel === channel), [
117874
+ config,
117875
+ provider,
117876
+ channel
117877
+ ]);
117878
+ const requestModel = mode === "edit" && activeProfile?.editModel ? studioModelForMode(activeProfile, mode) : model;
117765
117879
  const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
117766
117880
  const apiConfiguredCount = config?.providers.filter((item) => item.configured && !isSubscriptionProvider(item.provider)).length ?? 0;
117767
117881
  const subscriptionSignedInCount = config?.providers.filter((item) => item.configured && isSubscriptionProvider(item.provider)).length ?? 0;
117768
117882
  const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
117769
117883
  const comparisonProfiles = (0, react.useMemo)(() => (config?.providers ?? []).filter((item) => item.configured && (mode === "generate" || item.supportsEditing)), [config, mode]);
117770
- const selectedComparisonProfiles = (0, react.useMemo)(() => comparisonProfiles.filter((profile) => comparisonProviders.includes(profile.provider)), [comparisonProfiles, comparisonProviders]);
117884
+ const selectedComparisonProfiles = (0, react.useMemo)(() => comparisonProfiles.filter((profile) => comparisonProviderKeys.includes(studioTargetKey(profile.provider, profile.channel))), [comparisonProfiles, comparisonProviderKeys]);
117771
117885
  const comparisonResolutionOpts = (0, react.useMemo)(() => comparisonOutputOptions(selectedComparisonProfiles, ratio, "resolution"), [selectedComparisonProfiles, ratio]);
117772
117886
  const comparisonGenerationQualityOpts = (0, react.useMemo)(() => localizeQualityOptions(comparisonOutputOptions(selectedComparisonProfiles, ratio, "generationQuality"), lang), [
117773
117887
  selectedComparisonProfiles,
@@ -117776,12 +117890,12 @@ img.ProseMirror-separator {
117776
117890
  ]);
117777
117891
  const comparisonResolution = comparisonResolutionOpts.some((option) => option.value === resolution) ? resolution : comparisonResolutionOpts[0]?.value ?? "standard";
117778
117892
  const comparisonGenerationQuality = comparisonGenerationQualityOpts.some((option) => option.value === generationQuality) ? generationQuality : comparisonGenerationQualityOpts[0]?.value ?? "__default__";
117779
- const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality, {
117893
+ const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviderKeys, ratio, quality, {
117780
117894
  resolution: comparisonResolution,
117781
117895
  generationQuality: comparisonGenerationQuality
117782
117896
  }), [
117783
117897
  comparisonProfiles,
117784
- comparisonProviders,
117898
+ comparisonProviderKeys,
117785
117899
  ratio,
117786
117900
  quality,
117787
117901
  comparisonResolution,
@@ -117798,6 +117912,7 @@ img.ProseMirror-separator {
117798
117912
  const comparisonRatioOpts = (0, react.useMemo)(() => localizeRatioOptions(comparisonOptionUnion(comparisonProfiles, "ratioOptions"), lang), [comparisonProfiles, lang]);
117799
117913
  const applyProvider = (profile) => {
117800
117914
  setProvider(profile.provider);
117915
+ setChannel(profile.channel);
117801
117916
  setModel(profile.model);
117802
117917
  setRatio(profile.defaultRatio);
117803
117918
  setQuality(profile.defaultQuality);
@@ -117806,8 +117921,8 @@ img.ProseMirror-separator {
117806
117921
  setWatermark("default");
117807
117922
  setError(null);
117808
117923
  };
117809
- const changeProvider = (value) => {
117810
- const profile = config?.providers.find((item) => item.provider === value);
117924
+ const changeProvider = (value, channelId) => {
117925
+ const profile = config?.providers.find((item) => item.provider === value && (channelId === void 0 || item.channel === channelId));
117811
117926
  if (profile !== void 0) {
117812
117927
  applyProvider(profile);
117813
117928
  const providerMax = value === "dashscope" ? 3 : 5;
@@ -117821,6 +117936,10 @@ img.ProseMirror-separator {
117821
117936
  }
117822
117937
  }
117823
117938
  };
117939
+ const changeProfile = (value) => {
117940
+ const profile = config?.providers.find((item) => studioTargetKey(item.provider, item.channel) === value);
117941
+ if (profile !== void 0) changeProvider(profile.provider, profile.channel);
117942
+ };
117824
117943
  const resetFit = () => {
117825
117944
  setFit(true);
117826
117945
  setZoom(100);
@@ -117847,9 +117966,10 @@ img.ProseMirror-separator {
117847
117966
  setMode("generate");
117848
117967
  setComparisonEnabled(false);
117849
117968
  setPrompt(item.prompt);
117850
- const profile = config?.providers.find((entry) => entry.provider === item.provider);
117969
+ const profile = config?.providers.find((entry) => entry.provider === item.provider && (item.channel === void 0 || entry.channel === item.channel));
117851
117970
  if (!profile) return;
117852
117971
  applyProvider(profile);
117972
+ if (studioModelOptionsForMode(profile, "generate").some((option) => option.value === item.model)) setModel(item.model);
117853
117973
  const nextRatio = profile.ratioOptions.some((option) => option.value === item.aspectRatio) ? item.aspectRatio : profile.defaultRatio;
117854
117974
  setRatio(nextRatio);
117855
117975
  if (profile.qualityOptions.some((option) => option.value === item.imageSize)) {
@@ -117998,7 +118118,7 @@ img.ProseMirror-separator {
117998
118118
  const targetMax = targetItem.provider === "dashscope" ? 3 : 5;
117999
118119
  if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
118000
118120
  setMode("edit");
118001
- changeProvider(targetItem.provider);
118121
+ changeProvider(targetItem.provider, targetItem.channel);
118002
118122
  return;
118003
118123
  }
118004
118124
  if (referencesRef.current.length >= targetMax) {
@@ -118009,7 +118129,7 @@ img.ProseMirror-separator {
118009
118129
  const blob = image.blob ?? await fetchAttachmentBlob(targetItem.attachment);
118010
118130
  if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
118011
118131
  setMode("edit");
118012
- changeProvider(targetItem.provider);
118132
+ changeProvider(targetItem.provider, targetItem.channel);
118013
118133
  return;
118014
118134
  }
118015
118135
  if (referencesRef.current.length >= targetMax) {
@@ -118027,7 +118147,7 @@ img.ProseMirror-separator {
118027
118147
  referencesRef.current = nextList;
118028
118148
  setError(null);
118029
118149
  setMode("edit");
118030
- changeProvider(targetItem.provider);
118150
+ changeProvider(targetItem.provider, targetItem.channel);
118031
118151
  } catch {
118032
118152
  setError(t("imageLoadFailed"));
118033
118153
  }
@@ -118050,8 +118170,8 @@ img.ProseMirror-separator {
118050
118170
  return;
118051
118171
  }
118052
118172
  if (config === null) return;
118053
- const initial = initialComparisonProviders(comparisonProfiles, provider).filter((item) => mode !== "edit" || referencesRef.current.length <= 3 || item !== "dashscope");
118054
- setComparisonProviders(initial);
118173
+ const initial = initialComparisonProviders(comparisonProfiles, provider, channel).filter((item) => mode !== "edit" || referencesRef.current.length <= 3 || item !== "dashscope");
118174
+ setComparisonProviderKeys(initial);
118055
118175
  if (![
118056
118176
  "1:1",
118057
118177
  "3:2",
@@ -118066,12 +118186,12 @@ img.ProseMirror-separator {
118066
118186
  "4K"
118067
118187
  ].includes(quality)) setQuality("1K");
118068
118188
  };
118069
- const toggleComparisonProvider = (target) => {
118070
- if (mode === "edit" && target === "dashscope" && referencesRef.current.length > 3 && !comparisonProviders.includes(target)) {
118189
+ const toggleComparisonProvider = (targetKey) => {
118190
+ if (mode === "edit" && targetKey === "dashscope" && referencesRef.current.length > 3 && !comparisonProviderKeys.includes(targetKey)) {
118071
118191
  setError(t("maxReferencesExceeded", { max: "3" }));
118072
118192
  return;
118073
118193
  }
118074
- setComparisonProviders((current) => current.includes(target) ? current.filter((item) => item !== target) : [...current, target]);
118194
+ setComparisonProviderKeys((current) => current.includes(targetKey) ? current.filter((item) => item !== targetKey) : [...current, targetKey]);
118075
118195
  setError(null);
118076
118196
  };
118077
118197
  const submit = async () => {
@@ -118105,7 +118225,8 @@ img.ProseMirror-separator {
118105
118225
  body: JSON.stringify({
118106
118226
  mode,
118107
118227
  provider: target.profile.provider,
118108
- model: target.profile.model,
118228
+ ...target.profile.channel === void 0 ? {} : { channel: target.profile.channel },
118229
+ model: studioModelForMode(target.profile, mode),
118109
118230
  prompt: prompt.trim(),
118110
118231
  ratio: target.ratio,
118111
118232
  quality: target.quality,
@@ -118135,6 +118256,7 @@ img.ProseMirror-separator {
118135
118256
  attachment: value.payload.attachment,
118136
118257
  prompt: value.payload.prompt,
118137
118258
  provider: value.payload.provider,
118259
+ ...value.payload.channel === void 0 ? {} : { channel: value.payload.channel },
118138
118260
  model: value.payload.model,
118139
118261
  createdAt: value.payload.createdAt + idx,
118140
118262
  aspectRatio: value.target.ratio,
@@ -118174,10 +118296,11 @@ img.ProseMirror-separator {
118174
118296
  body: JSON.stringify({
118175
118297
  mode,
118176
118298
  provider,
118177
- model,
118299
+ model: requestModel,
118178
118300
  prompt: prompt.trim(),
118179
118301
  ratio,
118180
118302
  quality: selectedQuality,
118303
+ ...activeProfile.channel === void 0 ? {} : { channel: activeProfile.channel },
118181
118304
  ...selectedResolution === void 0 ? {} : { resolution: selectedResolution },
118182
118305
  ...provider === "openai-compat" ? { generationQuality: selectedGenerationQuality } : {},
118183
118306
  ...watermarkCapability.status === "supported" ? { watermark } : {},
@@ -118198,6 +118321,7 @@ img.ProseMirror-separator {
118198
118321
  attachment: gen.attachment,
118199
118322
  prompt: payload.prompt,
118200
118323
  provider: payload.provider,
118324
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
118201
118325
  model: payload.model,
118202
118326
  createdAt: payload.createdAt + idx,
118203
118327
  aspectRatio: ratio,
@@ -118759,7 +118883,7 @@ img.ProseMirror-separator {
118759
118883
  className: p.configured ? "is-configured" : "is-missing",
118760
118884
  children: [(0, react_jsx_runtime.jsxs)("div", {
118761
118885
  className: "dsh-ig-status-item-name",
118762
- children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
118886
+ children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: studioModelForMode(p, mode) })]
118763
118887
  }), (0, react_jsx_runtime.jsx)("span", {
118764
118888
  className: "dsh-ig-status-item-tag",
118765
118889
  children: isSubscriptionProvider(p.provider) ? p.configured ? t("subSignedIn") : t("subSignedOut") : p.configured ? t("configured") : t("unconfigured")
@@ -118883,7 +119007,7 @@ img.ProseMirror-separator {
118883
119007
  children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 28 })
118884
119008
  }),
118885
119009
  (0, react_jsx_runtime.jsx)("strong", { children: comparisonEnabled ? t("comparing", { count: String(comparisonTargets.length) }) : count > 1 ? t("generatingCount", { count: String(count) }) : t("generating") }),
118886
- (0, react_jsx_runtime.jsx)("span", { children: comparisonEnabled ? comparisonTargets.map((target) => target.profile.label).join(" · ") : `${activeProfile?.label ?? ""} · ${model}` })
119010
+ (0, react_jsx_runtime.jsx)("span", { children: comparisonEnabled ? comparisonTargets.map((target) => target.profile.label).join(" · ") : `${activeProfile?.label ?? ""} · ${requestModel}` })
118887
119011
  ]
118888
119012
  }) : currentBatch && currentBatch.length > 1 ? (0, react_jsx_runtime.jsx)("div", {
118889
119013
  className: "dsh-ig-canvas-cluster",
@@ -119183,12 +119307,13 @@ img.ProseMirror-separator {
119183
119307
  (0, react_jsx_runtime.jsx)("div", {
119184
119308
  className: "dsh-ig-model-checks",
119185
119309
  children: comparisonProfiles.map((item) => {
119186
- const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
119310
+ const targetKey = studioTargetKey(item.provider, item.channel);
119311
+ const target = comparisonTargets.find((candidate) => studioTargetKey(candidate.profile.provider, candidate.profile.channel) === targetKey);
119187
119312
  return (0, react_jsx_runtime.jsxs)("button", {
119188
119313
  type: "button",
119189
119314
  className: target ? "is-selected" : "",
119190
119315
  "aria-pressed": Boolean(target),
119191
- onClick: () => toggleComparisonProvider(item.provider),
119316
+ onClick: () => toggleComparisonProvider(targetKey),
119192
119317
  children: [
119193
119318
  (0, react_jsx_runtime.jsx)("span", {
119194
119319
  className: "dsh-ig-model-checkmark",
@@ -119197,8 +119322,8 @@ img.ProseMirror-separator {
119197
119322
  (0, react_jsx_runtime.jsxs)("span", {
119198
119323
  className: "dsh-ig-model-copy",
119199
119324
  children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
119200
- title: item.model,
119201
- children: item.model
119325
+ title: studioModelForMode(item, mode),
119326
+ children: studioModelForMode(item, mode)
119202
119327
  })]
119203
119328
  }),
119204
119329
  target && (0, react_jsx_runtime.jsxs)("span", {
@@ -119211,7 +119336,7 @@ img.ProseMirror-separator {
119211
119336
  ].filter(Boolean).join(" · "), target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null]
119212
119337
  })
119213
119338
  ]
119214
- }, item.provider);
119339
+ }, targetKey);
119215
119340
  })
119216
119341
  }),
119217
119342
  (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
@@ -119259,20 +119384,17 @@ img.ProseMirror-separator {
119259
119384
  className: "dsh-ig-field-grid",
119260
119385
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
119261
119386
  label: t("provider"),
119262
- value: provider,
119263
- onChange: changeProvider,
119387
+ value: studioTargetKey(provider, channel),
119388
+ onChange: changeProfile,
119264
119389
  options: config.providers.map((item) => ({
119265
- value: item.provider,
119390
+ value: studioTargetKey(item.provider, item.channel),
119266
119391
  label: `${item.label}${item.configured ? "" : ` · ${t("unconfigured")}`}`
119267
119392
  }))
119268
119393
  }), (0, react_jsx_runtime.jsx)(FieldSelect, {
119269
119394
  label: t("model"),
119270
- value: model,
119395
+ value: requestModel,
119271
119396
  onChange: setModel,
119272
- options: activeProfile === void 0 ? [] : [{
119273
- value: activeProfile.model,
119274
- label: activeProfile.model
119275
- }]
119397
+ options: activeProfile === void 0 ? [] : studioModelOptionsForMode(activeProfile, mode)
119276
119398
  })]
119277
119399
  }),
119278
119400
  (0, react_jsx_runtime.jsxs)("div", {
@@ -122277,6 +122399,7 @@ img.ProseMirror-separator {
122277
122399
  return {
122278
122400
  mode: "generate",
122279
122401
  provider,
122402
+ ...provider === "openai-compat" && image.channel !== void 0 ? { channel: image.channel } : {},
122280
122403
  model: image.model,
122281
122404
  prompt: prompt.trim(),
122282
122405
  ratio: settings.ratio,
@@ -123027,6 +123150,7 @@ img.ProseMirror-separator {
123027
123150
  attachment: payload.attachment,
123028
123151
  prompt: payload.prompt,
123029
123152
  provider: payload.provider,
123153
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
123030
123154
  model: payload.model,
123031
123155
  createdAt: payload.createdAt,
123032
123156
  aspectRatio: request.ratio,
@@ -124294,6 +124418,7 @@ img.ProseMirror-separator {
124294
124418
  attachment,
124295
124419
  prompt: stringValue(meta.prompt, "Generated Image"),
124296
124420
  provider: stringValue(meta.provider, "google"),
124421
+ ...typeof meta.channel === "string" ? { channel: meta.channel } : {},
124297
124422
  model: stringValue(meta.model, ""),
124298
124423
  output: stringValue(meta.output, ""),
124299
124424
  ...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
@@ -124960,7 +125085,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124960
125085
  function isRevision(value) {
124961
125086
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
124962
125087
  const candidate = value;
124963
- return isAttachment(candidate.attachment) && typeof candidate.provider === "string" && STUDIO_PROVIDERS.includes(candidate.provider) && typeof candidate.prompt === "string" && typeof candidate.model === "string" && typeof candidate.output === "string" && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && typeof candidate.ratio === "string" && typeof candidate.quality === "string" && (candidate.resolution === void 0 || typeof candidate.resolution === "string") && (candidate.generationQuality === void 0 || typeof candidate.generationQuality === "string") && (candidate.watermark === void 0 || STUDIO_WATERMARK_VALUES.some((value) => value === candidate.watermark));
125088
+ return isAttachment(candidate.attachment) && typeof candidate.provider === "string" && STUDIO_PROVIDERS.includes(candidate.provider) && (candidate.channel === void 0 || typeof candidate.channel === "string") && typeof candidate.prompt === "string" && typeof candidate.model === "string" && typeof candidate.output === "string" && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && typeof candidate.ratio === "string" && typeof candidate.quality === "string" && (candidate.resolution === void 0 || typeof candidate.resolution === "string") && (candidate.generationQuality === void 0 || typeof candidate.generationQuality === "string") && (candidate.watermark === void 0 || STUDIO_WATERMARK_VALUES.some((value) => value === candidate.watermark));
124964
125089
  }
124965
125090
  function isAttachment(value) {
124966
125091
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -125087,16 +125212,51 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125087
125212
  function pillVisible(value) {
125088
125213
  return value?.showProviderPill === true;
125089
125214
  }
125215
+ const PILL_MODEL_FIELDS = {
125216
+ google: "googleModel",
125217
+ openai: "openaiModel",
125218
+ "openai-compat": "openaiCompatModel",
125219
+ seedream: "seedreamModel",
125220
+ dashscope: "dashscopeModel",
125221
+ xai: "xaiModel",
125222
+ zhipu: "zhipuModel"
125223
+ };
125224
+ const PILL_MODEL_OPTIONS_FIELDS = { "openai-compat": "openaiCompatModels" };
125090
125225
  /** Model (or ComfyUI workflow) a provider row will use with the persisted settings. */
125091
- function pillModelOf(provider, value) {
125226
+ function pillModelOf(provider, value, channelId) {
125092
125227
  if (provider === "comfyui") {
125093
125228
  const workflow = activeComfyUIWorkflow(value ?? {});
125094
125229
  return workflow === void 0 ? "" : workflow.name;
125095
125230
  }
125096
125231
  if (SUBSCRIPTION_PROVIDERS.includes(provider)) return DEFAULT_MODELS[provider];
125097
- 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;
125232
+ if (provider === "openai-compat") {
125233
+ const channel = resolveOpenAICompatChannel(value ?? {}, channelId);
125234
+ return channel.model.trim().length > 0 ? channel.model.trim() : DEFAULT_MODELS[provider];
125235
+ }
125236
+ const field = PILL_MODEL_FIELDS[provider];
125237
+ const stored = field === void 0 ? void 0 : value?.[field];
125098
125238
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
125099
125239
  }
125240
+ /** Optional model choices exposed by a provider; single-model providers return an empty list. */
125241
+ function pillModelOptionsOf(provider, value, channelId) {
125242
+ if (provider === "openai-compat") {
125243
+ const channel = resolveOpenAICompatChannel(value ?? {}, channelId);
125244
+ return resolveOpenAICompatModels({
125245
+ openaiCompatModel: pillModelOf(provider, value, channel.id),
125246
+ openaiCompatModels: channel.models ?? []
125247
+ });
125248
+ }
125249
+ const field = PILL_MODEL_OPTIONS_FIELDS[provider];
125250
+ if (field === void 0) return [];
125251
+ const configured = value?.[field];
125252
+ const models = Array.isArray(configured) ? [...new Set(configured.filter((model) => typeof model === "string").map((model) => model.trim()).filter(Boolean))] : [];
125253
+ const defaultModel = pillModelOf(provider, value);
125254
+ if (defaultModel.length > 0 && !models.includes(defaultModel)) models.unshift(defaultModel);
125255
+ return models;
125256
+ }
125257
+ function pillTargetKey(provider, channelId) {
125258
+ return provider === "openai-compat" ? `openai-compat:${channelId ?? "default"}` : provider;
125259
+ }
125100
125260
  /** One fixed-position provider menu anchored to the pill button. */
125101
125261
  function ImageProviderPill(props) {
125102
125262
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
@@ -125104,15 +125264,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125104
125264
  const [open, setOpen] = (0, react.useState)(false);
125105
125265
  const [pending, setPending] = (0, react.useState)(void 0);
125106
125266
  const [error, setError] = (0, react.useState)("");
125107
- const [dots, setDots] = (0, react.useState)({
125108
- google: "unknown",
125109
- openai: "unknown",
125110
- "openai-compat": "unknown",
125111
- seedream: "unknown",
125112
- dashscope: "unknown",
125113
- xai: "unknown",
125114
- zhipu: "unknown"
125115
- });
125267
+ const [dots, setDots] = (0, react.useState)({});
125116
125268
  const [keyTick, setKeyTick] = (0, react.useState)(0);
125117
125269
  const [subDots, setSubDots] = (0, react.useState)(() => ({
125118
125270
  "chatgpt-sub": "unknown",
@@ -125132,35 +125284,33 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125132
125284
  }), [props.credentialEvents]);
125133
125285
  (0, react.useEffect)(() => {
125134
125286
  let active = true;
125135
- const refs = [];
125136
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
125137
- const ref = cloudCredentialRef(provider);
125138
- if (ref !== void 0) refs.push(ref);
125139
- }
125140
- props.credentials.describe(refs).then((response) => {
125287
+ const targets = [...CLOUD_IMAGE_PROVIDERS.filter((provider) => provider !== "openai-compat").map((provider) => ({
125288
+ key: provider,
125289
+ ref: cloudCredentialRef(provider)
125290
+ })), ...resolveOpenAICompatChannels(snapshot.value ?? {}).map((channel) => ({
125291
+ key: pillTargetKey("openai-compat", channel.id),
125292
+ ref: channel.keyRef
125293
+ }))];
125294
+ props.credentials.describe(targets.map((target) => target.ref)).then((response) => {
125141
125295
  if (!active) return;
125142
125296
  const next = {};
125143
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
125144
- const info = response.ok ? response.value?.[cloudCredentialRef(provider)] : void 0;
125145
- next[provider] = response.ok ? info?.configured ? "configured" : "missing" : "unknown";
125297
+ for (const target of targets) {
125298
+ const info = response.ok ? response.value?.[target.ref] : void 0;
125299
+ next[target.key] = response.ok ? info?.configured ? "configured" : "missing" : "unknown";
125146
125300
  }
125147
125301
  setDots(next);
125148
125302
  }).catch(() => {
125149
125303
  if (!active) return;
125150
- setDots({
125151
- google: "unknown",
125152
- openai: "unknown",
125153
- "openai-compat": "unknown",
125154
- seedream: "unknown",
125155
- dashscope: "unknown",
125156
- xai: "unknown",
125157
- zhipu: "unknown"
125158
- });
125304
+ setDots({});
125159
125305
  });
125160
125306
  return () => {
125161
125307
  active = false;
125162
125308
  };
125163
- }, [props.credentials, keyTick]);
125309
+ }, [
125310
+ props.credentials,
125311
+ keyTick,
125312
+ snapshot.value
125313
+ ]);
125164
125314
  (0, react.useEffect)(() => {
125165
125315
  if (!open) return;
125166
125316
  let active = true;
@@ -125188,6 +125338,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125188
125338
  }, [open, keyTick]);
125189
125339
  const t = (keyName) => (lang === "en" ? PILL_DICT.en : PILL_DICT.zh)[keyName];
125190
125340
  const current = snapshot.value?.provider ?? "google";
125341
+ const currentChannel = current === "openai-compat" ? resolveOpenAICompatChannel(snapshot.value ?? {}) : void 0;
125191
125342
  const writable = snapshot.writable;
125192
125343
  (0, react.useEffect)(() => {
125193
125344
  if (!open) {
@@ -125235,15 +125386,27 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125235
125386
  };
125236
125387
  }, [open]);
125237
125388
  if (!pillVisible(snapshot.value)) return null;
125238
- /** Same write path as the settings card: one field, one disposer, no implicit side effects. */
125239
- const select = (provider) => {
125240
- if (pending !== void 0 || provider === current) {
125389
+ const select = (provider, model, channelId) => {
125390
+ const currentModel = pillModelOf(provider, snapshot.value, channelId);
125391
+ const sameChannel = provider !== "openai-compat" || currentChannel?.id === (channelId ?? "default");
125392
+ if (pending !== void 0 || provider === current && sameChannel && (model === void 0 || model === currentModel)) {
125241
125393
  setOpen(false);
125242
125394
  return;
125243
125395
  }
125396
+ const modelField = model === void 0 ? void 0 : PILL_MODEL_FIELDS[provider];
125244
125397
  setError("");
125245
125398
  setPending(provider);
125246
- writeSetting(props.scope, "provider", provider, t("writeRejected")).then(() => {
125399
+ (async () => {
125400
+ if (model !== void 0 && provider === "openai-compat" && channelId !== void 0 && channelId !== "default") {
125401
+ const channels = (snapshot.value?.openaiCompatChannels ?? []).map((channel) => channel.id === channelId ? {
125402
+ ...channel,
125403
+ model
125404
+ } : channel);
125405
+ await writeSetting(props.scope, "openaiCompatChannels", channels, t("writeRejected"));
125406
+ } else if (model !== void 0 && modelField !== void 0) await writeSetting(props.scope, modelField, model, t("writeRejected"));
125407
+ if (provider === "openai-compat" && channelId !== void 0 && currentChannel?.id !== channelId) await writeSetting(props.scope, "openaiCompatActiveChannel", channelId, t("writeRejected"));
125408
+ if (provider !== current) await writeSetting(props.scope, "provider", provider, t("writeRejected"));
125409
+ })().then(() => {
125247
125410
  setOpen(false);
125248
125411
  }).catch((cause) => {
125249
125412
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -125254,13 +125417,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125254
125417
  /** Plan A dots: ComfyUI is always fixed blue (theme-independent); each
125255
125418
  * subscription reflects its login state (green in, gray out); cloud rows
125256
125419
  * reflect their API-key state as before. */
125257
- const dotClassOf = (provider) => {
125420
+ const dotClassOf = (provider, channelId) => {
125258
125421
  if (provider === "comfyui") return "dsh-ig-pill-dot-neutral";
125259
125422
  if (isSubscriptionProvider(provider)) return subDots[provider] === "logged-in" ? "dsh-ig-pill-dot-ok" : "dsh-ig-pill-dot-missing";
125260
- const dot = dots[provider];
125423
+ const dot = dots[pillTargetKey(provider, channelId)];
125261
125424
  return dot === "configured" ? "dsh-ig-pill-dot-ok" : dot === "missing" ? "dsh-ig-pill-dot-missing" : "dsh-ig-pill-dot-neutral";
125262
125425
  };
125263
- const subtitleOf = (provider) => {
125426
+ const subtitleOf = (provider, includeModel = true, channelId) => {
125264
125427
  if (provider === "comfyui") {
125265
125428
  const workflow = pillModelOf(provider, snapshot.value);
125266
125429
  return workflow.length > 0 ? `${t("noKeyNeeded")} · ${workflow}` : `${t("noKeyNeeded")} · ${t("noWorkflow")}`;
@@ -125270,12 +125433,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125270
125433
  const stateText = state === "logged-in" ? t("subSignedIn") : state === "logged-out" ? t("subSignedOut") : t("subStatusUnknown");
125271
125434
  return `${t("subNoKey")} · ${stateText} · ${pillModelOf(provider, snapshot.value)}`;
125272
125435
  }
125273
- const dot = dots[provider];
125274
- const model = pillModelOf(provider, snapshot.value);
125275
- if (dot === "configured") return `${t("keyConfigured")} · ${model}`;
125276
- if (dot === "missing") return `${t("keyMissing")} · ${model}`;
125277
- return model;
125436
+ const dot = dots[pillTargetKey(provider, channelId)];
125437
+ const model = pillModelOf(provider, snapshot.value, channelId);
125438
+ const modelSuffix = includeModel ? ` · ${model}` : "";
125439
+ if (dot === "configured") return `${t("keyConfigured")}${modelSuffix}`;
125440
+ if (dot === "missing") return `${t("keyMissing")}${modelSuffix}`;
125441
+ return includeModel ? model : "";
125278
125442
  };
125443
+ const menuEntries = IMAGE_PROVIDERS.flatMap((provider) => provider === "openai-compat" ? resolveOpenAICompatChannels(snapshot.value ?? {}).map((channel) => ({
125444
+ provider,
125445
+ channel
125446
+ })) : [{ provider }]);
125279
125447
  return (0, react_jsx_runtime.jsxs)("div", {
125280
125448
  className: "dsh-ig-pill-root",
125281
125449
  children: [(0, react_jsx_runtime.jsxs)("button", {
@@ -125319,7 +125487,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125319
125487
  }),
125320
125488
  (0, react_jsx_runtime.jsx)("span", {
125321
125489
  className: "dsh-ig-pill-provider",
125322
- children: PILL_PROVIDER_LABELS[lang][current]
125490
+ children: currentChannel?.label ?? PILL_PROVIDER_LABELS[lang][current]
125323
125491
  }),
125324
125492
  (0, react_jsx_runtime.jsx)("span", {
125325
125493
  className: `dsh-ig-pill-caret${open ? " dsh-ig-pill-caret-open" : ""}`,
@@ -125347,47 +125515,83 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125347
125515
  className: "dsh-ig-pill-menu-title",
125348
125516
  children: t("menuTitle")
125349
125517
  }),
125350
- IMAGE_PROVIDERS.map((provider) => (0, react_jsx_runtime.jsxs)("button", {
125351
- type: "button",
125352
- role: "menuitemradio",
125353
- "aria-checked": provider === current,
125354
- className: `dsh-ig-pill-option${provider === current ? " dsh-ig-pill-option-active" : ""}`,
125355
- disabled: pending !== void 0,
125356
- onClick: () => {
125357
- select(provider);
125358
- },
125359
- children: [
125360
- (0, react_jsx_runtime.jsx)("span", {
125361
- className: `dsh-ig-pill-dot ${dotClassOf(provider)}`,
125362
- "aria-hidden": "true"
125363
- }),
125364
- (0, react_jsx_runtime.jsxs)("span", {
125365
- className: "dsh-ig-pill-option-text",
125518
+ menuEntries.map(({ provider, channel }) => {
125519
+ const channelId = channel?.id;
125520
+ const modelOptions = pillModelOptionsOf(provider, snapshot.value, channelId);
125521
+ const active = provider === current && (provider !== "openai-compat" || currentChannel?.id === channelId);
125522
+ const currentModel = active ? pillModelOf(provider, snapshot.value, channelId) : "";
125523
+ return (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsxs)("button", {
125524
+ type: "button",
125525
+ role: modelOptions.length > 0 ? "menuitem" : "menuitemradio",
125526
+ "aria-checked": modelOptions.length > 0 ? void 0 : active,
125527
+ className: `dsh-ig-pill-option${active ? " dsh-ig-pill-option-active" : ""}`,
125528
+ disabled: pending !== void 0,
125529
+ onClick: () => {
125530
+ select(provider, void 0, channelId);
125531
+ },
125532
+ children: [
125533
+ (0, react_jsx_runtime.jsx)("span", {
125534
+ className: `dsh-ig-pill-dot ${dotClassOf(provider, channelId)}`,
125535
+ "aria-hidden": "true"
125536
+ }),
125537
+ (0, react_jsx_runtime.jsxs)("span", {
125538
+ className: "dsh-ig-pill-option-text",
125539
+ children: [(0, react_jsx_runtime.jsx)("span", {
125540
+ className: "dsh-ig-pill-option-name",
125541
+ children: channel?.label ?? PILL_PROVIDER_LABELS[lang][provider]
125542
+ }), (0, react_jsx_runtime.jsx)("span", {
125543
+ className: "dsh-ig-pill-option-sub",
125544
+ children: subtitleOf(provider, modelOptions.length === 0, channelId)
125545
+ })]
125546
+ }),
125547
+ active && modelOptions.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
125548
+ className: "dsh-ig-pill-check",
125549
+ "aria-hidden": "true",
125550
+ children: (0, react_jsx_runtime.jsx)("svg", {
125551
+ width: "13",
125552
+ height: "13",
125553
+ viewBox: "0 0 16 16",
125554
+ fill: "none",
125555
+ stroke: "currentColor",
125556
+ strokeWidth: "2.2",
125557
+ strokeLinecap: "round",
125558
+ strokeLinejoin: "round",
125559
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125560
+ })
125561
+ }) : null
125562
+ ]
125563
+ }), modelOptions.map((model) => {
125564
+ const selected = active && model === currentModel;
125565
+ return (0, react_jsx_runtime.jsxs)("button", {
125566
+ type: "button",
125567
+ role: "menuitemradio",
125568
+ "aria-checked": selected,
125569
+ className: `dsh-ig-pill-option dsh-ig-pill-model-option${selected ? " dsh-ig-pill-option-active" : ""}`,
125570
+ disabled: pending !== void 0,
125571
+ onClick: () => {
125572
+ select(provider, model, channelId);
125573
+ },
125366
125574
  children: [(0, react_jsx_runtime.jsx)("span", {
125367
- className: "dsh-ig-pill-option-name",
125368
- children: PILL_PROVIDER_LABELS[lang][provider]
125369
- }), (0, react_jsx_runtime.jsx)("span", {
125370
- className: "dsh-ig-pill-option-sub",
125371
- children: subtitleOf(provider)
125372
- })]
125373
- }),
125374
- provider === current ? (0, react_jsx_runtime.jsx)("span", {
125375
- className: "dsh-ig-pill-check",
125376
- "aria-hidden": "true",
125377
- children: (0, react_jsx_runtime.jsx)("svg", {
125378
- width: "13",
125379
- height: "13",
125380
- viewBox: "0 0 16 16",
125381
- fill: "none",
125382
- stroke: "currentColor",
125383
- strokeWidth: "2.2",
125384
- strokeLinecap: "round",
125385
- strokeLinejoin: "round",
125386
- children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125387
- })
125388
- }) : null
125389
- ]
125390
- }, provider)),
125575
+ className: "dsh-ig-pill-model-name",
125576
+ children: model
125577
+ }), selected ? (0, react_jsx_runtime.jsx)("span", {
125578
+ className: "dsh-ig-pill-check",
125579
+ "aria-hidden": "true",
125580
+ children: (0, react_jsx_runtime.jsx)("svg", {
125581
+ width: "13",
125582
+ height: "13",
125583
+ viewBox: "0 0 16 16",
125584
+ fill: "none",
125585
+ stroke: "currentColor",
125586
+ strokeWidth: "2.2",
125587
+ strokeLinecap: "round",
125588
+ strokeLinejoin: "round",
125589
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125590
+ })
125591
+ }) : null]
125592
+ }, model);
125593
+ })] }, channelId === void 0 ? provider : pillTargetKey("openai-compat", channelId));
125594
+ }),
125391
125595
  error.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
125392
125596
  className: "dsh-ig-pill-error",
125393
125597
  role: "alert",
@@ -125431,12 +125635,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125431
125635
  .dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
125432
125636
  .dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}
125433
125637
  .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}
125638
+ .dsh-ig-pill-model-option{padding-left:24px}
125639
+ .dsh-ig-pill-model-name{flex:1;min-width:0;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
125434
125640
  .dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
125435
125641
  .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}
125436
125642
  `;
125437
125643
  //#endregion
125438
125644
  //#region lib/types/client/index.js
125439
- console.info(`[dsh-image-gen] canvas bundle 2026-10-19T16:02:35.438Z`);
125645
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-28T11:56:09.512Z`);
125440
125646
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
125441
125647
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
125442
125648
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -125455,6 +125661,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125455
125661
  providerGoogle: "Google Gemini",
125456
125662
  providerOpenAI: "OpenAI",
125457
125663
  providerOpenAICompat: "OpenAI 兼容(中转站)",
125664
+ addCompatChannel: "新增 OpenAI 兼容渠道",
125665
+ channelName: "渠道名称",
125666
+ channelNameRequired: "请填写渠道名称。",
125667
+ removeChannel: "删除渠道",
125668
+ removeChannelConfirm: "确定删除“{label}”吗?该渠道的配置将被移除。",
125458
125669
  providerSeedream: "字节 Seedream",
125459
125670
  providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
125460
125671
  providerXAI: "xAI Grok Imagine",
@@ -125519,11 +125730,15 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125519
125730
  endpointHintOpenAI: "官方 api.openai.com 的 /v1 地址;中转站请使用下方「OpenAI 兼容」行。",
125520
125731
  endpointHintOpenAICompat: "中转站/自建服务的 OpenAI 兼容 /v1 地址(必填),例如 https://your-relay.example.com/v1。",
125521
125732
  editFormat: "图生图请求形态",
125733
+ editModel: "图生图模型(可选)",
125734
+ editModelHint: "留空沿用上方模型。硅基流动可分别填写 Qwen/Qwen-Image(文生图)和 Qwen/Qwen-Image-Edit(图生图)。",
125735
+ editFormatSiliconFlow: "硅基流动 JSON(image 字符串)",
125736
+ editFormatSiliconFlowHint: "编辑尺寸由参考图决定,不发送尺寸和质量字段。Qwen-Image-Edit 支持一张图,Edit-2509 最多三张;参考图字段由插件填入。",
125522
125737
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
125523
125738
  editFormatAutomatic: "自动:官方接口 JSON 格式",
125524
125739
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
125525
125740
  editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
125526
- editFormatHint: "商汤、通义已确认的官方接口自动使用对应 JSON 格式;其他中转按接口文档选择。文生图不受影响。",
125741
+ editFormatHint: "商汤、通义、硅基流动已确认的官方接口自动使用对应 JSON 格式;其他中转按接口文档选择。文生图不受影响。",
125527
125742
  editExtra: "附加 JSON 字段",
125528
125743
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
125529
125744
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -125557,6 +125772,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125557
125772
  endpointHintZhipu: "智谱开放平台 open.bigmodel.cn 的 /api/paas/v4 地址。",
125558
125773
  endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
125559
125774
  model: "模型",
125775
+ defaultModel: "默认模型",
125776
+ availableModels: "工作台可选模型",
125777
+ availableModelsHint: "勾选后可在工作台切换。手动输入的默认模型会自动加入列表,且不可移除。",
125778
+ defaultModelTag: "默认",
125560
125779
  workflow: "API Workflow 工作流",
125561
125780
  workflowImport: "导入 JSON 文件",
125562
125781
  workflowMissing: "尚未导入工作流",
@@ -125571,6 +125790,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125571
125790
  workflowDuplicateName: "工作流名称不能重复。",
125572
125791
  timeout: "生成超时(秒)",
125573
125792
  timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
125793
+ cloudTimeout: "云端生成超时(秒)",
125794
+ cloudTimeoutHint: "工作台所有 API Key 通道共用,5–3600 秒,默认 150 秒。不影响订阅通道和 ComfyUI;修改后对新请求生效。",
125574
125795
  workspaceSection: "工作区",
125575
125796
  saveToWorkspace: "保存到工作区",
125576
125797
  saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
@@ -125624,7 +125845,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125624
125845
  settingsIncompatible: "The loaded settings library cannot save these settings. Update @deepseek-ai/schemastery in the plugin environment to 3.18.4 or a compatible 3.x version, then restart DSH.",
125625
125846
  providerGoogle: "Google Gemini",
125626
125847
  providerOpenAI: "OpenAI",
125627
- providerOpenAICompat: "OpenAI-compatible (relay)",
125848
+ providerOpenAICompat: "OpenAI Compatible (relay)",
125849
+ addCompatChannel: "Add OpenAI-compatible channel",
125850
+ channelName: "Channel name",
125851
+ channelNameRequired: "Enter a channel name.",
125852
+ removeChannel: "Remove channel",
125853
+ removeChannelConfirm: "Remove \"{label}\"? Its channel configuration will be deleted.",
125628
125854
  providerSeedream: "ByteDance Seedream",
125629
125855
  providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
125630
125856
  providerXAI: "xAI Grok Imagine",
@@ -125689,11 +125915,15 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125689
125915
  endpointHintOpenAI: "Official api.openai.com /v1 base URL; for relays use the \"OpenAI-compatible\" row below.",
125690
125916
  endpointHintOpenAICompat: "OpenAI-compatible /v1 base URL of your relay or self-hosted service (required), e.g. https://your-relay.example.com/v1.",
125691
125917
  editFormat: "Edit request format",
125918
+ editModel: "Editing model (optional)",
125919
+ editModelHint: "Leave empty to reuse the model above. SiliconFlow can use Qwen/Qwen-Image for generation and Qwen/Qwen-Image-Edit for editing.",
125920
+ editFormatSiliconFlow: "SiliconFlow JSON (image string)",
125921
+ editFormatSiliconFlowHint: "Edit dimensions follow the reference; size and quality fields are omitted. Qwen-Image-Edit supports one reference, Edit-2509 up to three. The plugin fills the image fields.",
125692
125922
  editFormatMultipart: "Standard multipart (official OpenAI)",
125693
125923
  editFormatAutomatic: "Automatic: official JSON contract",
125694
125924
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
125695
125925
  editFormatFormReferenceImages: "Multipart with a reference_images JSON array (base64)",
125696
- editFormatHint: "Recognized official SenseNova and Qwen endpoints automatically use their JSON edit contracts. Choose other relay formats according to their documentation. Text-to-image is unaffected.",
125926
+ editFormatHint: "Recognized official SenseNova, Qwen and SiliconFlow endpoints automatically use their JSON edit contracts. Choose other relay formats according to their documentation. Text-to-image is unaffected.",
125697
125927
  editExtra: "Extra JSON fields",
125698
125928
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
125699
125929
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -125727,6 +125957,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125727
125957
  endpointHintZhipu: "Zhipu open.bigmodel.cn /api/paas/v4 base URL.",
125728
125958
  endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
125729
125959
  model: "Model",
125960
+ defaultModel: "Default model",
125961
+ availableModels: "Models available in Studio",
125962
+ availableModelsHint: "Selected models can be switched in Studio. A manually entered default is added automatically and cannot be removed.",
125963
+ defaultModelTag: "Default",
125730
125964
  workflow: "API Workflows",
125731
125965
  workflowImport: "Import JSON file",
125732
125966
  workflowMissing: "No workflow imported",
@@ -125741,6 +125975,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125741
125975
  workflowDuplicateName: "Workflow names must be unique.",
125742
125976
  timeout: "Generation timeout (seconds)",
125743
125977
  timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
125978
+ cloudTimeout: "Cloud generation timeout (seconds)",
125979
+ cloudTimeoutHint: "Shared by all API-key channels in the workbench, 5–3600 seconds; defaults to 150 seconds. Does not affect subscription channels or ComfyUI; applies to new requests.",
125744
125980
  workspaceSection: "Workspace",
125745
125981
  saveToWorkspace: "Save to workspace",
125746
125982
  saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
@@ -125817,6 +126053,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125817
126053
  .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)}
125818
126054
  .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
125819
126055
  .dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
126056
+ .dsh-ig-model-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px 12px;max-height:180px;overflow-y:auto;padding:2px 0}
126057
+ .dsh-ig-model-picker .dsh-ig-check-row{min-width:0}
126058
+ .dsh-ig-model-option{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}
126059
+ .dsh-ig-model-default{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:11px}
125820
126060
  .dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}
125821
126061
  .dsh-ig-save{appearance:none;border:0;border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:400;cursor:pointer;transition:opacity .15s}
125822
126062
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
@@ -125825,7 +126065,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125825
126065
  .dsh-ig-providers{display:flex;flex-direction:column;margin-top:6px}
125826
126066
  .dsh-ig-provider-row{border:0;background:none;padding:14px 0}
125827
126067
  .dsh-ig-provider-row + .dsh-ig-provider-row{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}
125828
- .dsh-ig-provider-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}
126068
+ .dsh-ig-provider-head-row{display:flex;align-items:center;gap:10px}
126069
+ .dsh-ig-provider-head{flex:1;min-width:0;width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}
126070
+ .dsh-ig-channel-add{flex:none;width:30px;padding:4px 0;font-size:16px;line-height:20px}
125829
126071
  .dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c78ff);outline-offset:2px;border-radius:var(--dsw-radius-sm,4px)}
125830
126072
  .dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-secondary,inherit)}
125831
126073
  .dsh-ig-provider-name{flex:1;min-width:0;font-size:13.5px;font-weight:550;color:var(--dsw-alias-label-primary,inherit)}
@@ -126327,7 +126569,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126327
126569
  function emptyProviderRow() {
126328
126570
  return {
126329
126571
  expanded: false,
126572
+ label: "",
126330
126573
  model: "",
126574
+ models: [],
126575
+ editModel: "",
126331
126576
  baseURL: "",
126332
126577
  googleNative: false,
126333
126578
  keyInput: "",
@@ -126410,7 +126655,29 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126410
126655
  }
126411
126656
  /** Persisted or selected edit-format value → row state; unknown values fall back to standard multipart. */
126412
126657
  function parseEditFormat(value) {
126413
- return value === "jsonImageUrlArray" || value === "formReferenceImages" ? value : "multipart";
126658
+ return value === "jsonImageUrlArray" || value === "formReferenceImages" || value === "siliconflowJson" ? value : "multipart";
126659
+ }
126660
+ function compatChannelRowKey(channelId) {
126661
+ return `openai-compat:${channelId}`;
126662
+ }
126663
+ function compatRowFields(channel) {
126664
+ return {
126665
+ label: channel.label,
126666
+ model: channel.model,
126667
+ models: resolveOpenAICompatModels({
126668
+ openaiCompatModel: channel.model,
126669
+ openaiCompatModels: channel.models ?? []
126670
+ }),
126671
+ editModel: channel.editModel ?? "",
126672
+ baseURL: channel.baseURL,
126673
+ editFormat: parseEditFormat(channel.editFormat),
126674
+ editExtraText: editExtraTextOf(channel.editExtra),
126675
+ sizeTableText: editExtraTextOf(channel.sizes),
126676
+ generationQuality: channel.quality ?? "",
126677
+ qualityOptionsText: (channel.qualityOptions !== void 0 && channel.qualityOptions.length > 0 ? channel.qualityOptions : DEFAULT_COMPAT_QUALITIES).join(", "),
126678
+ generateExtraText: editExtraTextOf(channel.extraBody),
126679
+ editSizeMode: channel.editSizeMode === "selected" ? "selected" : "auto"
126680
+ };
126414
126681
  }
126415
126682
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
126416
126683
  function rowsFromSettings(value) {
@@ -126426,21 +126693,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126426
126693
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
126427
126694
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
126428
126695
  } : {},
126429
- ...provider === "openai-compat" ? {
126430
- editFormat: parseEditFormat(value?.openaiCompatEditFormat),
126431
- editExtraText: editExtraTextOf(value?.openaiCompatEditExtra),
126432
- sizeTableText: editExtraTextOf(value?.openaiCompatSizes),
126433
- generationQuality: value?.openaiCompatQuality ?? "",
126434
- qualityOptionsText: (value?.openaiCompatQualityOptions ?? DEFAULT_COMPAT_QUALITIES).join(", "),
126435
- generateExtraText: editExtraTextOf(value?.openaiCompatExtraBody),
126436
- editSizeMode: value?.openaiCompatEditSizeMode === "selected" ? "selected" : "auto"
126437
- } : {},
126696
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126438
126697
  ...provider === "seedream" ? {
126439
126698
  outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
126440
126699
  watermark: value?.seedreamWatermark !== false,
126441
126700
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126442
126701
  } : {}
126443
126702
  };
126703
+ for (const channel of resolveOpenAICompatChannels(value ?? {}).slice(1)) rows[compatChannelRowKey(channel.id)] = {
126704
+ ...emptyProviderRow(),
126705
+ ...compatRowFields(channel)
126706
+ };
126444
126707
  return rows;
126445
126708
  }
126446
126709
  /** Edit each provider independently, pick an explicit default, and verify keys inline. */
@@ -126454,6 +126717,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126454
126717
  const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
126455
126718
  const [providerMessage, setProviderMessage] = (0, react.useState)("");
126456
126719
  const [providerMessageIsError, setProviderMessageIsError] = (0, react.useState)(false);
126720
+ const [cloudTimeoutSeconds, setCloudTimeoutSeconds] = (0, react.useState)(() => (props.scope.getSnapshot().value?.cloudTimeoutMs ?? 15e4) / 1e3);
126721
+ const [cloudTimeoutMessage, setCloudTimeoutMessage] = (0, react.useState)("");
126457
126722
  const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(() => props.scope.getSnapshot().value?.saveToWorkspace ?? true);
126458
126723
  const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)(() => props.scope.getSnapshot().value?.workspaceFolder ?? "dsh-image-gen");
126459
126724
  const [showPill, setShowPill] = (0, react.useState)(() => props.scope.getSnapshot().value?.showProviderPill === true);
@@ -126462,6 +126727,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126462
126727
  const [workspaceMessage, setWorkspaceMessage] = (0, react.useState)("");
126463
126728
  const [workspaceMessageIsError, setWorkspaceMessageIsError] = (0, react.useState)(false);
126464
126729
  const [rows, setRows] = (0, react.useState)(() => rowsFromSettings(props.scope.getSnapshot().value));
126730
+ const [compatChannels, setCompatChannels] = (0, react.useState)(() => resolveOpenAICompatChannels(props.scope.getSnapshot().value ?? {}).slice(1));
126465
126731
  const [keyTick, setKeyTick] = (0, react.useState)(0);
126466
126732
  const [subStatus, setSubStatus] = (0, react.useState)(() => ({
126467
126733
  "chatgpt-sub": { state: "unknown" },
@@ -126671,14 +126937,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126671
126937
  (0, react.useEffect)(() => {
126672
126938
  const value = snapshot.value;
126673
126939
  setDefaultProvider(value?.provider ?? "google");
126940
+ setCloudTimeoutSeconds((value?.cloudTimeoutMs ?? 15e4) / 1e3);
126674
126941
  setSaveToWorkspace(value?.saveToWorkspace ?? true);
126675
126942
  setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
126676
126943
  setShowPill(value?.showProviderPill === true);
126677
126944
  setRows((current) => {
126678
- const next = {};
126945
+ const next = { ...current };
126679
126946
  for (const provider of IMAGE_PROVIDERS) next[provider] = {
126680
126947
  ...current[provider],
126681
126948
  model: modelOf(provider, value),
126949
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126682
126950
  baseURL: baseURLOf(provider, value),
126683
126951
  ...provider === "comfyui" ? {
126684
126952
  workflows: resolveComfyUIWorkflows(value ?? {}),
@@ -126691,6 +126959,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126691
126959
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126692
126960
  } : {}
126693
126961
  };
126962
+ for (const channel of resolveOpenAICompatChannels(value ?? {}).slice(1)) {
126963
+ const key = compatChannelRowKey(channel.id);
126964
+ next[key] = {
126965
+ ...current[key] ?? emptyProviderRow(),
126966
+ ...compatRowFields(channel)
126967
+ };
126968
+ }
126694
126969
  return next;
126695
126970
  });
126696
126971
  }, [snapshot]);
@@ -126698,11 +126973,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126698
126973
  const credentialsAvailable = props.credentialsAvailable;
126699
126974
  (0, react.useEffect)(() => {
126700
126975
  let active = true;
126976
+ const targets = [...CLOUD_IMAGE_PROVIDERS.flatMap((provider) => {
126977
+ const keyRef = cloudCredentialRef(provider);
126978
+ return keyRef === void 0 ? [] : [{
126979
+ key: provider,
126980
+ keyRef
126981
+ }];
126982
+ }), ...compatChannels.map((channel) => ({
126983
+ key: compatChannelRowKey(channel.id),
126984
+ keyRef: channel.keyRef
126985
+ }))];
126701
126986
  if (!credentialsAvailable()) {
126702
126987
  setRows((current) => {
126703
126988
  const next = { ...current };
126704
- for (const provider of CLOUD_IMAGE_PROVIDERS) next[provider] = {
126705
- ...current[provider],
126989
+ for (const target of targets) next[target.key] = {
126990
+ ...current[target.key] ?? emptyProviderRow(),
126706
126991
  keyStatus: "unavailable",
126707
126992
  keyInfo: void 0
126708
126993
  };
@@ -126712,23 +126997,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126712
126997
  active = false;
126713
126998
  };
126714
126999
  }
126715
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
126716
- const keyRef = cloudCredentialRef(provider);
126717
- if (keyRef === void 0) continue;
127000
+ for (const target of targets) {
126718
127001
  setRows((current) => ({
126719
127002
  ...current,
126720
- [provider]: {
126721
- ...current[provider],
127003
+ [target.key]: {
127004
+ ...current[target.key],
126722
127005
  keyStatus: "checking"
126723
127006
  }
126724
127007
  }));
126725
- credentials.describe([keyRef]).then((response) => {
127008
+ credentials.describe([target.keyRef]).then((response) => {
126726
127009
  if (!active) return;
126727
- const info = response.ok ? response.value?.[keyRef] : void 0;
127010
+ const info = response.ok ? response.value?.[target.keyRef] : void 0;
126728
127011
  setRows((current) => ({
126729
127012
  ...current,
126730
- [provider]: {
126731
- ...current[provider],
127013
+ [target.key]: {
127014
+ ...current[target.key],
126732
127015
  keyStatus: response.ok ? info?.configured ? "configured" : "missing" : "unknown",
126733
127016
  keyInfo: info
126734
127017
  }
@@ -126737,8 +127020,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126737
127020
  if (!active) return;
126738
127021
  setRows((current) => ({
126739
127022
  ...current,
126740
- [provider]: {
126741
- ...current[provider],
127023
+ [target.key]: {
127024
+ ...current[target.key],
126742
127025
  keyStatus: "unknown"
126743
127026
  }
126744
127027
  }));
@@ -126750,13 +127033,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126750
127033
  }, [
126751
127034
  credentials,
126752
127035
  credentialsAvailable,
126753
- keyTick
127036
+ keyTick,
127037
+ compatChannels
126754
127038
  ]);
126755
- const updateRow = (provider, patch) => {
127039
+ const updateRow = (target, patch) => {
126756
127040
  setRows((current) => ({
126757
127041
  ...current,
126758
- [provider]: {
126759
- ...current[provider],
127042
+ [target]: {
127043
+ ...current[target],
126760
127044
  ...patch
126761
127045
  }
126762
127046
  }));
@@ -126772,15 +127056,49 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126772
127056
  setProviderMessageIsError(true);
126773
127057
  });
126774
127058
  };
126775
- const saveProviderRow = async (provider) => {
126776
- const row = rows[provider];
126777
- updateRow(provider, {
127059
+ const saveProviderRow = async (provider, channel) => {
127060
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127061
+ const row = rows[target];
127062
+ updateRow(target, {
126778
127063
  saving: true,
126779
127064
  message: "",
126780
127065
  messageIsError: false
126781
127066
  });
126782
127067
  try {
126783
- if (provider === "comfyui") {
127068
+ if (channel !== void 0) {
127069
+ const label = row.label.trim();
127070
+ if (label.length === 0) throw new Error(t("channelNameRequired"));
127071
+ const updated = {
127072
+ ...channel,
127073
+ label,
127074
+ baseURL: row.baseURL.trim(),
127075
+ model: row.model.trim(),
127076
+ models: resolveOpenAICompatModels({
127077
+ openaiCompatModel: row.model,
127078
+ openaiCompatModels: row.models
127079
+ }),
127080
+ editModel: row.editModel.trim(),
127081
+ quality: row.generationQuality.trim(),
127082
+ qualityOptions: [...new Set(row.qualityOptionsText.split(",").map((value) => value.trim()).filter(Boolean))],
127083
+ extraBody: parseExtraFields(row.generateExtraText, t("editExtraInvalid")),
127084
+ editSizeMode: row.editSizeMode,
127085
+ editFormat: row.editFormat,
127086
+ editExtra: parseExtraFields(row.editExtraText, t("editExtraInvalid")),
127087
+ sizes: parseSizeTable(row.sizeTableText, t("sizeTableInvalid"))
127088
+ };
127089
+ const nextChannels = compatChannels.some((entry) => entry.id === channel.id) ? compatChannels.map((entry) => entry.id === channel.id ? updated : entry) : [...compatChannels, updated];
127090
+ await saveSetting("openaiCompatChannels", nextChannels);
127091
+ setCompatChannels(nextChannels);
127092
+ if (row.keyInput.trim().length > 0) {
127093
+ if (!credentialsAvailable()) throw new Error(t("credentialsUnavailable"));
127094
+ const response = await props.credentials.set(channel.keyRef, row.keyInput.trim());
127095
+ if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
127096
+ updateRow(target, {
127097
+ keyInput: "",
127098
+ keyStatus: "configured"
127099
+ });
127100
+ }
127101
+ } else if (provider === "comfyui") {
126784
127102
  const entries = row.workflows.map((entry) => ({
126785
127103
  name: entry.name.trim(),
126786
127104
  json: entry.json,
@@ -126809,6 +127127,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126809
127127
  if (provider === "google") await saveSetting("googleNative", row.googleNative);
126810
127128
  if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126811
127129
  if (provider === "openai-compat") {
127130
+ await saveSetting("openaiCompatLabel", row.label.trim());
127131
+ await saveSetting("openaiCompatModels", resolveOpenAICompatModels({
127132
+ openaiCompatModel: row.model,
127133
+ openaiCompatModels: row.models
127134
+ }));
127135
+ await saveSetting("openaiCompatEditModel", row.editModel.trim());
126812
127136
  await saveSetting("openaiCompatEditFormat", row.editFormat);
126813
127137
  await saveSetting("openaiCompatEditExtra", editExtra);
126814
127138
  await saveSetting("openaiCompatSizes", sizeTable);
@@ -126828,13 +127152,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126828
127152
  if (!credentialsAvailable()) throw new Error(t("credentialsUnavailable"));
126829
127153
  const response = await props.credentials.set(keyRef, row.keyInput.trim());
126830
127154
  if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
126831
- updateRow(provider, {
127155
+ updateRow(target, {
126832
127156
  keyInput: "",
126833
127157
  keyStatus: "configured"
126834
127158
  });
126835
127159
  }
126836
127160
  }
126837
- updateRow(provider, {
127161
+ updateRow(target, {
126838
127162
  message: t("saved"),
126839
127163
  messageIsError: false,
126840
127164
  testResult: void 0,
@@ -126842,38 +127166,99 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126842
127166
  modelFetchIsError: false
126843
127167
  });
126844
127168
  } catch (cause) {
126845
- updateRow(provider, {
127169
+ updateRow(target, {
127170
+ message: cause instanceof Error ? cause.message : String(cause),
127171
+ messageIsError: true
127172
+ });
127173
+ } finally {
127174
+ updateRow(target, { saving: false });
127175
+ }
127176
+ };
127177
+ const addCompatChannel = () => {
127178
+ const id = `channel-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
127179
+ const channel = {
127180
+ id,
127181
+ label: `OpenAI 兼容 ${compatChannels.length + 2}`,
127182
+ keyRef: openAICompatKeyRef(id),
127183
+ baseURL: "",
127184
+ model: "",
127185
+ models: [],
127186
+ qualityOptions: [...DEFAULT_COMPAT_QUALITIES]
127187
+ };
127188
+ setCompatChannels((current) => [...current, channel]);
127189
+ setRows((current) => ({
127190
+ ...current,
127191
+ [compatChannelRowKey(id)]: {
127192
+ ...emptyProviderRow(),
127193
+ ...compatRowFields(channel),
127194
+ expanded: true
127195
+ }
127196
+ }));
127197
+ };
127198
+ const removeCompatChannel = async (channel) => {
127199
+ if (!window.confirm(t("removeChannelConfirm", { label: channel.label }))) return;
127200
+ const target = compatChannelRowKey(channel.id);
127201
+ if (!(snapshot.value?.openaiCompatChannels?.some((entry) => entry.id === channel.id) === true)) {
127202
+ setCompatChannels((current) => current.filter((entry) => entry.id !== channel.id));
127203
+ setRows((current) => {
127204
+ const next = { ...current };
127205
+ delete next[target];
127206
+ return next;
127207
+ });
127208
+ return;
127209
+ }
127210
+ let removed = false;
127211
+ updateRow(target, {
127212
+ saving: true,
127213
+ message: "",
127214
+ messageIsError: false
127215
+ });
127216
+ try {
127217
+ const nextChannels = compatChannels.filter((entry) => entry.id !== channel.id);
127218
+ await saveSetting("openaiCompatChannels", nextChannels);
127219
+ if (snapshot.value?.openaiCompatActiveChannel === channel.id) await saveSetting("openaiCompatActiveChannel", "default");
127220
+ removed = true;
127221
+ setCompatChannels(nextChannels);
127222
+ setRows((current) => {
127223
+ const next = { ...current };
127224
+ delete next[target];
127225
+ return next;
127226
+ });
127227
+ } catch (cause) {
127228
+ updateRow(target, {
126846
127229
  message: cause instanceof Error ? cause.message : String(cause),
126847
127230
  messageIsError: true
126848
127231
  });
126849
127232
  } finally {
126850
- updateRow(provider, { saving: false });
127233
+ if (!removed) updateRow(target, { saving: false });
126851
127234
  }
126852
127235
  };
126853
127236
  /** These actions use host settings, so drafts must be saved before making a request. */
126854
- const providerActionBlockMessage = (provider) => {
126855
- const row = rows[provider];
127237
+ const providerActionBlockMessage = (provider, channel) => {
127238
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127239
+ const row = rows[target];
126856
127240
  if (row.keyInput.trim().length > 0) return t("saveKeyFirst");
126857
127241
  if (isSubscriptionProvider(provider)) return void 0;
126858
127242
  const address = row.baseURL.trim();
126859
127243
  if (address.length === 0) return t("endpointRequired");
126860
- if (address !== baseURLOf(provider, props.scope.getSnapshot().value).trim()) return t("saveEndpointFirst");
127244
+ if (address !== (channel === void 0 ? baseURLOf(provider, props.scope.getSnapshot().value) : channel.baseURL).trim()) return t("saveEndpointFirst");
126861
127245
  if (provider === "google" && row.googleNative !== (props.scope.getSnapshot().value?.googleNative === true)) return t("saveGoogleProtocolFirst");
126862
127246
  };
126863
127247
  /** Localize the host's missing-address error while preserving upstream details. */
126864
127248
  const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
126865
127249
  /** Probe through the host route so the browser side never touches credential values. */
126866
- const testConnection = async (provider) => {
126867
- const blocked = providerActionBlockMessage(provider);
127250
+ const testConnection = async (provider, channel) => {
127251
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127252
+ const blocked = providerActionBlockMessage(provider, channel);
126868
127253
  if (blocked !== void 0) {
126869
- updateRow(provider, {
127254
+ updateRow(target, {
126870
127255
  message: blocked,
126871
127256
  messageIsError: false,
126872
127257
  testResult: void 0
126873
127258
  });
126874
127259
  return;
126875
127260
  }
126876
- updateRow(provider, {
127261
+ updateRow(target, {
126877
127262
  testing: true,
126878
127263
  testResult: void 0,
126879
127264
  message: "",
@@ -126883,31 +127268,35 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126883
127268
  const response = await fetch(TEST_CONNECTION_ROUTE, {
126884
127269
  method: "POST",
126885
127270
  headers: { "content-type": "application/json" },
126886
- body: JSON.stringify({ provider })
127271
+ body: JSON.stringify({
127272
+ provider,
127273
+ ...channel === void 0 ? {} : { channel: channel.id }
127274
+ })
126887
127275
  });
126888
127276
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126889
- updateRow(provider, { testResult: await response.json() });
127277
+ updateRow(target, { testResult: await response.json() });
126890
127278
  } catch (cause) {
126891
- updateRow(provider, { testResult: {
127279
+ updateRow(target, { testResult: {
126892
127280
  ok: false,
126893
127281
  reason: "error",
126894
127282
  message: cause instanceof Error ? cause.message : String(cause)
126895
127283
  } });
126896
127284
  } finally {
126897
- updateRow(provider, { testing: false });
127285
+ updateRow(target, { testing: false });
126898
127286
  }
126899
127287
  };
126900
127288
  /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
126901
- const fetchProviderModels = async (provider) => {
126902
- const blocked = providerActionBlockMessage(provider);
127289
+ const fetchProviderModels = async (provider, channel) => {
127290
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127291
+ const blocked = providerActionBlockMessage(provider, channel);
126903
127292
  if (blocked !== void 0) {
126904
- updateRow(provider, {
127293
+ updateRow(target, {
126905
127294
  modelFetchMessage: blocked,
126906
127295
  modelFetchIsError: false
126907
127296
  });
126908
127297
  return;
126909
127298
  }
126910
- updateRow(provider, {
127299
+ updateRow(target, {
126911
127300
  fetchingModels: true,
126912
127301
  modelFetchMessage: "",
126913
127302
  modelFetchIsError: false
@@ -126918,50 +127307,52 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126918
127307
  headers: { "content-type": "application/json" },
126919
127308
  body: JSON.stringify({
126920
127309
  provider,
126921
- action: "models"
127310
+ action: "models",
127311
+ ...channel === void 0 ? {} : { channel: channel.id }
126922
127312
  })
126923
127313
  });
126924
127314
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126925
127315
  const payload = await response.json();
126926
127316
  if (payload.ok && Array.isArray(payload.models)) {
126927
127317
  const models = payload.models.filter((id) => typeof id === "string");
126928
- updateRow(provider, {
127318
+ updateRow(target, {
126929
127319
  modelOptions: models,
126930
127320
  modelFetchMessage: models.length > 0 ? t("modelsFound", { n: String(models.length) }) : t("modelsNone"),
126931
127321
  modelFetchIsError: models.length === 0
126932
127322
  });
126933
- } else if (payload.reason === "missing-key") updateRow(provider, {
127323
+ } else if (payload.reason === "missing-key") updateRow(target, {
126934
127324
  modelFetchMessage: t("badgeMissing"),
126935
127325
  modelFetchIsError: true
126936
127326
  });
126937
- else if (payload.reason === "unauthorized") updateRow(provider, {
127327
+ else if (payload.reason === "unauthorized") updateRow(target, {
126938
127328
  modelFetchMessage: t("testUnauthorized"),
126939
127329
  modelFetchIsError: true
126940
127330
  });
126941
- else updateRow(provider, {
127331
+ else updateRow(target, {
126942
127332
  modelFetchMessage: providerErrorMessage(payload.message),
126943
127333
  modelFetchIsError: true
126944
127334
  });
126945
127335
  } catch (cause) {
126946
- updateRow(provider, {
127336
+ updateRow(target, {
126947
127337
  modelFetchMessage: cause instanceof Error ? cause.message : String(cause),
126948
127338
  modelFetchIsError: true
126949
127339
  });
126950
127340
  } finally {
126951
- updateRow(provider, { fetchingModels: false });
127341
+ updateRow(target, { fetchingModels: false });
126952
127342
  }
126953
127343
  };
126954
- const clearProviderKey = async (provider) => {
126955
- const keyRef = cloudCredentialRef(provider);
127344
+ const clearProviderKey = async (provider, channel) => {
127345
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127346
+ const keyRef = channel === void 0 ? cloudCredentialRef(provider) : channel.keyRef;
126956
127347
  if (keyRef === void 0) return;
126957
127348
  if (typeof props.credentials.unset !== "function") {
126958
- updateRow(provider, {
127349
+ updateRow(target, {
126959
127350
  message: t("clearKeyUnsupported"),
126960
127351
  messageIsError: true
126961
127352
  });
126962
127353
  return;
126963
127354
  }
126964
- updateRow(provider, {
127355
+ updateRow(target, {
126965
127356
  saving: true,
126966
127357
  message: "",
126967
127358
  messageIsError: false
@@ -126969,7 +127360,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126969
127360
  try {
126970
127361
  const response = await props.credentials.unset(keyRef);
126971
127362
  if (!response.ok) throw new Error(response.error?.message ?? t("clearKeyFailed"));
126972
- updateRow(provider, {
127363
+ updateRow(target, {
126973
127364
  keyStatus: "missing",
126974
127365
  keyInput: "",
126975
127366
  testResult: void 0,
@@ -126977,12 +127368,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126977
127368
  messageIsError: false
126978
127369
  });
126979
127370
  } catch (cause) {
126980
- updateRow(provider, {
127371
+ updateRow(target, {
126981
127372
  message: cause instanceof Error ? cause.message : String(cause),
126982
127373
  messageIsError: true
126983
127374
  });
126984
127375
  } finally {
126985
- updateRow(provider, { saving: false });
127376
+ updateRow(target, { saving: false });
126986
127377
  }
126987
127378
  };
126988
127379
  const testResultText = (result) => {
@@ -126992,7 +127383,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126992
127383
  if (result.reason === "unauthorized") return t("testUnauthorized");
126993
127384
  return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${providerErrorMessage(result.message)}` : ""}`;
126994
127385
  };
126995
- const badgeOf = (provider) => {
127386
+ const badgeOf = (provider, channel) => {
126996
127387
  if (provider === "comfyui") return {
126997
127388
  text: t("comfyuiNoKey"),
126998
127389
  className: "dsh-ig-badge dsh-ig-badge-neutral"
@@ -127012,7 +127403,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127012
127403
  className: "dsh-ig-badge dsh-ig-badge-neutral"
127013
127404
  };
127014
127405
  }
127015
- const status = rows[provider].keyStatus;
127406
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127407
+ const status = rows[target].keyStatus;
127016
127408
  if (status === "checking") return {
127017
127409
  text: t("badgeChecking"),
127018
127410
  className: "dsh-ig-badge dsh-ig-badge-neutral dsh-ig-badge-checking"
@@ -127120,6 +127512,19 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127120
127512
  };
127121
127513
  });
127122
127514
  };
127515
+ const commitCloudTimeout = () => {
127516
+ if (!Number.isInteger(cloudTimeoutSeconds) || cloudTimeoutSeconds < 5 || cloudTimeoutSeconds > 3600) {
127517
+ setCloudTimeoutSeconds((snapshot.value?.cloudTimeoutMs ?? 15e4) / 1e3);
127518
+ return;
127519
+ }
127520
+ const next = cloudTimeoutSeconds * 1e3;
127521
+ if (next === (snapshot.value?.cloudTimeoutMs ?? 15e4)) return;
127522
+ setCloudTimeoutMessage("");
127523
+ saveSetting("cloudTimeoutMs", next).catch((cause) => {
127524
+ setCloudTimeoutSeconds((snapshot.value?.cloudTimeoutMs ?? 15e4) / 1e3);
127525
+ setCloudTimeoutMessage(cause instanceof Error ? cause.message : String(cause));
127526
+ });
127527
+ };
127123
127528
  /** Workspace checkbox persists immediately, like every other toggle on this card. */
127124
127529
  const toggleSaveToWorkspace = (next) => {
127125
127530
  setSaveToWorkspace(next);
@@ -127156,31 +127561,57 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127156
127561
  setUiMessageIsError(true);
127157
127562
  });
127158
127563
  };
127159
- const renderCloudBody = (provider) => {
127160
- const row = rows[provider];
127564
+ const renderCloudBody = (provider, channel) => {
127565
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127566
+ const row = rows[target];
127567
+ const update = (patch) => {
127568
+ updateRow(target, patch);
127569
+ };
127570
+ const modelListId = `dsh-ig-${target.replace(/[^A-Za-z0-9_-]/g, "-")}-model-options`;
127161
127571
  const officialEditFormat = provider === "openai-compat" ? compatEditFormat({
127162
127572
  openaiCompatBaseURL: row.baseURL,
127163
- openaiCompatModel: row.model
127573
+ openaiCompatModel: row.editModel.trim() || row.model
127164
127574
  }) : "multipart";
127165
127575
  const automaticEditFormat = officialEditFormat !== "multipart";
127166
127576
  const editFormat = automaticEditFormat ? officialEditFormat : row.editFormat;
127167
- const keyRef = cloudCredentialRef(provider) ?? "";
127577
+ const keyRef = channel === void 0 ? cloudCredentialRef(provider) ?? "" : channel.keyRef;
127168
127578
  const keyReadOnly = row.keyInfo?.writable === false;
127169
127579
  const keyUnavailable = !credentialsAvailable();
127580
+ const providerLabel = provider === "openai-compat" && row.label.trim().length > 0 ? row.label.trim() : providerLabels[provider];
127581
+ const compatModelChoices = provider === "openai-compat" ? resolveOpenAICompatModels({
127582
+ openaiCompatModel: row.model,
127583
+ openaiCompatModels: [...row.models, ...row.modelOptions]
127584
+ }) : [];
127170
127585
  return (0, react_jsx_runtime.jsx)("div", {
127171
127586
  className: "dsh-ig-provider-body",
127172
127587
  children: (0, react_jsx_runtime.jsxs)("form", {
127173
127588
  onSubmit: (event) => {
127174
127589
  event.preventDefault();
127175
- saveProviderRow(provider);
127590
+ saveProviderRow(provider, channel);
127176
127591
  },
127177
127592
  children: [
127593
+ provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127594
+ className: "dsh-ig-field",
127595
+ children: [(0, react_jsx_runtime.jsx)("span", {
127596
+ className: "dsh-ig-label",
127597
+ children: t("channelName")
127598
+ }), (0, react_jsx_runtime.jsx)("input", {
127599
+ className: "dsh-ig-input",
127600
+ value: row.label,
127601
+ onChange: (event) => {
127602
+ update({ label: event.target.value });
127603
+ },
127604
+ placeholder: providerLabels[provider],
127605
+ required: channel !== void 0,
127606
+ disabled: !settingsWritable
127607
+ })]
127608
+ }) : null,
127178
127609
  (0, react_jsx_runtime.jsxs)("label", {
127179
127610
  className: "dsh-ig-field",
127180
127611
  children: [
127181
127612
  (0, react_jsx_runtime.jsx)("span", {
127182
127613
  className: "dsh-ig-label",
127183
- children: t("apiKeyLabel", { provider: providerLabels[provider] })
127614
+ children: t("apiKeyLabel", { provider: providerLabel })
127184
127615
  }),
127185
127616
  (0, react_jsx_runtime.jsx)("input", {
127186
127617
  className: "dsh-ig-input",
@@ -127188,7 +127619,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127188
127619
  autoComplete: "off",
127189
127620
  value: row.keyInput,
127190
127621
  onChange: (event) => {
127191
- updateRow(provider, { keyInput: event.target.value });
127622
+ update({ keyInput: event.target.value });
127192
127623
  },
127193
127624
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
127194
127625
  disabled: !settingsWritable || keyReadOnly || keyUnavailable
@@ -127213,7 +127644,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127213
127644
  type: "url",
127214
127645
  value: row.baseURL,
127215
127646
  onChange: (event) => {
127216
- updateRow(provider, { baseURL: event.target.value });
127647
+ update({ baseURL: event.target.value });
127217
127648
  },
127218
127649
  required: true,
127219
127650
  disabled: !settingsWritable
@@ -127222,7 +127653,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127222
127653
  className: "dsh-ig-btn-reset",
127223
127654
  title: t("resetTitle"),
127224
127655
  onClick: () => {
127225
- updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
127656
+ update({ baseURL: DEFAULT_BASE_URLS[provider] });
127226
127657
  },
127227
127658
  disabled: !settingsWritable,
127228
127659
  children: t("reset")
@@ -127243,7 +127674,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127243
127674
  className: "dsh-ig-input",
127244
127675
  value: row.googleNative ? "native" : "interactions",
127245
127676
  onChange: (event) => {
127246
- updateRow(provider, { googleNative: event.target.value === "native" });
127677
+ update({ googleNative: event.target.value === "native" });
127247
127678
  },
127248
127679
  disabled: !settingsWritable,
127249
127680
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127260,7 +127691,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127260
127691
  children: [
127261
127692
  (0, react_jsx_runtime.jsx)("span", {
127262
127693
  className: "dsh-ig-label",
127263
- children: t("model")
127694
+ children: t(provider === "openai-compat" ? "defaultModel" : "model")
127264
127695
  }),
127265
127696
  modelPullSupported(provider) ? (0, react_jsx_runtime.jsxs)("div", {
127266
127697
  className: "dsh-ig-input-group",
@@ -127269,14 +127700,20 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127269
127700
  className: "dsh-ig-input",
127270
127701
  value: row.model,
127271
127702
  onChange: (event) => {
127272
- updateRow(provider, { model: event.target.value });
127703
+ update({ model: event.target.value });
127273
127704
  },
127274
- list: `dsh-ig-${provider}-model-options`,
127705
+ onBlur: () => {
127706
+ if (provider === "openai-compat") update({ models: resolveOpenAICompatModels({
127707
+ openaiCompatModel: row.model,
127708
+ openaiCompatModels: row.models
127709
+ }) });
127710
+ },
127711
+ list: modelListId,
127275
127712
  required: provider !== "openai-compat",
127276
127713
  disabled: !settingsWritable
127277
127714
  }),
127278
127715
  (0, react_jsx_runtime.jsx)("datalist", {
127279
- id: `dsh-ig-${provider}-model-options`,
127716
+ id: modelListId,
127280
127717
  children: row.modelOptions.map((id) => (0, react_jsx_runtime.jsx)("option", { value: id }, id))
127281
127718
  }),
127282
127719
  (0, react_jsx_runtime.jsx)("button", {
@@ -127284,7 +127721,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127284
127721
  className: "dsh-ig-btn-secondary",
127285
127722
  disabled: row.fetchingModels || row.saving || !settingsWritable,
127286
127723
  onClick: () => {
127287
- fetchProviderModels(provider);
127724
+ fetchProviderModels(provider, channel);
127288
127725
  },
127289
127726
  children: row.fetchingModels ? t("fetchingModels") : t("fetchModels")
127290
127727
  })
@@ -127293,7 +127730,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127293
127730
  className: "dsh-ig-input",
127294
127731
  value: row.model,
127295
127732
  onChange: (event) => {
127296
- updateRow(provider, { model: event.target.value });
127733
+ update({ model: event.target.value });
127297
127734
  },
127298
127735
  required: true,
127299
127736
  disabled: !settingsWritable
@@ -127308,6 +127745,74 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127308
127745
  }) : null
127309
127746
  ]
127310
127747
  }),
127748
+ provider === "openai-compat" && compatModelChoices.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
127749
+ className: "dsh-ig-field",
127750
+ children: [
127751
+ (0, react_jsx_runtime.jsx)("span", {
127752
+ className: "dsh-ig-label",
127753
+ children: t("availableModels")
127754
+ }),
127755
+ (0, react_jsx_runtime.jsx)("div", {
127756
+ className: "dsh-ig-model-picker",
127757
+ children: compatModelChoices.map((id) => {
127758
+ const isDefault = row.model.trim() === id;
127759
+ const checked = isDefault || row.models.includes(id);
127760
+ return (0, react_jsx_runtime.jsxs)("label", {
127761
+ className: "dsh-ig-check-row",
127762
+ title: id,
127763
+ children: [
127764
+ (0, react_jsx_runtime.jsx)("input", {
127765
+ type: "checkbox",
127766
+ checked,
127767
+ disabled: !settingsWritable || isDefault,
127768
+ onChange: (event) => {
127769
+ const models = event.target.checked ? [...row.models, id] : row.models.filter((model) => model !== id);
127770
+ update({ models: resolveOpenAICompatModels({
127771
+ openaiCompatModel: row.model,
127772
+ openaiCompatModels: models
127773
+ }) });
127774
+ }
127775
+ }),
127776
+ (0, react_jsx_runtime.jsx)("span", {
127777
+ className: "dsh-ig-model-option",
127778
+ children: id
127779
+ }),
127780
+ isDefault ? (0, react_jsx_runtime.jsx)("span", {
127781
+ className: "dsh-ig-model-default",
127782
+ children: t("defaultModelTag")
127783
+ }) : null
127784
+ ]
127785
+ }, id);
127786
+ })
127787
+ }),
127788
+ (0, react_jsx_runtime.jsx)("span", {
127789
+ className: "dsh-ig-hint",
127790
+ children: t("availableModelsHint")
127791
+ })
127792
+ ]
127793
+ }) : null,
127794
+ provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127795
+ className: "dsh-ig-field",
127796
+ children: [
127797
+ (0, react_jsx_runtime.jsx)("span", {
127798
+ className: "dsh-ig-label",
127799
+ children: t("editModel")
127800
+ }),
127801
+ (0, react_jsx_runtime.jsx)("input", {
127802
+ className: "dsh-ig-input",
127803
+ value: row.editModel,
127804
+ onChange: (event) => {
127805
+ update({ editModel: event.target.value });
127806
+ },
127807
+ list: modelListId,
127808
+ disabled: !settingsWritable
127809
+ }),
127810
+ (0, react_jsx_runtime.jsx)("span", {
127811
+ className: "dsh-ig-hint",
127812
+ children: t("editModelHint")
127813
+ })
127814
+ ]
127815
+ }) : null,
127311
127816
  provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127312
127817
  className: "dsh-ig-field",
127313
127818
  children: [
@@ -127319,7 +127824,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127319
127824
  className: "dsh-ig-input",
127320
127825
  value: automaticEditFormat ? "__official__" : row.editFormat,
127321
127826
  onChange: (event) => {
127322
- updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
127827
+ update({ editFormat: parseEditFormat(event.target.value) });
127323
127828
  },
127324
127829
  disabled: !settingsWritable || automaticEditFormat,
127325
127830
  children: [
@@ -127338,12 +127843,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127338
127843
  (0, react_jsx_runtime.jsx)("option", {
127339
127844
  value: "formReferenceImages",
127340
127845
  children: t("editFormatFormReferenceImages")
127846
+ }),
127847
+ (0, react_jsx_runtime.jsx)("option", {
127848
+ value: "siliconflowJson",
127849
+ children: t("editFormatSiliconFlow")
127341
127850
  })
127342
127851
  ]
127343
127852
  }),
127344
127853
  (0, react_jsx_runtime.jsx)("span", {
127345
127854
  className: "dsh-ig-hint",
127346
- children: t("editFormatHint")
127855
+ children: t(editFormat === "siliconflowJson" ? "editFormatSiliconFlowHint" : "editFormatHint")
127347
127856
  })
127348
127857
  ]
127349
127858
  }) : null,
@@ -127359,7 +127868,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127359
127868
  className: "dsh-ig-input",
127360
127869
  value: row.outputFormat,
127361
127870
  onChange: (event) => {
127362
- updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127871
+ update({ outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127363
127872
  },
127364
127873
  disabled: !settingsWritable,
127365
127874
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127387,7 +127896,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127387
127896
  className: "dsh-ig-input",
127388
127897
  value: row.watermark ? "on" : "off",
127389
127898
  onChange: (event) => {
127390
- updateRow(provider, { watermark: event.target.value === "on" });
127899
+ update({ watermark: event.target.value === "on" });
127391
127900
  },
127392
127901
  disabled: !settingsWritable,
127393
127902
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127415,7 +127924,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127415
127924
  className: "dsh-ig-input",
127416
127925
  value: row.background,
127417
127926
  onChange: (event) => {
127418
- updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
127927
+ update({ background: event.target.value === "transparent" ? "transparent" : "opaque" });
127419
127928
  },
127420
127929
  disabled: !settingsWritable,
127421
127930
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127444,7 +127953,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127444
127953
  className: "dsh-ig-input dsh-ig-textarea",
127445
127954
  value: row.sizeTableText,
127446
127955
  onChange: (event) => {
127447
- updateRow(provider, { sizeTableText: event.target.value });
127956
+ update({ sizeTableText: event.target.value });
127448
127957
  },
127449
127958
  rows: 3,
127450
127959
  disabled: !settingsWritable
@@ -127467,7 +127976,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127467
127976
  className: "dsh-ig-input",
127468
127977
  value: row.generationQuality,
127469
127978
  onChange: (event) => {
127470
- updateRow(provider, { generationQuality: event.target.value });
127979
+ update({ generationQuality: event.target.value });
127471
127980
  },
127472
127981
  placeholder: "high",
127473
127982
  disabled: !settingsWritable
@@ -127487,7 +127996,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127487
127996
  className: "dsh-ig-input",
127488
127997
  value: row.qualityOptionsText,
127489
127998
  onChange: (event) => {
127490
- updateRow(provider, { qualityOptionsText: event.target.value });
127999
+ update({ qualityOptionsText: event.target.value });
127491
128000
  },
127492
128001
  disabled: !settingsWritable
127493
128002
  })]
@@ -127503,7 +128012,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127503
128012
  className: "dsh-ig-input dsh-ig-textarea",
127504
128013
  value: row.generateExtraText,
127505
128014
  onChange: (event) => {
127506
- updateRow(provider, { generateExtraText: event.target.value });
128015
+ update({ generateExtraText: event.target.value });
127507
128016
  },
127508
128017
  placeholder: "{\"watermark\":false}",
127509
128018
  rows: 2,
@@ -127516,7 +128025,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127516
128025
  ]
127517
128026
  })
127518
128027
  ] }) : null,
127519
- provider === "openai-compat" && (editFormat === "jsonImageUrlArray" || editFormat === "qwenJson") ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
128028
+ provider === "openai-compat" && (editFormat === "jsonImageUrlArray" || editFormat === "qwenJson" || editFormat === "siliconflowJson") ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
127520
128029
  className: "dsh-ig-field",
127521
128030
  children: [(0, react_jsx_runtime.jsx)("span", {
127522
128031
  className: "dsh-ig-label",
@@ -127525,7 +128034,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127525
128034
  className: "dsh-ig-input",
127526
128035
  value: row.editSizeMode,
127527
128036
  onChange: (event) => {
127528
- updateRow(provider, { editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
128037
+ update({ editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
127529
128038
  },
127530
128039
  disabled: !settingsWritable,
127531
128040
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127547,7 +128056,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127547
128056
  className: "dsh-ig-input dsh-ig-textarea",
127548
128057
  value: row.editExtraText,
127549
128058
  onChange: (event) => {
127550
- updateRow(provider, { editExtraText: event.target.value });
128059
+ update({ editExtraText: event.target.value });
127551
128060
  },
127552
128061
  placeholder: t("editExtraPlaceholder"),
127553
128062
  rows: 2,
@@ -127573,7 +128082,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127573
128082
  className: "dsh-ig-btn-secondary",
127574
128083
  disabled: row.testing || row.saving,
127575
128084
  onClick: () => {
127576
- testConnection(provider);
128085
+ testConnection(provider, channel);
127577
128086
  },
127578
128087
  children: row.testing ? t("testing") : t("testConnection")
127579
128088
  }),
@@ -127582,7 +128091,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127582
128091
  className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
127583
128092
  disabled: row.saving,
127584
128093
  onClick: () => {
127585
- clearProviderKey(provider);
128094
+ clearProviderKey(provider, channel);
127586
128095
  },
127587
128096
  children: t("clearKey")
127588
128097
  }) : null,
@@ -127900,47 +128409,115 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127900
128409
  const renderProviderRow = (provider) => {
127901
128410
  const row = rows[provider];
127902
128411
  const badge = badgeOf(provider);
128412
+ const label = provider === "openai-compat" && row.label.trim().length > 0 ? row.label.trim() : providerLabels[provider];
127903
128413
  return (0, react_jsx_runtime.jsxs)("div", {
127904
128414
  className: "dsh-ig-provider-row",
127905
- children: [(0, react_jsx_runtime.jsxs)("button", {
127906
- type: "button",
127907
- className: "dsh-ig-provider-head",
127908
- "aria-expanded": row.expanded,
127909
- onClick: () => {
127910
- updateRow(provider, { expanded: !row.expanded });
127911
- },
127912
- children: [
127913
- (0, react_jsx_runtime.jsx)("span", {
127914
- className: "dsh-ig-provider-name",
127915
- children: providerLabels[provider]
127916
- }),
127917
- (0, react_jsx_runtime.jsxs)("span", {
127918
- className: badge.className,
127919
- title: badge.text,
127920
- children: [(0, react_jsx_runtime.jsx)("span", {
127921
- className: "dsh-ig-badge-dot",
127922
- "aria-hidden": "true"
127923
- }), badge.text]
127924
- }),
127925
- (0, react_jsx_runtime.jsx)("span", {
127926
- className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
127927
- "aria-hidden": "true",
127928
- children: (0, react_jsx_runtime.jsx)("svg", {
127929
- width: "12",
127930
- height: "12",
127931
- viewBox: "0 0 16 16",
127932
- fill: "none",
127933
- stroke: "currentColor",
127934
- strokeWidth: "2",
127935
- strokeLinecap: "round",
127936
- strokeLinejoin: "round",
127937
- children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
128415
+ children: [(0, react_jsx_runtime.jsxs)("div", {
128416
+ className: "dsh-ig-provider-head-row",
128417
+ children: [provider === "openai-compat" ? (0, react_jsx_runtime.jsx)("button", {
128418
+ type: "button",
128419
+ className: "dsh-ig-btn-secondary dsh-ig-channel-add",
128420
+ title: t("addCompatChannel"),
128421
+ "aria-label": t("addCompatChannel"),
128422
+ onClick: addCompatChannel,
128423
+ disabled: !settingsWritable,
128424
+ children: "+"
128425
+ }) : null, (0, react_jsx_runtime.jsxs)("button", {
128426
+ type: "button",
128427
+ className: "dsh-ig-provider-head",
128428
+ "aria-expanded": row.expanded,
128429
+ onClick: () => {
128430
+ updateRow(provider, { expanded: !row.expanded });
128431
+ },
128432
+ children: [
128433
+ (0, react_jsx_runtime.jsx)("span", {
128434
+ className: "dsh-ig-provider-name",
128435
+ children: label
128436
+ }),
128437
+ (0, react_jsx_runtime.jsxs)("span", {
128438
+ className: badge.className,
128439
+ title: badge.text,
128440
+ children: [(0, react_jsx_runtime.jsx)("span", {
128441
+ className: "dsh-ig-badge-dot",
128442
+ "aria-hidden": "true"
128443
+ }), badge.text]
128444
+ }),
128445
+ (0, react_jsx_runtime.jsx)("span", {
128446
+ className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
128447
+ "aria-hidden": "true",
128448
+ children: (0, react_jsx_runtime.jsx)("svg", {
128449
+ width: "12",
128450
+ height: "12",
128451
+ viewBox: "0 0 16 16",
128452
+ fill: "none",
128453
+ stroke: "currentColor",
128454
+ strokeWidth: "2",
128455
+ strokeLinecap: "round",
128456
+ strokeLinejoin: "round",
128457
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
128458
+ })
127938
128459
  })
127939
- })
127940
- ]
128460
+ ]
128461
+ })]
127941
128462
  }), row.expanded ? provider === "comfyui" ? renderComfyUIBody() : isSubscriptionProvider(provider) ? renderSubscriptionBody(provider) : renderCloudBody(provider) : null]
127942
128463
  }, provider);
127943
128464
  };
128465
+ const renderCompatChannelRow = (channel) => {
128466
+ const target = compatChannelRowKey(channel.id);
128467
+ const row = rows[target];
128468
+ const badge = badgeOf("openai-compat", channel);
128469
+ return (0, react_jsx_runtime.jsxs)("div", {
128470
+ className: "dsh-ig-provider-row dsh-ig-compat-channel-row",
128471
+ children: [(0, react_jsx_runtime.jsxs)("div", {
128472
+ className: "dsh-ig-provider-head-row",
128473
+ children: [(0, react_jsx_runtime.jsxs)("button", {
128474
+ type: "button",
128475
+ className: "dsh-ig-provider-head",
128476
+ "aria-expanded": row.expanded,
128477
+ onClick: () => {
128478
+ updateRow(target, { expanded: !row.expanded });
128479
+ },
128480
+ children: [
128481
+ (0, react_jsx_runtime.jsx)("span", {
128482
+ className: "dsh-ig-provider-name",
128483
+ children: channel.label
128484
+ }),
128485
+ (0, react_jsx_runtime.jsxs)("span", {
128486
+ className: badge.className,
128487
+ title: badge.text,
128488
+ children: [(0, react_jsx_runtime.jsx)("span", {
128489
+ className: "dsh-ig-badge-dot",
128490
+ "aria-hidden": "true"
128491
+ }), badge.text]
128492
+ }),
128493
+ (0, react_jsx_runtime.jsx)("span", {
128494
+ className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
128495
+ "aria-hidden": "true",
128496
+ children: (0, react_jsx_runtime.jsx)("svg", {
128497
+ width: "12",
128498
+ height: "12",
128499
+ viewBox: "0 0 16 16",
128500
+ fill: "none",
128501
+ stroke: "currentColor",
128502
+ strokeWidth: "2",
128503
+ strokeLinecap: "round",
128504
+ strokeLinejoin: "round",
128505
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
128506
+ })
128507
+ })
128508
+ ]
128509
+ }), (0, react_jsx_runtime.jsx)("button", {
128510
+ type: "button",
128511
+ className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
128512
+ disabled: row.saving || !settingsWritable,
128513
+ onClick: () => {
128514
+ removeCompatChannel(channel);
128515
+ },
128516
+ children: t("removeChannel")
128517
+ })]
128518
+ }), row.expanded ? renderCloudBody("openai-compat", channel) : null]
128519
+ }, target);
128520
+ };
127944
128521
  return (0, react_jsx_runtime.jsxs)("li", {
127945
128522
  className: "dsh-ig-card",
127946
128523
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -127998,9 +128575,46 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127998
128575
  }) : null
127999
128576
  ]
128000
128577
  }),
128578
+ (0, react_jsx_runtime.jsxs)("label", {
128579
+ className: "dsh-ig-field",
128580
+ children: [
128581
+ (0, react_jsx_runtime.jsx)("span", {
128582
+ className: "dsh-ig-label",
128583
+ children: t("cloudTimeout")
128584
+ }),
128585
+ (0, react_jsx_runtime.jsx)("input", {
128586
+ className: "dsh-ig-input",
128587
+ type: "number",
128588
+ min: "5",
128589
+ max: "3600",
128590
+ step: "1",
128591
+ value: cloudTimeoutSeconds,
128592
+ onChange: (event) => {
128593
+ setCloudTimeoutSeconds(Number(event.target.value));
128594
+ },
128595
+ onBlur: () => {
128596
+ commitCloudTimeout();
128597
+ },
128598
+ onKeyDown: (event) => {
128599
+ if (event.key === "Enter") commitCloudTimeout();
128600
+ },
128601
+ required: true,
128602
+ disabled: !settingsWritable
128603
+ }),
128604
+ (0, react_jsx_runtime.jsx)("span", {
128605
+ className: "dsh-ig-hint",
128606
+ children: t("cloudTimeoutHint")
128607
+ }),
128608
+ cloudTimeoutMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
128609
+ className: "dsh-ig-status dsh-ig-status-error",
128610
+ role: "status",
128611
+ children: cloudTimeoutMessage
128612
+ }) : null
128613
+ ]
128614
+ }),
128001
128615
  (0, react_jsx_runtime.jsx)("div", {
128002
128616
  className: "dsh-ig-providers",
128003
- children: IMAGE_PROVIDERS.map((provider) => renderProviderRow(provider))
128617
+ children: IMAGE_PROVIDERS.flatMap((provider) => [renderProviderRow(provider), ...provider === "openai-compat" ? compatChannels.map((channel) => renderCompatChannelRow(channel)) : []])
128004
128618
  }),
128005
128619
  (0, react_jsx_runtime.jsxs)("div", {
128006
128620
  className: "dsh-ig-section",
@@ -128165,6 +128779,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128165
128779
  const activeResult = selectedRevision !== void 0 ? {
128166
128780
  prompt: selectedRevision.prompt,
128167
128781
  provider: selectedRevision.provider,
128782
+ ...selectedRevision.channel === void 0 ? {} : { channel: selectedRevision.channel },
128168
128783
  model: selectedRevision.model,
128169
128784
  output: selectedRevision.output,
128170
128785
  createdAt: selectedRevision.createdAt,
@@ -128195,6 +128810,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128195
128810
  attachment: result.attachment,
128196
128811
  prompt: result.prompt,
128197
128812
  provider: result.provider,
128813
+ ...result.channel === void 0 ? {} : { channel: result.channel },
128198
128814
  model: result.model,
128199
128815
  output: result.output,
128200
128816
  ...result.savedTo ? { savedTo: result.savedTo } : {},
@@ -128333,6 +128949,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128333
128949
  attachment: payload.attachment,
128334
128950
  prompt: payload.prompt,
128335
128951
  provider: payload.provider,
128952
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
128336
128953
  model: payload.model,
128337
128954
  output: payload.output,
128338
128955
  createdAt: payload.createdAt,
@@ -128347,6 +128964,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128347
128964
  attachment: revision.attachment,
128348
128965
  prompt: revision.prompt,
128349
128966
  provider: revision.provider,
128967
+ ...revision.channel === void 0 ? {} : { channel: revision.channel },
128350
128968
  model: revision.model,
128351
128969
  createdAt: revision.createdAt,
128352
128970
  aspectRatio: revision.ratio,