dsh-image-gen 0.9.3 → 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
@@ -160,17 +160,101 @@ window.__ModuleLoader__.load({
160
160
  "xai",
161
161
  "zhipu"
162
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
+ }
163
229
  /**
164
230
  * Providers the browser workbench can drive: the BYOK cloud set plus the
165
231
  * logged-in subscription channels. ComfyUI stays out; it has its own workflow
166
232
  * pipeline and no shared request shape.
167
233
  */
168
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";
169
253
  /** The credential reference each cloud provider's API key is stored under. */
170
254
  const CLOUD_CREDENTIAL_REFS = {
171
- google: "GEMINI_API_KEY",
172
- openai: "OPENAI_API_KEY",
173
- "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,
174
258
  seedream: "ARK_API_KEY",
175
259
  dashscope: "DASHSCOPE_API_KEY",
176
260
  xai: "XAI_API_KEY",
@@ -189,6 +273,16 @@ window.__ModuleLoader__.load({
189
273
  function studioModelForMode(profile, mode) {
190
274
  return mode === "edit" ? profile.editModel ?? profile.model : profile.model;
191
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
+ }
192
286
  function studioWatermarkCapability(profile, mode) {
193
287
  return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
194
288
  status: "unknown",
@@ -2104,9 +2198,9 @@ window.__ModuleLoader__.load({
2104
2198
  //#endregion
2105
2199
  //#region lib/types/client/multi-model-compare.js
2106
2200
  /** Map one shared output intent to settings accepted by every target model. */
2107
- function buildComparisonTargets(profiles, selectedProviders, ratio, quality, output) {
2108
- const selected = new Set(selectedProviders);
2109
- 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) => {
2110
2204
  const targetRatio = profile.ratioOptions.some((option) => option.value === ratio) ? ratio : profile.defaultRatio;
2111
2205
  const generationOptions = generationQualityOptions(profile);
2112
2206
  const qualityIntent = output === void 0 ? quality : profile.provider === "openai" || profile.provider === "chatgpt-sub" || profile.provider === "zhipu" ? output.generationQuality : output.resolution;
@@ -2145,10 +2239,11 @@ window.__ModuleLoader__.load({
2145
2239
  return [...options.values()];
2146
2240
  }
2147
2241
  /** Start with two models, not every configured API, to avoid surprise spend. */
2148
- function initialComparisonProviders(profiles, activeProvider) {
2242
+ function initialComparisonProviders(profiles, activeProvider, activeChannel) {
2149
2243
  const configured = profiles.filter((profile) => profile.configured);
2150
- const active = configured.find((profile) => profile.provider === activeProvider);
2151
- 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));
2152
2247
  }
2153
2248
  const RATIO_SORT_ORDER = [
2154
2249
  "auto",
@@ -49157,7 +49252,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49157
49252
  should not mutate them or their content. Rather, you create new
49158
49253
  instances whenever needed. The API tries to make this easy.
49159
49254
  */
49160
- var Fragment$47 = class Fragment$47 {
49255
+ var Fragment$48 = class Fragment$48 {
49161
49256
  /**
49162
49257
  @internal
49163
49258
  */
@@ -49218,7 +49313,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49218
49313
  i = 1;
49219
49314
  }
49220
49315
  for (; i < other.content.length; i++) content.push(other.content[i]);
49221
- return new Fragment$47(content, this.size + other.size);
49316
+ return new Fragment$48(content, this.size + other.size);
49222
49317
  }
49223
49318
  /**
49224
49319
  Cut out the sub-fragment between the two given positions.
@@ -49238,15 +49333,15 @@ To remove the watermark, please purchase a license at tldraw.dev.
49238
49333
  }
49239
49334
  pos = end;
49240
49335
  }
49241
- return new Fragment$47(result, size);
49336
+ return new Fragment$48(result, size);
49242
49337
  }
49243
49338
  /**
49244
49339
  @internal
49245
49340
  */
49246
49341
  cutByIndex(from, to) {
49247
- if (from == to) return Fragment$47.empty;
49342
+ if (from == to) return Fragment$48.empty;
49248
49343
  if (from == 0 && to == this.content.length) return this;
49249
- return new Fragment$47(this.content.slice(from, to));
49344
+ return new Fragment$48(this.content.slice(from, to));
49250
49345
  }
49251
49346
  /**
49252
49347
  Create a new fragment in which the node at the given index is
@@ -49258,21 +49353,21 @@ To remove the watermark, please purchase a license at tldraw.dev.
49258
49353
  let copy = this.content.slice();
49259
49354
  let size = this.size + node.nodeSize - current.nodeSize;
49260
49355
  copy[index] = node;
49261
- return new Fragment$47(copy, size);
49356
+ return new Fragment$48(copy, size);
49262
49357
  }
49263
49358
  /**
49264
49359
  Create a new fragment by prepending the given node to this
49265
49360
  fragment.
49266
49361
  */
49267
49362
  addToStart(node) {
49268
- 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);
49269
49364
  }
49270
49365
  /**
49271
49366
  Create a new fragment by appending the given node to this
49272
49367
  fragment.
49273
49368
  */
49274
49369
  addToEnd(node) {
49275
- 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);
49276
49371
  }
49277
49372
  /**
49278
49373
  Compare this fragment to another one.
@@ -49382,16 +49477,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
49382
49477
  Deserialize a fragment from its JSON representation.
49383
49478
  */
49384
49479
  static fromJSON(schema, value) {
49385
- if (!value) return Fragment$47.empty;
49480
+ if (!value) return Fragment$48.empty;
49386
49481
  if (!Array.isArray(value)) throw new RangeError("Invalid input for Fragment.fromJSON");
49387
- return Fragment$47.fromArray(value.map(schema.nodeFromJSON));
49482
+ return Fragment$48.fromArray(value.map(schema.nodeFromJSON));
49388
49483
  }
49389
49484
  /**
49390
49485
  Build a fragment from an array of nodes. Ensures that adjacent
49391
49486
  text nodes with the same marks are joined together.
49392
49487
  */
49393
49488
  static fromArray(array) {
49394
- if (!array.length) return Fragment$47.empty;
49489
+ if (!array.length) return Fragment$48.empty;
49395
49490
  let joined, size = 0;
49396
49491
  for (let i = 0; i < array.length; i++) {
49397
49492
  let node = array[i];
@@ -49401,7 +49496,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49401
49496
  joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text);
49402
49497
  } else if (joined) joined.push(node);
49403
49498
  }
49404
- return new Fragment$47(joined || array, size);
49499
+ return new Fragment$48(joined || array, size);
49405
49500
  }
49406
49501
  /**
49407
49502
  Create a fragment from something that can be interpreted as a
@@ -49410,10 +49505,10 @@ To remove the watermark, please purchase a license at tldraw.dev.
49410
49505
  fragment containing those nodes.
49411
49506
  */
49412
49507
  static from(nodes) {
49413
- if (!nodes) return Fragment$47.empty;
49414
- if (nodes instanceof Fragment$47) return nodes;
49508
+ if (!nodes) return Fragment$48.empty;
49509
+ if (nodes instanceof Fragment$48) return nodes;
49415
49510
  if (Array.isArray(nodes)) return this.fromArray(nodes);
49416
- if (nodes.attrs) return new Fragment$47([nodes], nodes.nodeSize);
49511
+ if (nodes.attrs) return new Fragment$48([nodes], nodes.nodeSize);
49417
49512
  throw new RangeError("Can not convert " + nodes + " to a Fragment" + (nodes.nodesBetween ? " (looks like multiple versions of prosemirror-model were loaded)" : ""));
49418
49513
  }
49419
49514
  };
@@ -49422,7 +49517,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49422
49517
  contain anything (rather than allocating a new empty fragment for
49423
49518
  each leaf node).
49424
49519
  */
49425
- Fragment$47.empty = new Fragment$47([], 0);
49520
+ Fragment$48.empty = new Fragment$48([], 0);
49426
49521
  const found = {
49427
49522
  index: 0,
49428
49523
  offset: 0
@@ -49635,7 +49730,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49635
49730
  if (!json) return Slice.empty;
49636
49731
  let openStart = json.openStart || 0, openEnd = json.openEnd || 0;
49637
49732
  if (typeof openStart != "number" || typeof openEnd != "number") throw new RangeError("Invalid input for Slice.fromJSON");
49638
- return new Slice(Fragment$47.fromJSON(schema, json.content), openStart, openEnd);
49733
+ return new Slice(Fragment$48.fromJSON(schema, json.content), openStart, openEnd);
49639
49734
  }
49640
49735
  /**
49641
49736
  Create a slice from a fragment by taking the maximum possible
@@ -49651,7 +49746,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
49651
49746
  /**
49652
49747
  The empty slice.
49653
49748
  */
49654
- Slice.empty = new Slice(Fragment$47.empty, 0, 0);
49749
+ Slice.empty = new Slice(Fragment$48.empty, 0, 0);
49655
49750
  function removeRange(content, from, to) {
49656
49751
  let { index, offset } = content.findIndex(from), child = content.maybeChild(index);
49657
49752
  let { index: indexTo, offset: offsetTo } = content.findIndex(to);
@@ -49735,19 +49830,19 @@ To remove the watermark, please purchase a license at tldraw.dev.
49735
49830
  if (openEnd) addNode(close(openEnd, replaceTwoWay($end, $to, depth + 1)), content);
49736
49831
  }
49737
49832
  addRange($to, null, depth, content);
49738
- return new Fragment$47(content);
49833
+ return new Fragment$48(content);
49739
49834
  }
49740
49835
  function replaceTwoWay($from, $to, depth) {
49741
49836
  let content = [];
49742
49837
  addRange(null, $from, depth, content);
49743
49838
  if ($from.depth > depth) addNode(close(joinable$1($from, $to, depth + 1), replaceTwoWay($from, $to, depth + 1)), content);
49744
49839
  addRange($to, null, depth, content);
49745
- return new Fragment$47(content);
49840
+ return new Fragment$48(content);
49746
49841
  }
49747
49842
  function prepareSliceForReplace(slice, $along) {
49748
49843
  let extra = $along.depth - slice.openStart;
49749
49844
  let node = $along.node(extra).copy(slice.content);
49750
- 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));
49751
49846
  return {
49752
49847
  start: node.resolveNoCache(slice.openStart + extra),
49753
49848
  end: node.resolveNoCache(node.content.size - slice.openEnd - extra)
@@ -50087,7 +50182,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50087
50182
  this.type = type;
50088
50183
  this.attrs = attrs;
50089
50184
  this.marks = marks;
50090
- this.content = content || Fragment$47.empty;
50185
+ this.content = content || Fragment$48.empty;
50091
50186
  }
50092
50187
  /**
50093
50188
  The array of this node's child nodes.
@@ -50396,7 +50491,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50396
50491
  can optionally pass `start` and `end` indices into the
50397
50492
  replacement fragment.
50398
50493
  */
50399
- 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) {
50400
50495
  let one = this.contentMatchAt(from).matchFragment(replacement, start, end);
50401
50496
  let two = one && one.matchFragment(this.content, to);
50402
50497
  if (!two || !two.validEnd) return false;
@@ -50466,7 +50561,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50466
50561
  if (typeof json.text != "string") throw new RangeError("Invalid text node in JSON");
50467
50562
  return schema.text(json.text, marks);
50468
50563
  }
50469
- let content = Fragment$47.fromJSON(schema, json.content);
50564
+ let content = Fragment$48.fromJSON(schema, json.content);
50470
50565
  let node = schema.nodeType(json.type).create(json.attrs, content, marks);
50471
50566
  node.type.checkAttrs(node.attrs);
50472
50567
  return node;
@@ -50605,7 +50700,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
50605
50700
  let seen = [this];
50606
50701
  function search(match, types) {
50607
50702
  let finished = match.matchFragment(after, startIndex);
50608
- 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()));
50609
50704
  for (let i = 0; i < match.next.length; i++) {
50610
50705
  let { type, next } = match.next[i];
50611
50706
  if (!(type.isText || type.hasRequiredAttrs()) && seen.indexOf(next) == -1) {
@@ -51045,7 +51140,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51045
51140
  */
51046
51141
  create(attrs = null, content, marks) {
51047
51142
  if (this.isText) throw new Error("NodeType.create can't construct text nodes");
51048
- 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));
51049
51144
  }
51050
51145
  /**
51051
51146
  Like [`create`](https://prosemirror.net/docs/ref/#model.NodeType.create), but check the given content
@@ -51053,7 +51148,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51053
51148
  if it doesn't match.
51054
51149
  */
51055
51150
  createChecked(attrs = null, content, marks) {
51056
- content = Fragment$47.from(content);
51151
+ content = Fragment$48.from(content);
51057
51152
  this.checkContent(content);
51058
51153
  return new Node$2(this, this.computeAttrs(attrs), content, Mark$1.setFrom(marks));
51059
51154
  }
@@ -51067,14 +51162,14 @@ To remove the watermark, please purchase a license at tldraw.dev.
51067
51162
  */
51068
51163
  createAndFill(attrs = null, content, marks) {
51069
51164
  attrs = this.computeAttrs(attrs);
51070
- content = Fragment$47.from(content);
51165
+ content = Fragment$48.from(content);
51071
51166
  if (content.size) {
51072
51167
  let before = this.contentMatch.fillBefore(content);
51073
51168
  if (!before) return null;
51074
51169
  content = before.append(content);
51075
51170
  }
51076
51171
  let matched = this.contentMatch.matchFragment(content);
51077
- let after = matched && matched.fillBefore(Fragment$47.empty, true);
51172
+ let after = matched && matched.fillBefore(Fragment$48.empty, true);
51078
51173
  if (!after) return null;
51079
51174
  return new Node$2(this, attrs, content.append(after), Mark$1.setFrom(marks));
51080
51175
  }
@@ -51526,7 +51621,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
51526
51621
  findWrapping(node) {
51527
51622
  if (!this.match) {
51528
51623
  if (!this.type) return [];
51529
- let fill = this.type.contentMatch.fillBefore(Fragment$47.from(node));
51624
+ let fill = this.type.contentMatch.fillBefore(Fragment$48.from(node));
51530
51625
  if (fill) this.match = this.type.contentMatch.matchFragment(fill);
51531
51626
  else {
51532
51627
  let start = this.type.contentMatch, wrap;
@@ -51547,8 +51642,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
51547
51642
  else this.content[this.content.length - 1] = text.withText(text.text.slice(0, text.text.length - m[0].length));
51548
51643
  }
51549
51644
  }
51550
- let content = Fragment$47.from(this.content);
51551
- 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));
51552
51647
  return this.type ? this.type.create(this.attrs, content, this.marks) : content;
51553
51648
  }
51554
51649
  inlineContext(node) {
@@ -52475,7 +52570,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52475
52570
  if (child.isInline) child = f(child, parent, i);
52476
52571
  mapped.push(child);
52477
52572
  }
52478
- return Fragment$47.fromArray(mapped);
52573
+ return Fragment$48.fromArray(mapped);
52479
52574
  }
52480
52575
  /**
52481
52576
  Add a mark to all inline content between two positions.
@@ -52593,7 +52688,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52593
52688
  let node = doc.nodeAt(this.pos);
52594
52689
  if (!node) return StepResult.fail("No node at mark step's position");
52595
52690
  let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks));
52596
- 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));
52597
52692
  }
52598
52693
  invert(doc) {
52599
52694
  let node = doc.nodeAt(this.pos);
@@ -52642,7 +52737,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52642
52737
  let node = doc.nodeAt(this.pos);
52643
52738
  if (!node) return StepResult.fail("No node at mark step's position");
52644
52739
  let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks));
52645
- 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));
52646
52741
  }
52647
52742
  invert(doc) {
52648
52743
  let node = doc.nodeAt(this.pos);
@@ -52906,7 +53001,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52906
53001
  if (clearNewlines && child.isText && parentType.whitespace != "pre") {
52907
53002
  let m, newline = /\r?\n|\r/g, slice;
52908
53003
  while (m = newline.exec(child.text)) {
52909
- 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);
52910
53005
  replSteps.push(new ReplaceStep(cur + m.index, cur + m.index + m[0].length, slice));
52911
53006
  }
52912
53007
  }
@@ -52914,7 +53009,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
52914
53009
  cur = end;
52915
53010
  }
52916
53011
  if (!match.validEnd) {
52917
- let fill = match.fillBefore(Fragment$47.empty, true);
53012
+ let fill = match.fillBefore(Fragment$48.empty, true);
52918
53013
  tr.replace(cur, cur, new Slice(fill, 0, 0));
52919
53014
  }
52920
53015
  for (let i = replSteps.length - 1; i >= 0; i--) tr.step(replSteps[i]);
@@ -52943,16 +53038,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
52943
53038
  let { $from, $to, depth } = range;
52944
53039
  let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1);
52945
53040
  let start = gapStart, end = gapEnd;
52946
- let before = Fragment$47.empty, openStart = 0;
53041
+ let before = Fragment$48.empty, openStart = 0;
52947
53042
  for (let d = depth, splitting = false; d > target; d--) if (splitting || $from.index(d) > 0) {
52948
53043
  splitting = true;
52949
- before = Fragment$47.from($from.node(d).copy(before));
53044
+ before = Fragment$48.from($from.node(d).copy(before));
52950
53045
  openStart++;
52951
53046
  } else start--;
52952
- let after = Fragment$47.empty, openEnd = 0;
53047
+ let after = Fragment$48.empty, openEnd = 0;
52953
53048
  for (let d = depth, splitting = false; d > target; d--) if (splitting || $to.after(d + 1) < $to.end(d)) {
52954
53049
  splitting = true;
52955
- after = Fragment$47.from($to.node(d).copy(after));
53050
+ after = Fragment$48.from($to.node(d).copy(after));
52956
53051
  openEnd++;
52957
53052
  } else end++;
52958
53053
  tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd, new Slice(before.append(after), openStart, openEnd), before.size - openStart, true));
@@ -52998,13 +53093,13 @@ To remove the watermark, please purchase a license at tldraw.dev.
52998
53093
  return inside;
52999
53094
  }
53000
53095
  function wrap(tr, range, wrappers) {
53001
- let content = Fragment$47.empty;
53096
+ let content = Fragment$48.empty;
53002
53097
  for (let i = wrappers.length - 1; i >= 0; i--) {
53003
53098
  if (content.size) {
53004
53099
  let match = wrappers[i].type.contentMatch.matchFragment(content);
53005
53100
  if (!match || !match.validEnd) throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper");
53006
53101
  }
53007
- 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));
53008
53103
  }
53009
53104
  let start = range.start, end = range.end;
53010
53105
  tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true));
@@ -53025,7 +53120,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53025
53120
  clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, void 0, convertNewlines === null);
53026
53121
  let mapping = tr.mapping.slice(mapFrom);
53027
53122
  let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
53028
- 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));
53029
53124
  if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom);
53030
53125
  return false;
53031
53126
  }
@@ -53065,7 +53160,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53065
53160
  let newNode = type.create(attrs, null, marks || node.marks);
53066
53161
  if (node.isLeaf) return tr.replaceWith(pos, pos + node.nodeSize, newNode);
53067
53162
  if (!type.validContent(node.content)) throw new RangeError("Invalid content for node type " + type.name);
53068
- 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));
53069
53164
  }
53070
53165
  /**
53071
53166
  Check whether splitting at the given position is allowed.
@@ -53088,11 +53183,11 @@ To remove the watermark, please purchase a license at tldraw.dev.
53088
53183
  return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type);
53089
53184
  }
53090
53185
  function split(tr, pos, depth = 1, typesAfter) {
53091
- 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;
53092
53187
  for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
53093
- before = Fragment$47.from($pos.node(d).copy(before));
53188
+ before = Fragment$48.from($pos.node(d).copy(before));
53094
53189
  let typeAfter = typesAfter && typesAfter[i];
53095
- 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));
53096
53191
  }
53097
53192
  tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true));
53098
53193
  }
@@ -53235,7 +53330,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53235
53330
  this.$to = $to;
53236
53331
  this.unplaced = unplaced;
53237
53332
  this.frontier = [];
53238
- this.placed = Fragment$47.empty;
53333
+ this.placed = Fragment$48.empty;
53239
53334
  for (let i = 0; i <= $from.depth; i++) {
53240
53335
  let node = $from.node(i);
53241
53336
  this.frontier.push({
@@ -53243,7 +53338,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53243
53338
  match: node.contentMatchAt($from.indexAfter(i))
53244
53339
  });
53245
53340
  }
53246
- 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));
53247
53342
  }
53248
53343
  get depth() {
53249
53344
  return this.frontier.length - 1;
@@ -53288,7 +53383,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53288
53383
  let first = fragment.firstChild;
53289
53384
  for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) {
53290
53385
  let { type, match } = this.frontier[frontierDepth], wrap, inject = null;
53291
- 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 {
53292
53387
  sliceDepth,
53293
53388
  frontierDepth,
53294
53389
  parent,
@@ -53342,7 +53437,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53342
53437
  }
53343
53438
  let toEnd = taken == fragment.childCount;
53344
53439
  if (!toEnd) openEndCount = -1;
53345
- this.placed = addToFragment(this.placed, frontierDepth, Fragment$47.from(add));
53440
+ this.placed = addToFragment(this.placed, frontierDepth, Fragment$48.from(add));
53346
53441
  this.frontier[frontierDepth].match = match;
53347
53442
  if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1) this.closeFrontierNode();
53348
53443
  for (let i = 0, cur = fragment; i < openEndCount; i++) {
@@ -53396,14 +53491,14 @@ To remove the watermark, please purchase a license at tldraw.dev.
53396
53491
  openFrontierNode(type, attrs = null, content) {
53397
53492
  let top = this.frontier[this.depth];
53398
53493
  top.match = top.match.matchType(type);
53399
- 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)));
53400
53495
  this.frontier.push({
53401
53496
  type,
53402
53497
  match: type.contentMatch
53403
53498
  });
53404
53499
  }
53405
53500
  closeFrontierNode() {
53406
- let add = this.frontier.pop().match.fillBefore(Fragment$47.empty, true);
53501
+ let add = this.frontier.pop().match.fillBefore(Fragment$48.empty, true);
53407
53502
  if (add.childCount) this.placed = addToFragment(this.placed, this.frontier.length, add);
53408
53503
  }
53409
53504
  };
@@ -53425,7 +53520,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53425
53520
  if (openStart > 1) frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0));
53426
53521
  if (openStart > 0) {
53427
53522
  frag = node.type.contentMatch.fillBefore(frag).append(frag);
53428
- 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));
53429
53524
  }
53430
53525
  return node.copy(frag);
53431
53526
  }
@@ -53508,7 +53603,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53508
53603
  if (depth > newOpen) {
53509
53604
  let match = parent.contentMatchAt(0);
53510
53605
  let start = match.fillBefore(fragment).append(fragment);
53511
- fragment = start.append(match.matchFragment(start).fillBefore(Fragment$47.empty, true));
53606
+ fragment = start.append(match.matchFragment(start).fillBefore(Fragment$48.empty, true));
53512
53607
  }
53513
53608
  return fragment;
53514
53609
  }
@@ -53517,7 +53612,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53517
53612
  let point = insertPoint(tr.doc, from, node.type);
53518
53613
  if (point != null) from = to = point;
53519
53614
  }
53520
- 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));
53521
53616
  }
53522
53617
  function deleteRange$1(tr, from, to) {
53523
53618
  let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to);
@@ -53570,7 +53665,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53570
53665
  for (let name in node.attrs) attrs[name] = node.attrs[name];
53571
53666
  attrs[this.attr] = this.value;
53572
53667
  let updated = node.type.create(attrs, null, node.marks);
53573
- 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));
53574
53669
  }
53575
53670
  getMap() {
53576
53671
  return StepMap.empty;
@@ -53753,7 +53848,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
53753
53848
  fragment, node, or array of nodes.
53754
53849
  */
53755
53850
  replaceWith(from, to, content) {
53756
- 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));
53757
53852
  }
53758
53853
  /**
53759
53854
  Delete the content between the given positions.
@@ -54258,7 +54353,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
54258
54353
  return new NodeSelection($pos);
54259
54354
  }
54260
54355
  content() {
54261
- return new Slice(Fragment$47.from(this.node), 0, 0);
54356
+ return new Slice(Fragment$48.from(this.node), 0, 0);
54262
54357
  }
54263
54358
  eq(other) {
54264
54359
  return other instanceof NodeSelection && other.anchor == this.anchor;
@@ -55301,9 +55396,9 @@ To remove the watermark, please purchase a license at tldraw.dev.
55301
55396
  let canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1);
55302
55397
  if (canDelAfter && (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && match.matchType(conn[0] || after.type).validEnd) {
55303
55398
  if (dispatch) {
55304
- let end = $cut.pos + after.nodeSize, wrap = Fragment$47.empty;
55305
- for (let i = conn.length - 1; i >= 0; i--) wrap = Fragment$47.from(conn[i].create(null, wrap));
55306
- 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));
55307
55402
  let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true));
55308
55403
  let $joinAt = tr.doc.resolve(end + 2 * conn.length);
55309
55404
  if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type && canJoin(tr.doc, $joinAt.pos)) tr.join($joinAt.pos);
@@ -55328,8 +55423,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55328
55423
  for (; !afterText.isTextblock; afterText = afterText.firstChild) afterDepth++;
55329
55424
  if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
55330
55425
  if (dispatch) {
55331
- let end = Fragment$47.empty;
55332
- 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));
55333
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());
55334
55429
  }
55335
55430
  return true;
@@ -55495,8 +55590,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55495
55590
  return true;
55496
55591
  }
55497
55592
  function doWrapInList(tr, range, wrappers, joinBefore, listType) {
55498
- let content = Fragment$47.empty;
55499
- 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));
55500
55595
  tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
55501
55596
  let found = 0;
55502
55597
  for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type == listType) found = i + 1;
@@ -55528,7 +55623,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
55528
55623
  function liftToOuterList(state, dispatch, itemType, range) {
55529
55624
  let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth);
55530
55625
  if (end < endOfList) {
55531
- 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));
55532
55627
  range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
55533
55628
  }
55534
55629
  const target = liftTarget(range);
@@ -55549,9 +55644,9 @@ To remove the watermark, please purchase a license at tldraw.dev.
55549
55644
  if (tr.mapping.map(range.end) != range.start + $start.nodeAfter.nodeSize) return false;
55550
55645
  let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
55551
55646
  let parent = $start.node(-1), indexBefore = $start.index(-1);
55552
- 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;
55553
55648
  let start = $start.pos, end = start + item.nodeSize;
55554
- 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));
55555
55650
  dispatch(tr.scrollIntoView());
55556
55651
  return true;
55557
55652
  }
@@ -55570,8 +55665,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
55570
55665
  if (nodeBefore.type != itemType) return false;
55571
55666
  if (dispatch) {
55572
55667
  let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
55573
- let inner = Fragment$47.from(nestedBefore ? itemType.create() : null);
55574
- 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);
55575
55670
  let before = range.start, after = range.end;
55576
55671
  dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true)).scrollIntoView());
55577
55672
  }
@@ -56665,7 +56760,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
56665
56760
  if (!rule.contentElement) {
56666
56761
  let found = addedNodes && addedNodes.find((n) => n.nodeType == 1 && addedNodes.indexOf(n.parentNode) < 0 && this.dom.contains(n));
56667
56762
  if (found) rule.contentElement = found;
56668
- else rule.getContent = () => Fragment$47.empty;
56763
+ else rule.getContent = () => Fragment$48.empty;
56669
56764
  }
56670
56765
  }
56671
56766
  return rule;
@@ -57775,7 +57870,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
57775
57870
  text = f(text, inCode || plainText, view);
57776
57871
  });
57777
57872
  if (inCode) {
57778
- 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);
57779
57874
  view.someProp("transformPasted", (f) => {
57780
57875
  slice = f(slice, view, true);
57781
57876
  });
@@ -57849,32 +57944,32 @@ To remove the watermark, please purchase a license at tldraw.dev.
57849
57944
  lastWrap = wrap;
57850
57945
  }
57851
57946
  });
57852
- if (result) return Fragment$47.from(result);
57947
+ if (result) return Fragment$48.from(result);
57853
57948
  }
57854
57949
  return fragment;
57855
57950
  }
57856
57951
  function withWrappers(node, wrap, from = 0) {
57857
- 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));
57858
57953
  return node;
57859
57954
  }
57860
57955
  function addToSibling(wrap, lastWrap, node, sibling, depth) {
57861
57956
  if (depth < wrap.length && depth < lastWrap.length && wrap[depth] == lastWrap[depth]) {
57862
57957
  let inner = addToSibling(wrap, lastWrap, node, sibling.lastChild, depth + 1);
57863
57958
  if (inner) return sibling.copy(sibling.content.replaceChild(sibling.childCount - 1, inner));
57864
- 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))));
57865
57960
  }
57866
57961
  }
57867
57962
  function closeRight(node, depth) {
57868
57963
  if (depth == 0) return node;
57869
57964
  let fragment = node.content.replaceChild(node.childCount - 1, closeRight(node.lastChild, depth - 1));
57870
- let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment$47.empty, true);
57965
+ let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment$48.empty, true);
57871
57966
  return node.copy(fragment.append(fill));
57872
57967
  }
57873
57968
  function closeRange(fragment, side, from, to, depth, openEnd) {
57874
57969
  let node = side < 0 ? fragment.firstChild : fragment.lastChild, inner = node.content;
57875
57970
  if (fragment.childCount > 1) openEnd = 0;
57876
57971
  if (depth < to - 1) inner = closeRange(inner, side, from, to, depth + 1, openEnd);
57877
- 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));
57878
57973
  return fragment.replaceChild(side < 0 ? 0 : fragment.childCount - 1, node.copy(inner));
57879
57974
  }
57880
57975
  function closeSlice(slice, openStart, openEnd) {
@@ -57960,7 +58055,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
57960
58055
  } catch (e) {
57961
58056
  break;
57962
58057
  }
57963
- content = Fragment$47.from(type.create(array[i + 1], content));
58058
+ content = Fragment$48.from(type.create(array[i + 1], content));
57964
58059
  openStart++;
57965
58060
  openEnd++;
57966
58061
  }
@@ -59686,7 +59781,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
59686
59781
  } else return null;
59687
59782
  let updated = [];
59688
59783
  for (let i = 0; i < prev.childCount; i++) updated.push(update(prev.child(i)));
59689
- if (Fragment$47.from(updated).eq(cur)) return {
59784
+ if (Fragment$48.from(updated).eq(cur)) return {
59690
59785
  mark,
59691
59786
  type
59692
59787
  };
@@ -60956,7 +61051,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
60956
61051
  };
60957
61052
  const isTextContent = typeof content === "string";
60958
61053
  if (isJSONContent) try {
60959
- 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)));
60960
61055
  const node = schema.nodeFromJSON(content);
60961
61056
  if (options.errorOnInvalidContent) node.check();
60962
61057
  return node;
@@ -61093,7 +61188,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
61093
61188
  else newContent = value;
61094
61189
  tr.insertText(newContent, from, to);
61095
61190
  } else {
61096
- newContent = Fragment$47.from(nodes);
61191
+ newContent = Fragment$48.from(nodes);
61097
61192
  const $from = tr.doc.resolve(from);
61098
61193
  const $fromNode = $from.node();
61099
61194
  const fromSelectionAtStart = $from.parentOffset === 0;
@@ -62509,16 +62604,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
62509
62604
  if ($from.depth === 2 || $from.node(-3).type !== type || $from.index(-2) !== $from.node(-2).childCount - 1) return false;
62510
62605
  if (dispatch) {
62511
62606
  var _type$contentMatch$de;
62512
- let wrap = Fragment$47.empty;
62607
+ let wrap = Fragment$48.empty;
62513
62608
  const depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
62514
- 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));
62515
62610
  const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1 : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
62516
62611
  const newNextTypeAttributes = {
62517
62612
  ...getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs),
62518
62613
  ...overrideAttrs
62519
62614
  };
62520
62615
  const nextType = ((_type$contentMatch$de = type.contentMatch.defaultType) === null || _type$contentMatch$de === void 0 ? void 0 : _type$contentMatch$de.createAndFill(newNextTypeAttributes)) || void 0;
62521
- 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));
62522
62617
  const start = $from.before($from.depth - (depthBefore - 1));
62523
62618
  tr.replace(start, $from.after(-depthAfter), new Slice(wrap, 4 - depthBefore, 0));
62524
62619
  let sel = -1;
@@ -63912,7 +64007,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
63912
64007
  if (!!simulatedInputMeta) setTimeout(() => {
63913
64008
  let { text } = simulatedInputMeta;
63914
64009
  if (typeof text === "string") text = text;
63915
- else text = getHTMLFromFragment(Fragment$47.from(text), state.schema);
64010
+ else text = getHTMLFromFragment(Fragment$48.from(text), state.schema);
63916
64011
  const { from } = simulatedInputMeta;
63917
64012
  const to = from + text.length;
63918
64013
  run$1({
@@ -64217,7 +64312,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
64217
64312
  if (isSimulatedPaste) {
64218
64313
  let { text } = simulatedPasteMeta;
64219
64314
  if (typeof text === "string") text = text;
64220
- else text = getHTMLFromFragment(Fragment$47.from(text), state.schema);
64315
+ else text = getHTMLFromFragment(Fragment$48.from(text), state.schema);
64221
64316
  const { from } = simulatedPasteMeta;
64222
64317
  const to = from + text.length;
64223
64318
  const pasteEvt = createClipboardPasteEvent(text);
@@ -64949,7 +65044,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
64949
65044
  } catch {
64950
65045
  return;
64951
65046
  }
64952
- if (content instanceof Fragment$47) return;
65047
+ if (content instanceof Fragment$48) return;
64953
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.");
64954
65049
  }
64955
65050
  var NodePos = class NodePos {
@@ -71721,7 +71816,7 @@ img.ProseMirror-separator {
71721
71816
  const tr = state.tr;
71722
71817
  tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize);
71723
71818
  const mappedInsertPos = tr.mapping.map(insertPos);
71724
- tr.insert(mappedInsertPos, Fragment$47.from(items));
71819
+ tr.insert(mappedInsertPos, Fragment$48.from(items));
71725
71820
  tr.setSelection(selection.map(tr.doc, tr.mapping));
71726
71821
  if (dispatch) dispatch(tr);
71727
71822
  return true;
@@ -72134,13 +72229,13 @@ img.ProseMirror-separator {
72134
72229
  const lastItem = previous.lastChild;
72135
72230
  if (!lastItem || lastItem.type.name !== name) return false;
72136
72231
  const block = $from.parent;
72137
- 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;
72138
72233
  const blockStart = $from.before();
72139
72234
  const blockEnd = $from.after();
72140
72235
  const insideLastItemEnd = blockStart - 2;
72141
72236
  return editor.commands.command(({ tr, dispatch }) => {
72142
72237
  if (dispatch) {
72143
- tr.delete(blockStart, blockEnd).insert(insideLastItemEnd, Fragment$47.from(block));
72238
+ tr.delete(blockStart, blockEnd).insert(insideLastItemEnd, Fragment$48.from(block));
72144
72239
  tr.setSelection(TextSelection.create(tr.doc, insideLastItemEnd + 1));
72145
72240
  tr.scrollIntoView();
72146
72241
  }
@@ -73517,8 +73612,8 @@ img.ProseMirror-separator {
73517
73612
  let { $from } = view.state.selection;
73518
73613
  let insert = $from.parent.contentMatchAt($from.index()).findWrapping(view.state.schema.nodes.text);
73519
73614
  if (!insert) return false;
73520
- let frag = Fragment$47.empty;
73521
- 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));
73522
73617
  let tr = view.state.tr.replace($from.pos, $from.pos, new Slice(frag, 0, 0));
73523
73618
  tr.setSelection(TextSelection.near(tr.doc.resolve($from.pos + 1)));
73524
73619
  view.dispatch(tr);
@@ -117528,6 +117623,7 @@ img.ProseMirror-separator {
117528
117623
  const [selected, setSelected] = (0, react.useState)(null);
117529
117624
  const [mode, setMode] = (0, react.useState)("generate");
117530
117625
  const [provider, setProvider] = (0, react.useState)("google");
117626
+ const [channel, setChannel] = (0, react.useState)(void 0);
117531
117627
  const [model, setModel] = (0, react.useState)("");
117532
117628
  const [ratio, setRatio] = (0, react.useState)("1:1");
117533
117629
  const [quality, setQuality] = (0, react.useState)("1K");
@@ -117541,7 +117637,7 @@ img.ProseMirror-separator {
117541
117637
  const [selectedBatchIds, setSelectedBatchIds] = (0, react.useState)([]);
117542
117638
  const [batchKind, setBatchKind] = (0, react.useState)(null);
117543
117639
  const [comparisonEnabled, setComparisonEnabled] = (0, react.useState)(false);
117544
- const [comparisonProviders, setComparisonProviders] = (0, react.useState)([]);
117640
+ const [comparisonProviderKeys, setComparisonProviderKeys] = (0, react.useState)([]);
117545
117641
  const [isGenerating, setIsGenerating] = (0, react.useState)(false);
117546
117642
  const [error, setError] = (0, react.useState)(null);
117547
117643
  const [notice, setNotice] = (0, react.useState)(null);
@@ -117616,7 +117712,7 @@ img.ProseMirror-separator {
117616
117712
  observer.disconnect();
117617
117713
  };
117618
117714
  }, []);
117619
- 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;
117620
117716
  const referencesRef = (0, react.useRef)(references);
117621
117717
  referencesRef.current = references;
117622
117718
  const t = (key, values) => {
@@ -117636,10 +117732,12 @@ img.ProseMirror-separator {
117636
117732
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
117637
117733
  const formStateRef = (0, react.useRef)({
117638
117734
  provider,
117735
+ channel,
117639
117736
  model
117640
117737
  });
117641
117738
  formStateRef.current = {
117642
117739
  provider,
117740
+ channel,
117643
117741
  model
117644
117742
  };
117645
117743
  const loadConfig = (0, react.useCallback)(async (options) => {
@@ -117658,16 +117756,19 @@ img.ProseMirror-separator {
117658
117756
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
117659
117757
  setConfig(payload);
117660
117758
  if (silent) {
117661
- const { provider: currentProvider, model: currentModel } = formStateRef.current;
117662
- const activeProfile = payload.providers.find((item) => item.provider === currentProvider);
117663
- if (activeProfile !== void 0 && activeProfile.model !== currentModel) setModel(activeProfile.model);
117664
- 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)));
117665
117766
  return;
117666
117767
  }
117667
- 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];
117668
117769
  if (initial !== void 0) {
117669
117770
  applyProvider(initial);
117670
- setComparisonProviders(initialComparisonProviders(payload.providers, initial.provider));
117771
+ setComparisonProviderKeys(initialComparisonProviders(payload.providers, initial.provider, initial.channel));
117671
117772
  }
117672
117773
  } catch (fetchError) {
117673
117774
  if (!silent && !controller.signal.aborted) setConfigError(messageOf(fetchError));
@@ -117769,14 +117870,18 @@ img.ProseMirror-separator {
117769
117870
  window.removeEventListener("mouseup", handleMouseUp);
117770
117871
  };
117771
117872
  }, []);
117772
- 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
+ ]);
117773
117878
  const requestModel = mode === "edit" && activeProfile?.editModel ? studioModelForMode(activeProfile, mode) : model;
117774
117879
  const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
117775
117880
  const apiConfiguredCount = config?.providers.filter((item) => item.configured && !isSubscriptionProvider(item.provider)).length ?? 0;
117776
117881
  const subscriptionSignedInCount = config?.providers.filter((item) => item.configured && isSubscriptionProvider(item.provider)).length ?? 0;
117777
117882
  const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
117778
117883
  const comparisonProfiles = (0, react.useMemo)(() => (config?.providers ?? []).filter((item) => item.configured && (mode === "generate" || item.supportsEditing)), [config, mode]);
117779
- 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]);
117780
117885
  const comparisonResolutionOpts = (0, react.useMemo)(() => comparisonOutputOptions(selectedComparisonProfiles, ratio, "resolution"), [selectedComparisonProfiles, ratio]);
117781
117886
  const comparisonGenerationQualityOpts = (0, react.useMemo)(() => localizeQualityOptions(comparisonOutputOptions(selectedComparisonProfiles, ratio, "generationQuality"), lang), [
117782
117887
  selectedComparisonProfiles,
@@ -117785,12 +117890,12 @@ img.ProseMirror-separator {
117785
117890
  ]);
117786
117891
  const comparisonResolution = comparisonResolutionOpts.some((option) => option.value === resolution) ? resolution : comparisonResolutionOpts[0]?.value ?? "standard";
117787
117892
  const comparisonGenerationQuality = comparisonGenerationQualityOpts.some((option) => option.value === generationQuality) ? generationQuality : comparisonGenerationQualityOpts[0]?.value ?? "__default__";
117788
- const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality, {
117893
+ const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviderKeys, ratio, quality, {
117789
117894
  resolution: comparisonResolution,
117790
117895
  generationQuality: comparisonGenerationQuality
117791
117896
  }), [
117792
117897
  comparisonProfiles,
117793
- comparisonProviders,
117898
+ comparisonProviderKeys,
117794
117899
  ratio,
117795
117900
  quality,
117796
117901
  comparisonResolution,
@@ -117807,6 +117912,7 @@ img.ProseMirror-separator {
117807
117912
  const comparisonRatioOpts = (0, react.useMemo)(() => localizeRatioOptions(comparisonOptionUnion(comparisonProfiles, "ratioOptions"), lang), [comparisonProfiles, lang]);
117808
117913
  const applyProvider = (profile) => {
117809
117914
  setProvider(profile.provider);
117915
+ setChannel(profile.channel);
117810
117916
  setModel(profile.model);
117811
117917
  setRatio(profile.defaultRatio);
117812
117918
  setQuality(profile.defaultQuality);
@@ -117815,8 +117921,8 @@ img.ProseMirror-separator {
117815
117921
  setWatermark("default");
117816
117922
  setError(null);
117817
117923
  };
117818
- const changeProvider = (value) => {
117819
- 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));
117820
117926
  if (profile !== void 0) {
117821
117927
  applyProvider(profile);
117822
117928
  const providerMax = value === "dashscope" ? 3 : 5;
@@ -117830,6 +117936,10 @@ img.ProseMirror-separator {
117830
117936
  }
117831
117937
  }
117832
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
+ };
117833
117943
  const resetFit = () => {
117834
117944
  setFit(true);
117835
117945
  setZoom(100);
@@ -117856,9 +117966,10 @@ img.ProseMirror-separator {
117856
117966
  setMode("generate");
117857
117967
  setComparisonEnabled(false);
117858
117968
  setPrompt(item.prompt);
117859
- 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));
117860
117970
  if (!profile) return;
117861
117971
  applyProvider(profile);
117972
+ if (studioModelOptionsForMode(profile, "generate").some((option) => option.value === item.model)) setModel(item.model);
117862
117973
  const nextRatio = profile.ratioOptions.some((option) => option.value === item.aspectRatio) ? item.aspectRatio : profile.defaultRatio;
117863
117974
  setRatio(nextRatio);
117864
117975
  if (profile.qualityOptions.some((option) => option.value === item.imageSize)) {
@@ -118007,7 +118118,7 @@ img.ProseMirror-separator {
118007
118118
  const targetMax = targetItem.provider === "dashscope" ? 3 : 5;
118008
118119
  if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
118009
118120
  setMode("edit");
118010
- changeProvider(targetItem.provider);
118121
+ changeProvider(targetItem.provider, targetItem.channel);
118011
118122
  return;
118012
118123
  }
118013
118124
  if (referencesRef.current.length >= targetMax) {
@@ -118018,7 +118129,7 @@ img.ProseMirror-separator {
118018
118129
  const blob = image.blob ?? await fetchAttachmentBlob(targetItem.attachment);
118019
118130
  if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
118020
118131
  setMode("edit");
118021
- changeProvider(targetItem.provider);
118132
+ changeProvider(targetItem.provider, targetItem.channel);
118022
118133
  return;
118023
118134
  }
118024
118135
  if (referencesRef.current.length >= targetMax) {
@@ -118036,7 +118147,7 @@ img.ProseMirror-separator {
118036
118147
  referencesRef.current = nextList;
118037
118148
  setError(null);
118038
118149
  setMode("edit");
118039
- changeProvider(targetItem.provider);
118150
+ changeProvider(targetItem.provider, targetItem.channel);
118040
118151
  } catch {
118041
118152
  setError(t("imageLoadFailed"));
118042
118153
  }
@@ -118059,8 +118170,8 @@ img.ProseMirror-separator {
118059
118170
  return;
118060
118171
  }
118061
118172
  if (config === null) return;
118062
- const initial = initialComparisonProviders(comparisonProfiles, provider).filter((item) => mode !== "edit" || referencesRef.current.length <= 3 || item !== "dashscope");
118063
- setComparisonProviders(initial);
118173
+ const initial = initialComparisonProviders(comparisonProfiles, provider, channel).filter((item) => mode !== "edit" || referencesRef.current.length <= 3 || item !== "dashscope");
118174
+ setComparisonProviderKeys(initial);
118064
118175
  if (![
118065
118176
  "1:1",
118066
118177
  "3:2",
@@ -118075,12 +118186,12 @@ img.ProseMirror-separator {
118075
118186
  "4K"
118076
118187
  ].includes(quality)) setQuality("1K");
118077
118188
  };
118078
- const toggleComparisonProvider = (target) => {
118079
- 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)) {
118080
118191
  setError(t("maxReferencesExceeded", { max: "3" }));
118081
118192
  return;
118082
118193
  }
118083
- 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]);
118084
118195
  setError(null);
118085
118196
  };
118086
118197
  const submit = async () => {
@@ -118114,6 +118225,7 @@ img.ProseMirror-separator {
118114
118225
  body: JSON.stringify({
118115
118226
  mode,
118116
118227
  provider: target.profile.provider,
118228
+ ...target.profile.channel === void 0 ? {} : { channel: target.profile.channel },
118117
118229
  model: studioModelForMode(target.profile, mode),
118118
118230
  prompt: prompt.trim(),
118119
118231
  ratio: target.ratio,
@@ -118144,6 +118256,7 @@ img.ProseMirror-separator {
118144
118256
  attachment: value.payload.attachment,
118145
118257
  prompt: value.payload.prompt,
118146
118258
  provider: value.payload.provider,
118259
+ ...value.payload.channel === void 0 ? {} : { channel: value.payload.channel },
118147
118260
  model: value.payload.model,
118148
118261
  createdAt: value.payload.createdAt + idx,
118149
118262
  aspectRatio: value.target.ratio,
@@ -118187,6 +118300,7 @@ img.ProseMirror-separator {
118187
118300
  prompt: prompt.trim(),
118188
118301
  ratio,
118189
118302
  quality: selectedQuality,
118303
+ ...activeProfile.channel === void 0 ? {} : { channel: activeProfile.channel },
118190
118304
  ...selectedResolution === void 0 ? {} : { resolution: selectedResolution },
118191
118305
  ...provider === "openai-compat" ? { generationQuality: selectedGenerationQuality } : {},
118192
118306
  ...watermarkCapability.status === "supported" ? { watermark } : {},
@@ -118207,6 +118321,7 @@ img.ProseMirror-separator {
118207
118321
  attachment: gen.attachment,
118208
118322
  prompt: payload.prompt,
118209
118323
  provider: payload.provider,
118324
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
118210
118325
  model: payload.model,
118211
118326
  createdAt: payload.createdAt + idx,
118212
118327
  aspectRatio: ratio,
@@ -119192,12 +119307,13 @@ img.ProseMirror-separator {
119192
119307
  (0, react_jsx_runtime.jsx)("div", {
119193
119308
  className: "dsh-ig-model-checks",
119194
119309
  children: comparisonProfiles.map((item) => {
119195
- 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);
119196
119312
  return (0, react_jsx_runtime.jsxs)("button", {
119197
119313
  type: "button",
119198
119314
  className: target ? "is-selected" : "",
119199
119315
  "aria-pressed": Boolean(target),
119200
- onClick: () => toggleComparisonProvider(item.provider),
119316
+ onClick: () => toggleComparisonProvider(targetKey),
119201
119317
  children: [
119202
119318
  (0, react_jsx_runtime.jsx)("span", {
119203
119319
  className: "dsh-ig-model-checkmark",
@@ -119220,7 +119336,7 @@ img.ProseMirror-separator {
119220
119336
  ].filter(Boolean).join(" · "), target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null]
119221
119337
  })
119222
119338
  ]
119223
- }, item.provider);
119339
+ }, targetKey);
119224
119340
  })
119225
119341
  }),
119226
119342
  (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
@@ -119268,20 +119384,17 @@ img.ProseMirror-separator {
119268
119384
  className: "dsh-ig-field-grid",
119269
119385
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
119270
119386
  label: t("provider"),
119271
- value: provider,
119272
- onChange: changeProvider,
119387
+ value: studioTargetKey(provider, channel),
119388
+ onChange: changeProfile,
119273
119389
  options: config.providers.map((item) => ({
119274
- value: item.provider,
119390
+ value: studioTargetKey(item.provider, item.channel),
119275
119391
  label: `${item.label}${item.configured ? "" : ` · ${t("unconfigured")}`}`
119276
119392
  }))
119277
119393
  }), (0, react_jsx_runtime.jsx)(FieldSelect, {
119278
119394
  label: t("model"),
119279
119395
  value: requestModel,
119280
119396
  onChange: setModel,
119281
- options: activeProfile === void 0 ? [] : [{
119282
- value: studioModelForMode(activeProfile, mode),
119283
- label: studioModelForMode(activeProfile, mode)
119284
- }]
119397
+ options: activeProfile === void 0 ? [] : studioModelOptionsForMode(activeProfile, mode)
119285
119398
  })]
119286
119399
  }),
119287
119400
  (0, react_jsx_runtime.jsxs)("div", {
@@ -122286,6 +122399,7 @@ img.ProseMirror-separator {
122286
122399
  return {
122287
122400
  mode: "generate",
122288
122401
  provider,
122402
+ ...provider === "openai-compat" && image.channel !== void 0 ? { channel: image.channel } : {},
122289
122403
  model: image.model,
122290
122404
  prompt: prompt.trim(),
122291
122405
  ratio: settings.ratio,
@@ -123036,6 +123150,7 @@ img.ProseMirror-separator {
123036
123150
  attachment: payload.attachment,
123037
123151
  prompt: payload.prompt,
123038
123152
  provider: payload.provider,
123153
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
123039
123154
  model: payload.model,
123040
123155
  createdAt: payload.createdAt,
123041
123156
  aspectRatio: request.ratio,
@@ -124303,6 +124418,7 @@ img.ProseMirror-separator {
124303
124418
  attachment,
124304
124419
  prompt: stringValue(meta.prompt, "Generated Image"),
124305
124420
  provider: stringValue(meta.provider, "google"),
124421
+ ...typeof meta.channel === "string" ? { channel: meta.channel } : {},
124306
124422
  model: stringValue(meta.model, ""),
124307
124423
  output: stringValue(meta.output, ""),
124308
124424
  ...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
@@ -124969,7 +125085,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124969
125085
  function isRevision(value) {
124970
125086
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
124971
125087
  const candidate = value;
124972
- 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));
124973
125089
  }
124974
125090
  function isAttachment(value) {
124975
125091
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -125096,16 +125212,51 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125096
125212
  function pillVisible(value) {
125097
125213
  return value?.showProviderPill === true;
125098
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" };
125099
125225
  /** Model (or ComfyUI workflow) a provider row will use with the persisted settings. */
125100
- function pillModelOf(provider, value) {
125226
+ function pillModelOf(provider, value, channelId) {
125101
125227
  if (provider === "comfyui") {
125102
125228
  const workflow = activeComfyUIWorkflow(value ?? {});
125103
125229
  return workflow === void 0 ? "" : workflow.name;
125104
125230
  }
125105
125231
  if (SUBSCRIPTION_PROVIDERS.includes(provider)) return DEFAULT_MODELS[provider];
125106
- 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];
125107
125238
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
125108
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
+ }
125109
125260
  /** One fixed-position provider menu anchored to the pill button. */
125110
125261
  function ImageProviderPill(props) {
125111
125262
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
@@ -125113,15 +125264,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125113
125264
  const [open, setOpen] = (0, react.useState)(false);
125114
125265
  const [pending, setPending] = (0, react.useState)(void 0);
125115
125266
  const [error, setError] = (0, react.useState)("");
125116
- const [dots, setDots] = (0, react.useState)({
125117
- google: "unknown",
125118
- openai: "unknown",
125119
- "openai-compat": "unknown",
125120
- seedream: "unknown",
125121
- dashscope: "unknown",
125122
- xai: "unknown",
125123
- zhipu: "unknown"
125124
- });
125267
+ const [dots, setDots] = (0, react.useState)({});
125125
125268
  const [keyTick, setKeyTick] = (0, react.useState)(0);
125126
125269
  const [subDots, setSubDots] = (0, react.useState)(() => ({
125127
125270
  "chatgpt-sub": "unknown",
@@ -125141,35 +125284,33 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125141
125284
  }), [props.credentialEvents]);
125142
125285
  (0, react.useEffect)(() => {
125143
125286
  let active = true;
125144
- const refs = [];
125145
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
125146
- const ref = cloudCredentialRef(provider);
125147
- if (ref !== void 0) refs.push(ref);
125148
- }
125149
- 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) => {
125150
125295
  if (!active) return;
125151
125296
  const next = {};
125152
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
125153
- const info = response.ok ? response.value?.[cloudCredentialRef(provider)] : void 0;
125154
- 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";
125155
125300
  }
125156
125301
  setDots(next);
125157
125302
  }).catch(() => {
125158
125303
  if (!active) return;
125159
- setDots({
125160
- google: "unknown",
125161
- openai: "unknown",
125162
- "openai-compat": "unknown",
125163
- seedream: "unknown",
125164
- dashscope: "unknown",
125165
- xai: "unknown",
125166
- zhipu: "unknown"
125167
- });
125304
+ setDots({});
125168
125305
  });
125169
125306
  return () => {
125170
125307
  active = false;
125171
125308
  };
125172
- }, [props.credentials, keyTick]);
125309
+ }, [
125310
+ props.credentials,
125311
+ keyTick,
125312
+ snapshot.value
125313
+ ]);
125173
125314
  (0, react.useEffect)(() => {
125174
125315
  if (!open) return;
125175
125316
  let active = true;
@@ -125197,6 +125338,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125197
125338
  }, [open, keyTick]);
125198
125339
  const t = (keyName) => (lang === "en" ? PILL_DICT.en : PILL_DICT.zh)[keyName];
125199
125340
  const current = snapshot.value?.provider ?? "google";
125341
+ const currentChannel = current === "openai-compat" ? resolveOpenAICompatChannel(snapshot.value ?? {}) : void 0;
125200
125342
  const writable = snapshot.writable;
125201
125343
  (0, react.useEffect)(() => {
125202
125344
  if (!open) {
@@ -125244,15 +125386,27 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125244
125386
  };
125245
125387
  }, [open]);
125246
125388
  if (!pillVisible(snapshot.value)) return null;
125247
- /** Same write path as the settings card: one field, one disposer, no implicit side effects. */
125248
- const select = (provider) => {
125249
- 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)) {
125250
125393
  setOpen(false);
125251
125394
  return;
125252
125395
  }
125396
+ const modelField = model === void 0 ? void 0 : PILL_MODEL_FIELDS[provider];
125253
125397
  setError("");
125254
125398
  setPending(provider);
125255
- 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(() => {
125256
125410
  setOpen(false);
125257
125411
  }).catch((cause) => {
125258
125412
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -125263,13 +125417,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125263
125417
  /** Plan A dots: ComfyUI is always fixed blue (theme-independent); each
125264
125418
  * subscription reflects its login state (green in, gray out); cloud rows
125265
125419
  * reflect their API-key state as before. */
125266
- const dotClassOf = (provider) => {
125420
+ const dotClassOf = (provider, channelId) => {
125267
125421
  if (provider === "comfyui") return "dsh-ig-pill-dot-neutral";
125268
125422
  if (isSubscriptionProvider(provider)) return subDots[provider] === "logged-in" ? "dsh-ig-pill-dot-ok" : "dsh-ig-pill-dot-missing";
125269
- const dot = dots[provider];
125423
+ const dot = dots[pillTargetKey(provider, channelId)];
125270
125424
  return dot === "configured" ? "dsh-ig-pill-dot-ok" : dot === "missing" ? "dsh-ig-pill-dot-missing" : "dsh-ig-pill-dot-neutral";
125271
125425
  };
125272
- const subtitleOf = (provider) => {
125426
+ const subtitleOf = (provider, includeModel = true, channelId) => {
125273
125427
  if (provider === "comfyui") {
125274
125428
  const workflow = pillModelOf(provider, snapshot.value);
125275
125429
  return workflow.length > 0 ? `${t("noKeyNeeded")} · ${workflow}` : `${t("noKeyNeeded")} · ${t("noWorkflow")}`;
@@ -125279,12 +125433,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125279
125433
  const stateText = state === "logged-in" ? t("subSignedIn") : state === "logged-out" ? t("subSignedOut") : t("subStatusUnknown");
125280
125434
  return `${t("subNoKey")} · ${stateText} · ${pillModelOf(provider, snapshot.value)}`;
125281
125435
  }
125282
- const dot = dots[provider];
125283
- const model = pillModelOf(provider, snapshot.value);
125284
- if (dot === "configured") return `${t("keyConfigured")} · ${model}`;
125285
- if (dot === "missing") return `${t("keyMissing")} · ${model}`;
125286
- 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 : "";
125287
125442
  };
125443
+ const menuEntries = IMAGE_PROVIDERS.flatMap((provider) => provider === "openai-compat" ? resolveOpenAICompatChannels(snapshot.value ?? {}).map((channel) => ({
125444
+ provider,
125445
+ channel
125446
+ })) : [{ provider }]);
125288
125447
  return (0, react_jsx_runtime.jsxs)("div", {
125289
125448
  className: "dsh-ig-pill-root",
125290
125449
  children: [(0, react_jsx_runtime.jsxs)("button", {
@@ -125328,7 +125487,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125328
125487
  }),
125329
125488
  (0, react_jsx_runtime.jsx)("span", {
125330
125489
  className: "dsh-ig-pill-provider",
125331
- children: PILL_PROVIDER_LABELS[lang][current]
125490
+ children: currentChannel?.label ?? PILL_PROVIDER_LABELS[lang][current]
125332
125491
  }),
125333
125492
  (0, react_jsx_runtime.jsx)("span", {
125334
125493
  className: `dsh-ig-pill-caret${open ? " dsh-ig-pill-caret-open" : ""}`,
@@ -125356,47 +125515,83 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125356
125515
  className: "dsh-ig-pill-menu-title",
125357
125516
  children: t("menuTitle")
125358
125517
  }),
125359
- IMAGE_PROVIDERS.map((provider) => (0, react_jsx_runtime.jsxs)("button", {
125360
- type: "button",
125361
- role: "menuitemradio",
125362
- "aria-checked": provider === current,
125363
- className: `dsh-ig-pill-option${provider === current ? " dsh-ig-pill-option-active" : ""}`,
125364
- disabled: pending !== void 0,
125365
- onClick: () => {
125366
- select(provider);
125367
- },
125368
- children: [
125369
- (0, react_jsx_runtime.jsx)("span", {
125370
- className: `dsh-ig-pill-dot ${dotClassOf(provider)}`,
125371
- "aria-hidden": "true"
125372
- }),
125373
- (0, react_jsx_runtime.jsxs)("span", {
125374
- 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
+ },
125375
125574
  children: [(0, react_jsx_runtime.jsx)("span", {
125376
- className: "dsh-ig-pill-option-name",
125377
- children: PILL_PROVIDER_LABELS[lang][provider]
125378
- }), (0, react_jsx_runtime.jsx)("span", {
125379
- className: "dsh-ig-pill-option-sub",
125380
- children: subtitleOf(provider)
125381
- })]
125382
- }),
125383
- provider === current ? (0, react_jsx_runtime.jsx)("span", {
125384
- className: "dsh-ig-pill-check",
125385
- "aria-hidden": "true",
125386
- children: (0, react_jsx_runtime.jsx)("svg", {
125387
- width: "13",
125388
- height: "13",
125389
- viewBox: "0 0 16 16",
125390
- fill: "none",
125391
- stroke: "currentColor",
125392
- strokeWidth: "2.2",
125393
- strokeLinecap: "round",
125394
- strokeLinejoin: "round",
125395
- children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125396
- })
125397
- }) : null
125398
- ]
125399
- }, 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
+ }),
125400
125595
  error.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
125401
125596
  className: "dsh-ig-pill-error",
125402
125597
  role: "alert",
@@ -125440,12 +125635,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125440
125635
  .dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
125441
125636
  .dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}
125442
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}
125443
125640
  .dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
125444
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}
125445
125642
  `;
125446
125643
  //#endregion
125447
125644
  //#region lib/types/client/index.js
125448
- console.info(`[dsh-image-gen] canvas bundle 2026-10-26T00:24:12.240Z`);
125645
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-28T11:56:09.512Z`);
125449
125646
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
125450
125647
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
125451
125648
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -125464,6 +125661,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125464
125661
  providerGoogle: "Google Gemini",
125465
125662
  providerOpenAI: "OpenAI",
125466
125663
  providerOpenAICompat: "OpenAI 兼容(中转站)",
125664
+ addCompatChannel: "新增 OpenAI 兼容渠道",
125665
+ channelName: "渠道名称",
125666
+ channelNameRequired: "请填写渠道名称。",
125667
+ removeChannel: "删除渠道",
125668
+ removeChannelConfirm: "确定删除“{label}”吗?该渠道的配置将被移除。",
125467
125669
  providerSeedream: "字节 Seedream",
125468
125670
  providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
125469
125671
  providerXAI: "xAI Grok Imagine",
@@ -125570,6 +125772,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125570
125772
  endpointHintZhipu: "智谱开放平台 open.bigmodel.cn 的 /api/paas/v4 地址。",
125571
125773
  endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
125572
125774
  model: "模型",
125775
+ defaultModel: "默认模型",
125776
+ availableModels: "工作台可选模型",
125777
+ availableModelsHint: "勾选后可在工作台切换。手动输入的默认模型会自动加入列表,且不可移除。",
125778
+ defaultModelTag: "默认",
125573
125779
  workflow: "API Workflow 工作流",
125574
125780
  workflowImport: "导入 JSON 文件",
125575
125781
  workflowMissing: "尚未导入工作流",
@@ -125584,6 +125790,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125584
125790
  workflowDuplicateName: "工作流名称不能重复。",
125585
125791
  timeout: "生成超时(秒)",
125586
125792
  timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
125793
+ cloudTimeout: "云端生成超时(秒)",
125794
+ cloudTimeoutHint: "工作台所有 API Key 通道共用,5–3600 秒,默认 150 秒。不影响订阅通道和 ComfyUI;修改后对新请求生效。",
125587
125795
  workspaceSection: "工作区",
125588
125796
  saveToWorkspace: "保存到工作区",
125589
125797
  saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
@@ -125637,7 +125845,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125637
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.",
125638
125846
  providerGoogle: "Google Gemini",
125639
125847
  providerOpenAI: "OpenAI",
125640
- 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.",
125641
125854
  providerSeedream: "ByteDance Seedream",
125642
125855
  providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
125643
125856
  providerXAI: "xAI Grok Imagine",
@@ -125744,6 +125957,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125744
125957
  endpointHintZhipu: "Zhipu open.bigmodel.cn /api/paas/v4 base URL.",
125745
125958
  endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
125746
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",
125747
125964
  workflow: "API Workflows",
125748
125965
  workflowImport: "Import JSON file",
125749
125966
  workflowMissing: "No workflow imported",
@@ -125758,6 +125975,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125758
125975
  workflowDuplicateName: "Workflow names must be unique.",
125759
125976
  timeout: "Generation timeout (seconds)",
125760
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.",
125761
125980
  workspaceSection: "Workspace",
125762
125981
  saveToWorkspace: "Save to workspace",
125763
125982
  saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
@@ -125834,6 +126053,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125834
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)}
125835
126054
  .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
125836
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}
125837
126060
  .dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}
125838
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}
125839
126062
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
@@ -125842,7 +126065,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125842
126065
  .dsh-ig-providers{display:flex;flex-direction:column;margin-top:6px}
125843
126066
  .dsh-ig-provider-row{border:0;background:none;padding:14px 0}
125844
126067
  .dsh-ig-provider-row + .dsh-ig-provider-row{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}
125845
- .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}
125846
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)}
125847
126072
  .dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-secondary,inherit)}
125848
126073
  .dsh-ig-provider-name{flex:1;min-width:0;font-size:13.5px;font-weight:550;color:var(--dsw-alias-label-primary,inherit)}
@@ -126344,7 +126569,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126344
126569
  function emptyProviderRow() {
126345
126570
  return {
126346
126571
  expanded: false,
126572
+ label: "",
126347
126573
  model: "",
126574
+ models: [],
126348
126575
  editModel: "",
126349
126576
  baseURL: "",
126350
126577
  googleNative: false,
@@ -126430,6 +126657,28 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126430
126657
  function parseEditFormat(value) {
126431
126658
  return value === "jsonImageUrlArray" || value === "formReferenceImages" || value === "siliconflowJson" ? value : "multipart";
126432
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
+ };
126681
+ }
126433
126682
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
126434
126683
  function rowsFromSettings(value) {
126435
126684
  const rows = {};
@@ -126444,22 +126693,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126444
126693
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
126445
126694
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
126446
126695
  } : {},
126447
- ...provider === "openai-compat" ? {
126448
- editModel: value?.openaiCompatEditModel ?? "",
126449
- editFormat: parseEditFormat(value?.openaiCompatEditFormat),
126450
- editExtraText: editExtraTextOf(value?.openaiCompatEditExtra),
126451
- sizeTableText: editExtraTextOf(value?.openaiCompatSizes),
126452
- generationQuality: value?.openaiCompatQuality ?? "",
126453
- qualityOptionsText: (value?.openaiCompatQualityOptions ?? DEFAULT_COMPAT_QUALITIES).join(", "),
126454
- generateExtraText: editExtraTextOf(value?.openaiCompatExtraBody),
126455
- editSizeMode: value?.openaiCompatEditSizeMode === "selected" ? "selected" : "auto"
126456
- } : {},
126696
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126457
126697
  ...provider === "seedream" ? {
126458
126698
  outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
126459
126699
  watermark: value?.seedreamWatermark !== false,
126460
126700
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126461
126701
  } : {}
126462
126702
  };
126703
+ for (const channel of resolveOpenAICompatChannels(value ?? {}).slice(1)) rows[compatChannelRowKey(channel.id)] = {
126704
+ ...emptyProviderRow(),
126705
+ ...compatRowFields(channel)
126706
+ };
126463
126707
  return rows;
126464
126708
  }
126465
126709
  /** Edit each provider independently, pick an explicit default, and verify keys inline. */
@@ -126473,6 +126717,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126473
126717
  const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
126474
126718
  const [providerMessage, setProviderMessage] = (0, react.useState)("");
126475
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)("");
126476
126722
  const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(() => props.scope.getSnapshot().value?.saveToWorkspace ?? true);
126477
126723
  const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)(() => props.scope.getSnapshot().value?.workspaceFolder ?? "dsh-image-gen");
126478
126724
  const [showPill, setShowPill] = (0, react.useState)(() => props.scope.getSnapshot().value?.showProviderPill === true);
@@ -126481,6 +126727,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126481
126727
  const [workspaceMessage, setWorkspaceMessage] = (0, react.useState)("");
126482
126728
  const [workspaceMessageIsError, setWorkspaceMessageIsError] = (0, react.useState)(false);
126483
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));
126484
126731
  const [keyTick, setKeyTick] = (0, react.useState)(0);
126485
126732
  const [subStatus, setSubStatus] = (0, react.useState)(() => ({
126486
126733
  "chatgpt-sub": { state: "unknown" },
@@ -126690,15 +126937,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126690
126937
  (0, react.useEffect)(() => {
126691
126938
  const value = snapshot.value;
126692
126939
  setDefaultProvider(value?.provider ?? "google");
126940
+ setCloudTimeoutSeconds((value?.cloudTimeoutMs ?? 15e4) / 1e3);
126693
126941
  setSaveToWorkspace(value?.saveToWorkspace ?? true);
126694
126942
  setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
126695
126943
  setShowPill(value?.showProviderPill === true);
126696
126944
  setRows((current) => {
126697
- const next = {};
126945
+ const next = { ...current };
126698
126946
  for (const provider of IMAGE_PROVIDERS) next[provider] = {
126699
126947
  ...current[provider],
126700
126948
  model: modelOf(provider, value),
126701
- ...provider === "openai-compat" ? { editModel: value?.openaiCompatEditModel ?? "" } : {},
126949
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126702
126950
  baseURL: baseURLOf(provider, value),
126703
126951
  ...provider === "comfyui" ? {
126704
126952
  workflows: resolveComfyUIWorkflows(value ?? {}),
@@ -126711,6 +126959,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126711
126959
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126712
126960
  } : {}
126713
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
+ }
126714
126969
  return next;
126715
126970
  });
126716
126971
  }, [snapshot]);
@@ -126718,11 +126973,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126718
126973
  const credentialsAvailable = props.credentialsAvailable;
126719
126974
  (0, react.useEffect)(() => {
126720
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
+ }))];
126721
126986
  if (!credentialsAvailable()) {
126722
126987
  setRows((current) => {
126723
126988
  const next = { ...current };
126724
- for (const provider of CLOUD_IMAGE_PROVIDERS) next[provider] = {
126725
- ...current[provider],
126989
+ for (const target of targets) next[target.key] = {
126990
+ ...current[target.key] ?? emptyProviderRow(),
126726
126991
  keyStatus: "unavailable",
126727
126992
  keyInfo: void 0
126728
126993
  };
@@ -126732,23 +126997,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126732
126997
  active = false;
126733
126998
  };
126734
126999
  }
126735
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
126736
- const keyRef = cloudCredentialRef(provider);
126737
- if (keyRef === void 0) continue;
127000
+ for (const target of targets) {
126738
127001
  setRows((current) => ({
126739
127002
  ...current,
126740
- [provider]: {
126741
- ...current[provider],
127003
+ [target.key]: {
127004
+ ...current[target.key],
126742
127005
  keyStatus: "checking"
126743
127006
  }
126744
127007
  }));
126745
- credentials.describe([keyRef]).then((response) => {
127008
+ credentials.describe([target.keyRef]).then((response) => {
126746
127009
  if (!active) return;
126747
- const info = response.ok ? response.value?.[keyRef] : void 0;
127010
+ const info = response.ok ? response.value?.[target.keyRef] : void 0;
126748
127011
  setRows((current) => ({
126749
127012
  ...current,
126750
- [provider]: {
126751
- ...current[provider],
127013
+ [target.key]: {
127014
+ ...current[target.key],
126752
127015
  keyStatus: response.ok ? info?.configured ? "configured" : "missing" : "unknown",
126753
127016
  keyInfo: info
126754
127017
  }
@@ -126757,8 +127020,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126757
127020
  if (!active) return;
126758
127021
  setRows((current) => ({
126759
127022
  ...current,
126760
- [provider]: {
126761
- ...current[provider],
127023
+ [target.key]: {
127024
+ ...current[target.key],
126762
127025
  keyStatus: "unknown"
126763
127026
  }
126764
127027
  }));
@@ -126770,13 +127033,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126770
127033
  }, [
126771
127034
  credentials,
126772
127035
  credentialsAvailable,
126773
- keyTick
127036
+ keyTick,
127037
+ compatChannels
126774
127038
  ]);
126775
- const updateRow = (provider, patch) => {
127039
+ const updateRow = (target, patch) => {
126776
127040
  setRows((current) => ({
126777
127041
  ...current,
126778
- [provider]: {
126779
- ...current[provider],
127042
+ [target]: {
127043
+ ...current[target],
126780
127044
  ...patch
126781
127045
  }
126782
127046
  }));
@@ -126792,15 +127056,49 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126792
127056
  setProviderMessageIsError(true);
126793
127057
  });
126794
127058
  };
126795
- const saveProviderRow = async (provider) => {
126796
- const row = rows[provider];
126797
- 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, {
126798
127063
  saving: true,
126799
127064
  message: "",
126800
127065
  messageIsError: false
126801
127066
  });
126802
127067
  try {
126803
- 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") {
126804
127102
  const entries = row.workflows.map((entry) => ({
126805
127103
  name: entry.name.trim(),
126806
127104
  json: entry.json,
@@ -126829,6 +127127,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126829
127127
  if (provider === "google") await saveSetting("googleNative", row.googleNative);
126830
127128
  if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126831
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
+ }));
126832
127135
  await saveSetting("openaiCompatEditModel", row.editModel.trim());
126833
127136
  await saveSetting("openaiCompatEditFormat", row.editFormat);
126834
127137
  await saveSetting("openaiCompatEditExtra", editExtra);
@@ -126849,13 +127152,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126849
127152
  if (!credentialsAvailable()) throw new Error(t("credentialsUnavailable"));
126850
127153
  const response = await props.credentials.set(keyRef, row.keyInput.trim());
126851
127154
  if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
126852
- updateRow(provider, {
127155
+ updateRow(target, {
126853
127156
  keyInput: "",
126854
127157
  keyStatus: "configured"
126855
127158
  });
126856
127159
  }
126857
127160
  }
126858
- updateRow(provider, {
127161
+ updateRow(target, {
126859
127162
  message: t("saved"),
126860
127163
  messageIsError: false,
126861
127164
  testResult: void 0,
@@ -126863,38 +127166,99 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126863
127166
  modelFetchIsError: false
126864
127167
  });
126865
127168
  } catch (cause) {
126866
- updateRow(provider, {
127169
+ updateRow(target, {
126867
127170
  message: cause instanceof Error ? cause.message : String(cause),
126868
127171
  messageIsError: true
126869
127172
  });
126870
127173
  } finally {
126871
- updateRow(provider, { saving: false });
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, {
127229
+ message: cause instanceof Error ? cause.message : String(cause),
127230
+ messageIsError: true
127231
+ });
127232
+ } finally {
127233
+ if (!removed) updateRow(target, { saving: false });
126872
127234
  }
126873
127235
  };
126874
127236
  /** These actions use host settings, so drafts must be saved before making a request. */
126875
- const providerActionBlockMessage = (provider) => {
126876
- const row = rows[provider];
127237
+ const providerActionBlockMessage = (provider, channel) => {
127238
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127239
+ const row = rows[target];
126877
127240
  if (row.keyInput.trim().length > 0) return t("saveKeyFirst");
126878
127241
  if (isSubscriptionProvider(provider)) return void 0;
126879
127242
  const address = row.baseURL.trim();
126880
127243
  if (address.length === 0) return t("endpointRequired");
126881
- 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");
126882
127245
  if (provider === "google" && row.googleNative !== (props.scope.getSnapshot().value?.googleNative === true)) return t("saveGoogleProtocolFirst");
126883
127246
  };
126884
127247
  /** Localize the host's missing-address error while preserving upstream details. */
126885
127248
  const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
126886
127249
  /** Probe through the host route so the browser side never touches credential values. */
126887
- const testConnection = async (provider) => {
126888
- 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);
126889
127253
  if (blocked !== void 0) {
126890
- updateRow(provider, {
127254
+ updateRow(target, {
126891
127255
  message: blocked,
126892
127256
  messageIsError: false,
126893
127257
  testResult: void 0
126894
127258
  });
126895
127259
  return;
126896
127260
  }
126897
- updateRow(provider, {
127261
+ updateRow(target, {
126898
127262
  testing: true,
126899
127263
  testResult: void 0,
126900
127264
  message: "",
@@ -126904,31 +127268,35 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126904
127268
  const response = await fetch(TEST_CONNECTION_ROUTE, {
126905
127269
  method: "POST",
126906
127270
  headers: { "content-type": "application/json" },
126907
- body: JSON.stringify({ provider })
127271
+ body: JSON.stringify({
127272
+ provider,
127273
+ ...channel === void 0 ? {} : { channel: channel.id }
127274
+ })
126908
127275
  });
126909
127276
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126910
- updateRow(provider, { testResult: await response.json() });
127277
+ updateRow(target, { testResult: await response.json() });
126911
127278
  } catch (cause) {
126912
- updateRow(provider, { testResult: {
127279
+ updateRow(target, { testResult: {
126913
127280
  ok: false,
126914
127281
  reason: "error",
126915
127282
  message: cause instanceof Error ? cause.message : String(cause)
126916
127283
  } });
126917
127284
  } finally {
126918
- updateRow(provider, { testing: false });
127285
+ updateRow(target, { testing: false });
126919
127286
  }
126920
127287
  };
126921
127288
  /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
126922
- const fetchProviderModels = async (provider) => {
126923
- 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);
126924
127292
  if (blocked !== void 0) {
126925
- updateRow(provider, {
127293
+ updateRow(target, {
126926
127294
  modelFetchMessage: blocked,
126927
127295
  modelFetchIsError: false
126928
127296
  });
126929
127297
  return;
126930
127298
  }
126931
- updateRow(provider, {
127299
+ updateRow(target, {
126932
127300
  fetchingModels: true,
126933
127301
  modelFetchMessage: "",
126934
127302
  modelFetchIsError: false
@@ -126939,50 +127307,52 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126939
127307
  headers: { "content-type": "application/json" },
126940
127308
  body: JSON.stringify({
126941
127309
  provider,
126942
- action: "models"
127310
+ action: "models",
127311
+ ...channel === void 0 ? {} : { channel: channel.id }
126943
127312
  })
126944
127313
  });
126945
127314
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126946
127315
  const payload = await response.json();
126947
127316
  if (payload.ok && Array.isArray(payload.models)) {
126948
127317
  const models = payload.models.filter((id) => typeof id === "string");
126949
- updateRow(provider, {
127318
+ updateRow(target, {
126950
127319
  modelOptions: models,
126951
127320
  modelFetchMessage: models.length > 0 ? t("modelsFound", { n: String(models.length) }) : t("modelsNone"),
126952
127321
  modelFetchIsError: models.length === 0
126953
127322
  });
126954
- } else if (payload.reason === "missing-key") updateRow(provider, {
127323
+ } else if (payload.reason === "missing-key") updateRow(target, {
126955
127324
  modelFetchMessage: t("badgeMissing"),
126956
127325
  modelFetchIsError: true
126957
127326
  });
126958
- else if (payload.reason === "unauthorized") updateRow(provider, {
127327
+ else if (payload.reason === "unauthorized") updateRow(target, {
126959
127328
  modelFetchMessage: t("testUnauthorized"),
126960
127329
  modelFetchIsError: true
126961
127330
  });
126962
- else updateRow(provider, {
127331
+ else updateRow(target, {
126963
127332
  modelFetchMessage: providerErrorMessage(payload.message),
126964
127333
  modelFetchIsError: true
126965
127334
  });
126966
127335
  } catch (cause) {
126967
- updateRow(provider, {
127336
+ updateRow(target, {
126968
127337
  modelFetchMessage: cause instanceof Error ? cause.message : String(cause),
126969
127338
  modelFetchIsError: true
126970
127339
  });
126971
127340
  } finally {
126972
- updateRow(provider, { fetchingModels: false });
127341
+ updateRow(target, { fetchingModels: false });
126973
127342
  }
126974
127343
  };
126975
- const clearProviderKey = async (provider) => {
126976
- 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;
126977
127347
  if (keyRef === void 0) return;
126978
127348
  if (typeof props.credentials.unset !== "function") {
126979
- updateRow(provider, {
127349
+ updateRow(target, {
126980
127350
  message: t("clearKeyUnsupported"),
126981
127351
  messageIsError: true
126982
127352
  });
126983
127353
  return;
126984
127354
  }
126985
- updateRow(provider, {
127355
+ updateRow(target, {
126986
127356
  saving: true,
126987
127357
  message: "",
126988
127358
  messageIsError: false
@@ -126990,7 +127360,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126990
127360
  try {
126991
127361
  const response = await props.credentials.unset(keyRef);
126992
127362
  if (!response.ok) throw new Error(response.error?.message ?? t("clearKeyFailed"));
126993
- updateRow(provider, {
127363
+ updateRow(target, {
126994
127364
  keyStatus: "missing",
126995
127365
  keyInput: "",
126996
127366
  testResult: void 0,
@@ -126998,12 +127368,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126998
127368
  messageIsError: false
126999
127369
  });
127000
127370
  } catch (cause) {
127001
- updateRow(provider, {
127371
+ updateRow(target, {
127002
127372
  message: cause instanceof Error ? cause.message : String(cause),
127003
127373
  messageIsError: true
127004
127374
  });
127005
127375
  } finally {
127006
- updateRow(provider, { saving: false });
127376
+ updateRow(target, { saving: false });
127007
127377
  }
127008
127378
  };
127009
127379
  const testResultText = (result) => {
@@ -127013,7 +127383,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127013
127383
  if (result.reason === "unauthorized") return t("testUnauthorized");
127014
127384
  return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${providerErrorMessage(result.message)}` : ""}`;
127015
127385
  };
127016
- const badgeOf = (provider) => {
127386
+ const badgeOf = (provider, channel) => {
127017
127387
  if (provider === "comfyui") return {
127018
127388
  text: t("comfyuiNoKey"),
127019
127389
  className: "dsh-ig-badge dsh-ig-badge-neutral"
@@ -127033,7 +127403,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127033
127403
  className: "dsh-ig-badge dsh-ig-badge-neutral"
127034
127404
  };
127035
127405
  }
127036
- const status = rows[provider].keyStatus;
127406
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127407
+ const status = rows[target].keyStatus;
127037
127408
  if (status === "checking") return {
127038
127409
  text: t("badgeChecking"),
127039
127410
  className: "dsh-ig-badge dsh-ig-badge-neutral dsh-ig-badge-checking"
@@ -127141,6 +127512,19 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127141
127512
  };
127142
127513
  });
127143
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
+ };
127144
127528
  /** Workspace checkbox persists immediately, like every other toggle on this card. */
127145
127529
  const toggleSaveToWorkspace = (next) => {
127146
127530
  setSaveToWorkspace(next);
@@ -127177,31 +127561,57 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127177
127561
  setUiMessageIsError(true);
127178
127562
  });
127179
127563
  };
127180
- const renderCloudBody = (provider) => {
127181
- 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`;
127182
127571
  const officialEditFormat = provider === "openai-compat" ? compatEditFormat({
127183
127572
  openaiCompatBaseURL: row.baseURL,
127184
127573
  openaiCompatModel: row.editModel.trim() || row.model
127185
127574
  }) : "multipart";
127186
127575
  const automaticEditFormat = officialEditFormat !== "multipart";
127187
127576
  const editFormat = automaticEditFormat ? officialEditFormat : row.editFormat;
127188
- const keyRef = cloudCredentialRef(provider) ?? "";
127577
+ const keyRef = channel === void 0 ? cloudCredentialRef(provider) ?? "" : channel.keyRef;
127189
127578
  const keyReadOnly = row.keyInfo?.writable === false;
127190
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
+ }) : [];
127191
127585
  return (0, react_jsx_runtime.jsx)("div", {
127192
127586
  className: "dsh-ig-provider-body",
127193
127587
  children: (0, react_jsx_runtime.jsxs)("form", {
127194
127588
  onSubmit: (event) => {
127195
127589
  event.preventDefault();
127196
- saveProviderRow(provider);
127590
+ saveProviderRow(provider, channel);
127197
127591
  },
127198
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,
127199
127609
  (0, react_jsx_runtime.jsxs)("label", {
127200
127610
  className: "dsh-ig-field",
127201
127611
  children: [
127202
127612
  (0, react_jsx_runtime.jsx)("span", {
127203
127613
  className: "dsh-ig-label",
127204
- children: t("apiKeyLabel", { provider: providerLabels[provider] })
127614
+ children: t("apiKeyLabel", { provider: providerLabel })
127205
127615
  }),
127206
127616
  (0, react_jsx_runtime.jsx)("input", {
127207
127617
  className: "dsh-ig-input",
@@ -127209,7 +127619,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127209
127619
  autoComplete: "off",
127210
127620
  value: row.keyInput,
127211
127621
  onChange: (event) => {
127212
- updateRow(provider, { keyInput: event.target.value });
127622
+ update({ keyInput: event.target.value });
127213
127623
  },
127214
127624
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
127215
127625
  disabled: !settingsWritable || keyReadOnly || keyUnavailable
@@ -127234,7 +127644,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127234
127644
  type: "url",
127235
127645
  value: row.baseURL,
127236
127646
  onChange: (event) => {
127237
- updateRow(provider, { baseURL: event.target.value });
127647
+ update({ baseURL: event.target.value });
127238
127648
  },
127239
127649
  required: true,
127240
127650
  disabled: !settingsWritable
@@ -127243,7 +127653,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127243
127653
  className: "dsh-ig-btn-reset",
127244
127654
  title: t("resetTitle"),
127245
127655
  onClick: () => {
127246
- updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
127656
+ update({ baseURL: DEFAULT_BASE_URLS[provider] });
127247
127657
  },
127248
127658
  disabled: !settingsWritable,
127249
127659
  children: t("reset")
@@ -127264,7 +127674,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127264
127674
  className: "dsh-ig-input",
127265
127675
  value: row.googleNative ? "native" : "interactions",
127266
127676
  onChange: (event) => {
127267
- updateRow(provider, { googleNative: event.target.value === "native" });
127677
+ update({ googleNative: event.target.value === "native" });
127268
127678
  },
127269
127679
  disabled: !settingsWritable,
127270
127680
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127281,7 +127691,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127281
127691
  children: [
127282
127692
  (0, react_jsx_runtime.jsx)("span", {
127283
127693
  className: "dsh-ig-label",
127284
- children: t("model")
127694
+ children: t(provider === "openai-compat" ? "defaultModel" : "model")
127285
127695
  }),
127286
127696
  modelPullSupported(provider) ? (0, react_jsx_runtime.jsxs)("div", {
127287
127697
  className: "dsh-ig-input-group",
@@ -127290,14 +127700,20 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127290
127700
  className: "dsh-ig-input",
127291
127701
  value: row.model,
127292
127702
  onChange: (event) => {
127293
- updateRow(provider, { model: event.target.value });
127703
+ update({ model: event.target.value });
127294
127704
  },
127295
- 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,
127296
127712
  required: provider !== "openai-compat",
127297
127713
  disabled: !settingsWritable
127298
127714
  }),
127299
127715
  (0, react_jsx_runtime.jsx)("datalist", {
127300
- id: `dsh-ig-${provider}-model-options`,
127716
+ id: modelListId,
127301
127717
  children: row.modelOptions.map((id) => (0, react_jsx_runtime.jsx)("option", { value: id }, id))
127302
127718
  }),
127303
127719
  (0, react_jsx_runtime.jsx)("button", {
@@ -127305,7 +127721,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127305
127721
  className: "dsh-ig-btn-secondary",
127306
127722
  disabled: row.fetchingModels || row.saving || !settingsWritable,
127307
127723
  onClick: () => {
127308
- fetchProviderModels(provider);
127724
+ fetchProviderModels(provider, channel);
127309
127725
  },
127310
127726
  children: row.fetchingModels ? t("fetchingModels") : t("fetchModels")
127311
127727
  })
@@ -127314,7 +127730,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127314
127730
  className: "dsh-ig-input",
127315
127731
  value: row.model,
127316
127732
  onChange: (event) => {
127317
- updateRow(provider, { model: event.target.value });
127733
+ update({ model: event.target.value });
127318
127734
  },
127319
127735
  required: true,
127320
127736
  disabled: !settingsWritable
@@ -127329,6 +127745,52 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127329
127745
  }) : null
127330
127746
  ]
127331
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,
127332
127794
  provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127333
127795
  className: "dsh-ig-field",
127334
127796
  children: [
@@ -127340,9 +127802,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127340
127802
  className: "dsh-ig-input",
127341
127803
  value: row.editModel,
127342
127804
  onChange: (event) => {
127343
- updateRow(provider, { editModel: event.target.value });
127805
+ update({ editModel: event.target.value });
127344
127806
  },
127345
- list: "dsh-ig-openai-compat-model-options",
127807
+ list: modelListId,
127346
127808
  disabled: !settingsWritable
127347
127809
  }),
127348
127810
  (0, react_jsx_runtime.jsx)("span", {
@@ -127362,7 +127824,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127362
127824
  className: "dsh-ig-input",
127363
127825
  value: automaticEditFormat ? "__official__" : row.editFormat,
127364
127826
  onChange: (event) => {
127365
- updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
127827
+ update({ editFormat: parseEditFormat(event.target.value) });
127366
127828
  },
127367
127829
  disabled: !settingsWritable || automaticEditFormat,
127368
127830
  children: [
@@ -127406,7 +127868,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127406
127868
  className: "dsh-ig-input",
127407
127869
  value: row.outputFormat,
127408
127870
  onChange: (event) => {
127409
- updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127871
+ update({ outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127410
127872
  },
127411
127873
  disabled: !settingsWritable,
127412
127874
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127434,7 +127896,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127434
127896
  className: "dsh-ig-input",
127435
127897
  value: row.watermark ? "on" : "off",
127436
127898
  onChange: (event) => {
127437
- updateRow(provider, { watermark: event.target.value === "on" });
127899
+ update({ watermark: event.target.value === "on" });
127438
127900
  },
127439
127901
  disabled: !settingsWritable,
127440
127902
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127462,7 +127924,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127462
127924
  className: "dsh-ig-input",
127463
127925
  value: row.background,
127464
127926
  onChange: (event) => {
127465
- updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
127927
+ update({ background: event.target.value === "transparent" ? "transparent" : "opaque" });
127466
127928
  },
127467
127929
  disabled: !settingsWritable,
127468
127930
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127491,7 +127953,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127491
127953
  className: "dsh-ig-input dsh-ig-textarea",
127492
127954
  value: row.sizeTableText,
127493
127955
  onChange: (event) => {
127494
- updateRow(provider, { sizeTableText: event.target.value });
127956
+ update({ sizeTableText: event.target.value });
127495
127957
  },
127496
127958
  rows: 3,
127497
127959
  disabled: !settingsWritable
@@ -127514,7 +127976,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127514
127976
  className: "dsh-ig-input",
127515
127977
  value: row.generationQuality,
127516
127978
  onChange: (event) => {
127517
- updateRow(provider, { generationQuality: event.target.value });
127979
+ update({ generationQuality: event.target.value });
127518
127980
  },
127519
127981
  placeholder: "high",
127520
127982
  disabled: !settingsWritable
@@ -127534,7 +127996,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127534
127996
  className: "dsh-ig-input",
127535
127997
  value: row.qualityOptionsText,
127536
127998
  onChange: (event) => {
127537
- updateRow(provider, { qualityOptionsText: event.target.value });
127999
+ update({ qualityOptionsText: event.target.value });
127538
128000
  },
127539
128001
  disabled: !settingsWritable
127540
128002
  })]
@@ -127550,7 +128012,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127550
128012
  className: "dsh-ig-input dsh-ig-textarea",
127551
128013
  value: row.generateExtraText,
127552
128014
  onChange: (event) => {
127553
- updateRow(provider, { generateExtraText: event.target.value });
128015
+ update({ generateExtraText: event.target.value });
127554
128016
  },
127555
128017
  placeholder: "{\"watermark\":false}",
127556
128018
  rows: 2,
@@ -127572,7 +128034,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127572
128034
  className: "dsh-ig-input",
127573
128035
  value: row.editSizeMode,
127574
128036
  onChange: (event) => {
127575
- updateRow(provider, { editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
128037
+ update({ editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
127576
128038
  },
127577
128039
  disabled: !settingsWritable,
127578
128040
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127594,7 +128056,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127594
128056
  className: "dsh-ig-input dsh-ig-textarea",
127595
128057
  value: row.editExtraText,
127596
128058
  onChange: (event) => {
127597
- updateRow(provider, { editExtraText: event.target.value });
128059
+ update({ editExtraText: event.target.value });
127598
128060
  },
127599
128061
  placeholder: t("editExtraPlaceholder"),
127600
128062
  rows: 2,
@@ -127620,7 +128082,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127620
128082
  className: "dsh-ig-btn-secondary",
127621
128083
  disabled: row.testing || row.saving,
127622
128084
  onClick: () => {
127623
- testConnection(provider);
128085
+ testConnection(provider, channel);
127624
128086
  },
127625
128087
  children: row.testing ? t("testing") : t("testConnection")
127626
128088
  }),
@@ -127629,7 +128091,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127629
128091
  className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
127630
128092
  disabled: row.saving,
127631
128093
  onClick: () => {
127632
- clearProviderKey(provider);
128094
+ clearProviderKey(provider, channel);
127633
128095
  },
127634
128096
  children: t("clearKey")
127635
128097
  }) : null,
@@ -127947,47 +128409,115 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127947
128409
  const renderProviderRow = (provider) => {
127948
128410
  const row = rows[provider];
127949
128411
  const badge = badgeOf(provider);
128412
+ const label = provider === "openai-compat" && row.label.trim().length > 0 ? row.label.trim() : providerLabels[provider];
127950
128413
  return (0, react_jsx_runtime.jsxs)("div", {
127951
128414
  className: "dsh-ig-provider-row",
127952
- children: [(0, react_jsx_runtime.jsxs)("button", {
127953
- type: "button",
127954
- className: "dsh-ig-provider-head",
127955
- "aria-expanded": row.expanded,
127956
- onClick: () => {
127957
- updateRow(provider, { expanded: !row.expanded });
127958
- },
127959
- children: [
127960
- (0, react_jsx_runtime.jsx)("span", {
127961
- className: "dsh-ig-provider-name",
127962
- children: providerLabels[provider]
127963
- }),
127964
- (0, react_jsx_runtime.jsxs)("span", {
127965
- className: badge.className,
127966
- title: badge.text,
127967
- children: [(0, react_jsx_runtime.jsx)("span", {
127968
- className: "dsh-ig-badge-dot",
127969
- "aria-hidden": "true"
127970
- }), badge.text]
127971
- }),
127972
- (0, react_jsx_runtime.jsx)("span", {
127973
- className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
127974
- "aria-hidden": "true",
127975
- children: (0, react_jsx_runtime.jsx)("svg", {
127976
- width: "12",
127977
- height: "12",
127978
- viewBox: "0 0 16 16",
127979
- fill: "none",
127980
- stroke: "currentColor",
127981
- strokeWidth: "2",
127982
- strokeLinecap: "round",
127983
- strokeLinejoin: "round",
127984
- 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
+ })
127985
128459
  })
127986
- })
127987
- ]
128460
+ ]
128461
+ })]
127988
128462
  }), row.expanded ? provider === "comfyui" ? renderComfyUIBody() : isSubscriptionProvider(provider) ? renderSubscriptionBody(provider) : renderCloudBody(provider) : null]
127989
128463
  }, provider);
127990
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
+ };
127991
128521
  return (0, react_jsx_runtime.jsxs)("li", {
127992
128522
  className: "dsh-ig-card",
127993
128523
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -128045,9 +128575,46 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128045
128575
  }) : null
128046
128576
  ]
128047
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
+ }),
128048
128615
  (0, react_jsx_runtime.jsx)("div", {
128049
128616
  className: "dsh-ig-providers",
128050
- children: IMAGE_PROVIDERS.map((provider) => renderProviderRow(provider))
128617
+ children: IMAGE_PROVIDERS.flatMap((provider) => [renderProviderRow(provider), ...provider === "openai-compat" ? compatChannels.map((channel) => renderCompatChannelRow(channel)) : []])
128051
128618
  }),
128052
128619
  (0, react_jsx_runtime.jsxs)("div", {
128053
128620
  className: "dsh-ig-section",
@@ -128212,6 +128779,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128212
128779
  const activeResult = selectedRevision !== void 0 ? {
128213
128780
  prompt: selectedRevision.prompt,
128214
128781
  provider: selectedRevision.provider,
128782
+ ...selectedRevision.channel === void 0 ? {} : { channel: selectedRevision.channel },
128215
128783
  model: selectedRevision.model,
128216
128784
  output: selectedRevision.output,
128217
128785
  createdAt: selectedRevision.createdAt,
@@ -128242,6 +128810,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128242
128810
  attachment: result.attachment,
128243
128811
  prompt: result.prompt,
128244
128812
  provider: result.provider,
128813
+ ...result.channel === void 0 ? {} : { channel: result.channel },
128245
128814
  model: result.model,
128246
128815
  output: result.output,
128247
128816
  ...result.savedTo ? { savedTo: result.savedTo } : {},
@@ -128380,6 +128949,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128380
128949
  attachment: payload.attachment,
128381
128950
  prompt: payload.prompt,
128382
128951
  provider: payload.provider,
128952
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
128383
128953
  model: payload.model,
128384
128954
  output: payload.output,
128385
128955
  createdAt: payload.createdAt,
@@ -128394,6 +128964,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128394
128964
  attachment: revision.attachment,
128395
128965
  prompt: revision.prompt,
128396
128966
  provider: revision.provider,
128967
+ ...revision.channel === void 0 ? {} : { channel: revision.channel },
128397
128968
  model: revision.model,
128398
128969
  createdAt: revision.createdAt,
128399
128970
  aspectRatio: revision.ratio,