dsh-image-gen 0.9.3 → 0.10.1

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,
@@ -122869,7 +122983,6 @@ img.ProseMirror-separator {
122869
122983
  };
122870
122984
  }, []);
122871
122985
  const [previewLoading, setPreviewLoading] = (0, react.useState)(false);
122872
- const blobCache = (0, react.useMemo)(() => /* @__PURE__ */ new Map(), []);
122873
122986
  const abortControllerRef = (0, react.useRef)(null);
122874
122987
  const filteredItems = (0, react.useMemo)(() => {
122875
122988
  return items.filter((item) => {
@@ -122925,7 +123038,6 @@ img.ProseMirror-separator {
122925
123038
  }
122926
123039
  setPreviewItem(item);
122927
123040
  if (blob) {
122928
- blobCache.set(item.id, blob);
122929
123041
  const url = URL.createObjectURL(blob);
122930
123042
  previewUrlRef.current = url;
122931
123043
  setPreviewBlob(blob);
@@ -122933,15 +123045,6 @@ img.ProseMirror-separator {
122933
123045
  setPreviewLoading(false);
122934
123046
  return;
122935
123047
  }
122936
- const cached = blobCache.get(item.id);
122937
- if (cached) {
122938
- const url = URL.createObjectURL(cached);
122939
- previewUrlRef.current = url;
122940
- setPreviewBlob(cached);
122941
- setPreviewUrl(url);
122942
- setPreviewLoading(false);
122943
- return;
122944
- }
122945
123048
  setPreviewLoading(true);
122946
123049
  setPreviewBlob(null);
122947
123050
  setPreviewUrl(null);
@@ -122949,7 +123052,6 @@ img.ProseMirror-separator {
122949
123052
  abortControllerRef.current = controller;
122950
123053
  fetchAttachmentBlob(item.attachment).then((fetchedBlob) => {
122951
123054
  if (controller.signal.aborted) return;
122952
- blobCache.set(item.id, fetchedBlob);
122953
123055
  const url = URL.createObjectURL(fetchedBlob);
122954
123056
  previewUrlRef.current = url;
122955
123057
  setPreviewBlob(fetchedBlob);
@@ -123036,6 +123138,7 @@ img.ProseMirror-separator {
123036
123138
  attachment: payload.attachment,
123037
123139
  prompt: payload.prompt,
123038
123140
  provider: payload.provider,
123141
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
123039
123142
  model: payload.model,
123040
123143
  createdAt: payload.createdAt,
123041
123144
  aspectRatio: request.ratio,
@@ -123212,11 +123315,7 @@ img.ProseMirror-separator {
123212
123315
  if (itemsToDownload.length === 1) {
123213
123316
  const single = itemsToDownload[0];
123214
123317
  if (!single) return;
123215
- let blob = blobCache.get(single.id);
123216
- if (!blob) {
123217
- blob = await fetchAttachmentBlob(single.attachment);
123218
- blobCache.set(single.id, blob);
123219
- }
123318
+ const blob = await fetchAttachmentBlob(single.attachment);
123220
123319
  const objectUrl = URL.createObjectURL(blob);
123221
123320
  downloadBlobUrl(objectUrl, `dsh-${single.provider}-${single.id}.png`);
123222
123321
  URL.revokeObjectURL(objectUrl);
@@ -123229,11 +123328,7 @@ img.ProseMirror-separator {
123229
123328
  const it = itemsToDownload[i];
123230
123329
  if (!it) continue;
123231
123330
  setBatchDownloadProgress(`${i + 1}/${itemsToDownload.length}`);
123232
- let blob = blobCache.get(it.id);
123233
- if (!blob) {
123234
- blob = await fetchAttachmentBlob(it.attachment);
123235
- blobCache.set(it.id, blob);
123236
- }
123331
+ const blob = await fetchAttachmentBlob(it.attachment);
123237
123332
  const arrayBuffer = await blob.arrayBuffer();
123238
123333
  const ext = blob.type === "image/jpeg" ? "jpg" : blob.type === "image/webp" ? "webp" : "png";
123239
123334
  let baseName = `dsh-${it.provider}-${it.id.slice(0, 8)}.${ext}`;
@@ -123637,7 +123732,6 @@ img.ProseMirror-separator {
123637
123732
  onToggleSelect: (e) => handleToggleSelect(item.id, idx, e),
123638
123733
  onRequestDelete: () => requestSingleDelete(item),
123639
123734
  onPreview: (blob) => openPreviewItem(item, blob),
123640
- onBlobLoaded: (b) => blobCache.set(item.id, b),
123641
123735
  onToast: showToast
123642
123736
  }, item.id))
123643
123737
  }) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
@@ -124012,39 +124106,37 @@ img.ProseMirror-separator {
124012
124106
  ]
124013
124107
  });
124014
124108
  };
124015
- const GalleryCard = ({ lang, item, index: _index, isManageMode, isSelected, t, onClick, onToggleSelect, onRequestDelete, onPreview: _onPreview, onBlobLoaded, onToast }) => {
124109
+ const GalleryCard = ({ lang, item, index: _index, isManageMode, isSelected, t, onClick, onToggleSelect, onRequestDelete, onPreview: _onPreview, onToast }) => {
124110
+ const [setCardRef, inView] = useInView();
124016
124111
  const [url, setUrl] = (0, react.useState)();
124017
124112
  const [blob, setBlob] = (0, react.useState)();
124018
124113
  const [loading, setLoading] = (0, react.useState)(true);
124019
124114
  const [error, setError] = (0, react.useState)();
124020
124115
  (0, react.useEffect)(() => {
124021
- const controller = new AbortController();
124116
+ if (!inView) return;
124117
+ let active = true;
124022
124118
  let objectUrl;
124023
- fetch(IMAGE_ROUTE, {
124024
- method: "POST",
124025
- signal: controller.signal,
124026
- headers: { "content-type": "application/json" },
124027
- body: JSON.stringify({ attachment: item.attachment })
124028
- }).then(async (response) => {
124029
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
124030
- const resBlob = await response.blob();
124031
- if (controller.signal.aborted) return;
124119
+ setUrl(void 0);
124120
+ setBlob(void 0);
124121
+ setError(void 0);
124122
+ setLoading(true);
124123
+ fetchAttachmentBlob(item.attachment).then((resBlob) => {
124124
+ if (!active) return;
124032
124125
  setBlob(resBlob);
124033
- onBlobLoaded?.(resBlob);
124034
124126
  objectUrl = URL.createObjectURL(resBlob);
124035
124127
  setUrl(objectUrl);
124036
124128
  setLoading(false);
124037
124129
  }).catch((err) => {
124038
- if (!controller.signal.aborted) {
124130
+ if (active) {
124039
124131
  setError(err instanceof Error ? err.message : String(err));
124040
124132
  setLoading(false);
124041
124133
  }
124042
124134
  });
124043
124135
  return () => {
124044
- controller.abort();
124136
+ active = false;
124045
124137
  if (objectUrl) URL.revokeObjectURL(objectUrl);
124046
124138
  };
124047
- }, [item.attachment]);
124139
+ }, [item.attachment.attachmentId, inView]);
124048
124140
  const copyPrompt = async (e) => {
124049
124141
  e.stopPropagation();
124050
124142
  try {
@@ -124075,6 +124167,7 @@ img.ProseMirror-separator {
124075
124167
  };
124076
124168
  const badgeLabel = item.model || item.provider;
124077
124169
  return (0, react_jsx_runtime.jsxs)("div", {
124170
+ ref: setCardRef,
124078
124171
  className: `dsh-ig-gallery-card ${isSelected ? "is-selected" : ""} ${isManageMode ? "is-manage-mode" : ""}`,
124079
124172
  onClick: (e) => onClick(e, blob),
124080
124173
  children: [(0, react_jsx_runtime.jsxs)("div", {
@@ -124303,6 +124396,7 @@ img.ProseMirror-separator {
124303
124396
  attachment,
124304
124397
  prompt: stringValue(meta.prompt, "Generated Image"),
124305
124398
  provider: stringValue(meta.provider, "google"),
124399
+ ...typeof meta.channel === "string" ? { channel: meta.channel } : {},
124306
124400
  model: stringValue(meta.model, ""),
124307
124401
  output: stringValue(meta.output, ""),
124308
124402
  ...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
@@ -124969,7 +125063,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124969
125063
  function isRevision(value) {
124970
125064
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
124971
125065
  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));
125066
+ 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
125067
  }
124974
125068
  function isAttachment(value) {
124975
125069
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -125096,16 +125190,51 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125096
125190
  function pillVisible(value) {
125097
125191
  return value?.showProviderPill === true;
125098
125192
  }
125193
+ const PILL_MODEL_FIELDS = {
125194
+ google: "googleModel",
125195
+ openai: "openaiModel",
125196
+ "openai-compat": "openaiCompatModel",
125197
+ seedream: "seedreamModel",
125198
+ dashscope: "dashscopeModel",
125199
+ xai: "xaiModel",
125200
+ zhipu: "zhipuModel"
125201
+ };
125202
+ const PILL_MODEL_OPTIONS_FIELDS = { "openai-compat": "openaiCompatModels" };
125099
125203
  /** Model (or ComfyUI workflow) a provider row will use with the persisted settings. */
125100
- function pillModelOf(provider, value) {
125204
+ function pillModelOf(provider, value, channelId) {
125101
125205
  if (provider === "comfyui") {
125102
125206
  const workflow = activeComfyUIWorkflow(value ?? {});
125103
125207
  return workflow === void 0 ? "" : workflow.name;
125104
125208
  }
125105
125209
  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;
125210
+ if (provider === "openai-compat") {
125211
+ const channel = resolveOpenAICompatChannel(value ?? {}, channelId);
125212
+ return channel.model.trim().length > 0 ? channel.model.trim() : DEFAULT_MODELS[provider];
125213
+ }
125214
+ const field = PILL_MODEL_FIELDS[provider];
125215
+ const stored = field === void 0 ? void 0 : value?.[field];
125107
125216
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
125108
125217
  }
125218
+ /** Optional model choices exposed by a provider; single-model providers return an empty list. */
125219
+ function pillModelOptionsOf(provider, value, channelId) {
125220
+ if (provider === "openai-compat") {
125221
+ const channel = resolveOpenAICompatChannel(value ?? {}, channelId);
125222
+ return resolveOpenAICompatModels({
125223
+ openaiCompatModel: pillModelOf(provider, value, channel.id),
125224
+ openaiCompatModels: channel.models ?? []
125225
+ });
125226
+ }
125227
+ const field = PILL_MODEL_OPTIONS_FIELDS[provider];
125228
+ if (field === void 0) return [];
125229
+ const configured = value?.[field];
125230
+ const models = Array.isArray(configured) ? [...new Set(configured.filter((model) => typeof model === "string").map((model) => model.trim()).filter(Boolean))] : [];
125231
+ const defaultModel = pillModelOf(provider, value);
125232
+ if (defaultModel.length > 0 && !models.includes(defaultModel)) models.unshift(defaultModel);
125233
+ return models;
125234
+ }
125235
+ function pillTargetKey(provider, channelId) {
125236
+ return provider === "openai-compat" ? `openai-compat:${channelId ?? "default"}` : provider;
125237
+ }
125109
125238
  /** One fixed-position provider menu anchored to the pill button. */
125110
125239
  function ImageProviderPill(props) {
125111
125240
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
@@ -125113,15 +125242,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125113
125242
  const [open, setOpen] = (0, react.useState)(false);
125114
125243
  const [pending, setPending] = (0, react.useState)(void 0);
125115
125244
  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
- });
125245
+ const [dots, setDots] = (0, react.useState)({});
125125
125246
  const [keyTick, setKeyTick] = (0, react.useState)(0);
125126
125247
  const [subDots, setSubDots] = (0, react.useState)(() => ({
125127
125248
  "chatgpt-sub": "unknown",
@@ -125141,35 +125262,33 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125141
125262
  }), [props.credentialEvents]);
125142
125263
  (0, react.useEffect)(() => {
125143
125264
  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) => {
125265
+ const targets = [...CLOUD_IMAGE_PROVIDERS.filter((provider) => provider !== "openai-compat").map((provider) => ({
125266
+ key: provider,
125267
+ ref: cloudCredentialRef(provider)
125268
+ })), ...resolveOpenAICompatChannels(snapshot.value ?? {}).map((channel) => ({
125269
+ key: pillTargetKey("openai-compat", channel.id),
125270
+ ref: channel.keyRef
125271
+ }))];
125272
+ props.credentials.describe(targets.map((target) => target.ref)).then((response) => {
125150
125273
  if (!active) return;
125151
125274
  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";
125275
+ for (const target of targets) {
125276
+ const info = response.ok ? response.value?.[target.ref] : void 0;
125277
+ next[target.key] = response.ok ? info?.configured ? "configured" : "missing" : "unknown";
125155
125278
  }
125156
125279
  setDots(next);
125157
125280
  }).catch(() => {
125158
125281
  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
- });
125282
+ setDots({});
125168
125283
  });
125169
125284
  return () => {
125170
125285
  active = false;
125171
125286
  };
125172
- }, [props.credentials, keyTick]);
125287
+ }, [
125288
+ props.credentials,
125289
+ keyTick,
125290
+ snapshot.value
125291
+ ]);
125173
125292
  (0, react.useEffect)(() => {
125174
125293
  if (!open) return;
125175
125294
  let active = true;
@@ -125197,6 +125316,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125197
125316
  }, [open, keyTick]);
125198
125317
  const t = (keyName) => (lang === "en" ? PILL_DICT.en : PILL_DICT.zh)[keyName];
125199
125318
  const current = snapshot.value?.provider ?? "google";
125319
+ const currentChannel = current === "openai-compat" ? resolveOpenAICompatChannel(snapshot.value ?? {}) : void 0;
125200
125320
  const writable = snapshot.writable;
125201
125321
  (0, react.useEffect)(() => {
125202
125322
  if (!open) {
@@ -125244,15 +125364,27 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125244
125364
  };
125245
125365
  }, [open]);
125246
125366
  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) {
125367
+ const select = (provider, model, channelId) => {
125368
+ const currentModel = pillModelOf(provider, snapshot.value, channelId);
125369
+ const sameChannel = provider !== "openai-compat" || currentChannel?.id === (channelId ?? "default");
125370
+ if (pending !== void 0 || provider === current && sameChannel && (model === void 0 || model === currentModel)) {
125250
125371
  setOpen(false);
125251
125372
  return;
125252
125373
  }
125374
+ const modelField = model === void 0 ? void 0 : PILL_MODEL_FIELDS[provider];
125253
125375
  setError("");
125254
125376
  setPending(provider);
125255
- writeSetting(props.scope, "provider", provider, t("writeRejected")).then(() => {
125377
+ (async () => {
125378
+ if (model !== void 0 && provider === "openai-compat" && channelId !== void 0 && channelId !== "default") {
125379
+ const channels = (snapshot.value?.openaiCompatChannels ?? []).map((channel) => channel.id === channelId ? {
125380
+ ...channel,
125381
+ model
125382
+ } : channel);
125383
+ await writeSetting(props.scope, "openaiCompatChannels", channels, t("writeRejected"));
125384
+ } else if (model !== void 0 && modelField !== void 0) await writeSetting(props.scope, modelField, model, t("writeRejected"));
125385
+ if (provider === "openai-compat" && channelId !== void 0 && currentChannel?.id !== channelId) await writeSetting(props.scope, "openaiCompatActiveChannel", channelId, t("writeRejected"));
125386
+ if (provider !== current) await writeSetting(props.scope, "provider", provider, t("writeRejected"));
125387
+ })().then(() => {
125256
125388
  setOpen(false);
125257
125389
  }).catch((cause) => {
125258
125390
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -125263,13 +125395,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125263
125395
  /** Plan A dots: ComfyUI is always fixed blue (theme-independent); each
125264
125396
  * subscription reflects its login state (green in, gray out); cloud rows
125265
125397
  * reflect their API-key state as before. */
125266
- const dotClassOf = (provider) => {
125398
+ const dotClassOf = (provider, channelId) => {
125267
125399
  if (provider === "comfyui") return "dsh-ig-pill-dot-neutral";
125268
125400
  if (isSubscriptionProvider(provider)) return subDots[provider] === "logged-in" ? "dsh-ig-pill-dot-ok" : "dsh-ig-pill-dot-missing";
125269
- const dot = dots[provider];
125401
+ const dot = dots[pillTargetKey(provider, channelId)];
125270
125402
  return dot === "configured" ? "dsh-ig-pill-dot-ok" : dot === "missing" ? "dsh-ig-pill-dot-missing" : "dsh-ig-pill-dot-neutral";
125271
125403
  };
125272
- const subtitleOf = (provider) => {
125404
+ const subtitleOf = (provider, includeModel = true, channelId) => {
125273
125405
  if (provider === "comfyui") {
125274
125406
  const workflow = pillModelOf(provider, snapshot.value);
125275
125407
  return workflow.length > 0 ? `${t("noKeyNeeded")} · ${workflow}` : `${t("noKeyNeeded")} · ${t("noWorkflow")}`;
@@ -125279,12 +125411,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125279
125411
  const stateText = state === "logged-in" ? t("subSignedIn") : state === "logged-out" ? t("subSignedOut") : t("subStatusUnknown");
125280
125412
  return `${t("subNoKey")} · ${stateText} · ${pillModelOf(provider, snapshot.value)}`;
125281
125413
  }
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;
125414
+ const dot = dots[pillTargetKey(provider, channelId)];
125415
+ const model = pillModelOf(provider, snapshot.value, channelId);
125416
+ const modelSuffix = includeModel ? ` · ${model}` : "";
125417
+ if (dot === "configured") return `${t("keyConfigured")}${modelSuffix}`;
125418
+ if (dot === "missing") return `${t("keyMissing")}${modelSuffix}`;
125419
+ return includeModel ? model : "";
125287
125420
  };
125421
+ const menuEntries = IMAGE_PROVIDERS.flatMap((provider) => provider === "openai-compat" ? resolveOpenAICompatChannels(snapshot.value ?? {}).map((channel) => ({
125422
+ provider,
125423
+ channel
125424
+ })) : [{ provider }]);
125288
125425
  return (0, react_jsx_runtime.jsxs)("div", {
125289
125426
  className: "dsh-ig-pill-root",
125290
125427
  children: [(0, react_jsx_runtime.jsxs)("button", {
@@ -125328,7 +125465,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125328
125465
  }),
125329
125466
  (0, react_jsx_runtime.jsx)("span", {
125330
125467
  className: "dsh-ig-pill-provider",
125331
- children: PILL_PROVIDER_LABELS[lang][current]
125468
+ children: currentChannel?.label ?? PILL_PROVIDER_LABELS[lang][current]
125332
125469
  }),
125333
125470
  (0, react_jsx_runtime.jsx)("span", {
125334
125471
  className: `dsh-ig-pill-caret${open ? " dsh-ig-pill-caret-open" : ""}`,
@@ -125356,47 +125493,83 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125356
125493
  className: "dsh-ig-pill-menu-title",
125357
125494
  children: t("menuTitle")
125358
125495
  }),
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",
125496
+ menuEntries.map(({ provider, channel }) => {
125497
+ const channelId = channel?.id;
125498
+ const modelOptions = pillModelOptionsOf(provider, snapshot.value, channelId);
125499
+ const active = provider === current && (provider !== "openai-compat" || currentChannel?.id === channelId);
125500
+ const currentModel = active ? pillModelOf(provider, snapshot.value, channelId) : "";
125501
+ return (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsxs)("button", {
125502
+ type: "button",
125503
+ role: modelOptions.length > 0 ? "menuitem" : "menuitemradio",
125504
+ "aria-checked": modelOptions.length > 0 ? void 0 : active,
125505
+ className: `dsh-ig-pill-option${active ? " dsh-ig-pill-option-active" : ""}`,
125506
+ disabled: pending !== void 0,
125507
+ onClick: () => {
125508
+ select(provider, void 0, channelId);
125509
+ },
125510
+ children: [
125511
+ (0, react_jsx_runtime.jsx)("span", {
125512
+ className: `dsh-ig-pill-dot ${dotClassOf(provider, channelId)}`,
125513
+ "aria-hidden": "true"
125514
+ }),
125515
+ (0, react_jsx_runtime.jsxs)("span", {
125516
+ className: "dsh-ig-pill-option-text",
125517
+ children: [(0, react_jsx_runtime.jsx)("span", {
125518
+ className: "dsh-ig-pill-option-name",
125519
+ children: channel?.label ?? PILL_PROVIDER_LABELS[lang][provider]
125520
+ }), (0, react_jsx_runtime.jsx)("span", {
125521
+ className: "dsh-ig-pill-option-sub",
125522
+ children: subtitleOf(provider, modelOptions.length === 0, channelId)
125523
+ })]
125524
+ }),
125525
+ active && modelOptions.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
125526
+ className: "dsh-ig-pill-check",
125527
+ "aria-hidden": "true",
125528
+ children: (0, react_jsx_runtime.jsx)("svg", {
125529
+ width: "13",
125530
+ height: "13",
125531
+ viewBox: "0 0 16 16",
125532
+ fill: "none",
125533
+ stroke: "currentColor",
125534
+ strokeWidth: "2.2",
125535
+ strokeLinecap: "round",
125536
+ strokeLinejoin: "round",
125537
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125538
+ })
125539
+ }) : null
125540
+ ]
125541
+ }), modelOptions.map((model) => {
125542
+ const selected = active && model === currentModel;
125543
+ return (0, react_jsx_runtime.jsxs)("button", {
125544
+ type: "button",
125545
+ role: "menuitemradio",
125546
+ "aria-checked": selected,
125547
+ className: `dsh-ig-pill-option dsh-ig-pill-model-option${selected ? " dsh-ig-pill-option-active" : ""}`,
125548
+ disabled: pending !== void 0,
125549
+ onClick: () => {
125550
+ select(provider, model, channelId);
125551
+ },
125375
125552
  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)),
125553
+ className: "dsh-ig-pill-model-name",
125554
+ children: model
125555
+ }), selected ? (0, react_jsx_runtime.jsx)("span", {
125556
+ className: "dsh-ig-pill-check",
125557
+ "aria-hidden": "true",
125558
+ children: (0, react_jsx_runtime.jsx)("svg", {
125559
+ width: "13",
125560
+ height: "13",
125561
+ viewBox: "0 0 16 16",
125562
+ fill: "none",
125563
+ stroke: "currentColor",
125564
+ strokeWidth: "2.2",
125565
+ strokeLinecap: "round",
125566
+ strokeLinejoin: "round",
125567
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8.5l3.5 3.5 7-8" })
125568
+ })
125569
+ }) : null]
125570
+ }, model);
125571
+ })] }, channelId === void 0 ? provider : pillTargetKey("openai-compat", channelId));
125572
+ }),
125400
125573
  error.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
125401
125574
  className: "dsh-ig-pill-error",
125402
125575
  role: "alert",
@@ -125440,12 +125613,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125440
125613
  .dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
125441
125614
  .dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}
125442
125615
  .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}
125616
+ .dsh-ig-pill-model-option{padding-left:24px}
125617
+ .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
125618
  .dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}
125444
125619
  .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
125620
  `;
125446
125621
  //#endregion
125447
125622
  //#region lib/types/client/index.js
125448
- console.info(`[dsh-image-gen] canvas bundle 2026-10-26T00:24:12.240Z`);
125623
+ console.info(`[dsh-image-gen] canvas bundle 2026-11-01T16:40:54.993Z`);
125449
125624
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
125450
125625
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
125451
125626
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -125464,6 +125639,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125464
125639
  providerGoogle: "Google Gemini",
125465
125640
  providerOpenAI: "OpenAI",
125466
125641
  providerOpenAICompat: "OpenAI 兼容(中转站)",
125642
+ addCompatChannel: "新增 OpenAI 兼容渠道",
125643
+ channelName: "渠道名称",
125644
+ channelNameRequired: "请填写渠道名称。",
125645
+ removeChannel: "删除渠道",
125646
+ removeChannelConfirm: "确定删除“{label}”吗?该渠道的配置将被移除。",
125467
125647
  providerSeedream: "字节 Seedream",
125468
125648
  providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
125469
125649
  providerXAI: "xAI Grok Imagine",
@@ -125570,6 +125750,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125570
125750
  endpointHintZhipu: "智谱开放平台 open.bigmodel.cn 的 /api/paas/v4 地址。",
125571
125751
  endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
125572
125752
  model: "模型",
125753
+ defaultModel: "默认模型",
125754
+ availableModels: "工作台可选模型",
125755
+ availableModelsHint: "勾选后可在工作台切换。手动输入的默认模型会自动加入列表,且不可移除。",
125756
+ defaultModelTag: "默认",
125573
125757
  workflow: "API Workflow 工作流",
125574
125758
  workflowImport: "导入 JSON 文件",
125575
125759
  workflowMissing: "尚未导入工作流",
@@ -125584,6 +125768,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125584
125768
  workflowDuplicateName: "工作流名称不能重复。",
125585
125769
  timeout: "生成超时(秒)",
125586
125770
  timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
125771
+ cloudTimeout: "云端生成超时(秒)",
125772
+ cloudTimeoutHint: "工作台所有 API Key 通道共用,5–3600 秒,默认 150 秒。不影响订阅通道和 ComfyUI;修改后对新请求生效。",
125587
125773
  workspaceSection: "工作区",
125588
125774
  saveToWorkspace: "保存到工作区",
125589
125775
  saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
@@ -125637,7 +125823,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125637
125823
  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
125824
  providerGoogle: "Google Gemini",
125639
125825
  providerOpenAI: "OpenAI",
125640
- providerOpenAICompat: "OpenAI-compatible (relay)",
125826
+ providerOpenAICompat: "OpenAI Compatible (relay)",
125827
+ addCompatChannel: "Add OpenAI-compatible channel",
125828
+ channelName: "Channel name",
125829
+ channelNameRequired: "Enter a channel name.",
125830
+ removeChannel: "Remove channel",
125831
+ removeChannelConfirm: "Remove \"{label}\"? Its channel configuration will be deleted.",
125641
125832
  providerSeedream: "ByteDance Seedream",
125642
125833
  providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
125643
125834
  providerXAI: "xAI Grok Imagine",
@@ -125744,6 +125935,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125744
125935
  endpointHintZhipu: "Zhipu open.bigmodel.cn /api/paas/v4 base URL.",
125745
125936
  endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
125746
125937
  model: "Model",
125938
+ defaultModel: "Default model",
125939
+ availableModels: "Models available in Studio",
125940
+ availableModelsHint: "Selected models can be switched in Studio. A manually entered default is added automatically and cannot be removed.",
125941
+ defaultModelTag: "Default",
125747
125942
  workflow: "API Workflows",
125748
125943
  workflowImport: "Import JSON file",
125749
125944
  workflowMissing: "No workflow imported",
@@ -125758,6 +125953,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125758
125953
  workflowDuplicateName: "Workflow names must be unique.",
125759
125954
  timeout: "Generation timeout (seconds)",
125760
125955
  timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
125956
+ cloudTimeout: "Cloud generation timeout (seconds)",
125957
+ 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
125958
  workspaceSection: "Workspace",
125762
125959
  saveToWorkspace: "Save to workspace",
125763
125960
  saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
@@ -125834,26 +126031,35 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125834
126031
  .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
126032
  .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
125836
126033
  .dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
126034
+ .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}
126035
+ .dsh-ig-model-picker .dsh-ig-check-row{min-width:0}
126036
+ .dsh-ig-model-option{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}
126037
+ .dsh-ig-model-default{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:11px}
125837
126038
  .dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}
125838
126039
  .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
126040
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
125840
126041
 
125841
126042
  /* Provider list: one expandable row per provider, each saving independently. */
125842
- .dsh-ig-providers{display:flex;flex-direction:column;margin-top:6px}
125843
- .dsh-ig-provider-row{border:0;background:none;padding:14px 0}
125844
- .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}
126043
+ .dsh-ig-providers{display:flex;flex-direction:column;margin-top:12px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#e8ebf0);border-radius:10px;background:var(--dsw-alias-bg-layer-3,transparent)}
126044
+ .dsh-ig-provider-row{border:0;background:none;padding:0 14px}
126045
+ .dsh-ig-provider-row + .dsh-ig-provider-row{border-top:1px solid var(--dsw-alias-border-l2,#e8ebf0)}
126046
+ .dsh-ig-provider-head-row{display:flex;align-items:center;gap:8px;min-height:48px}
126047
+ .dsh-ig-provider-head{flex:1;min-width:0;width:100%;min-height:48px;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}
126048
+ .dsh-ig-channel-add.dsh-ig-btn-secondary{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:26px;height:26px;padding:0;border:0;border-radius:7px;background:var(--dsw-alias-bg-layer-2,#f3f4f6);color:var(--dsw-alias-label-tertiary,#7b818b);line-height:1;transition:background .15s,color .15s}
126049
+ .dsh-ig-channel-add.dsh-ig-btn-secondary:hover:not(:disabled){border:0;background:var(--dsw-alias-bg-layer-1,#e5e7eb);color:var(--dsw-alias-label-primary,inherit)}
126050
+ .dsh-ig-channel-add svg{width:14px;height:14px}
125846
126051
  .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
- .dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-secondary,inherit)}
125848
- .dsh-ig-provider-name{flex:1;min-width:0;font-size:13.5px;font-weight:550;color:var(--dsw-alias-label-primary,inherit)}
125849
- .dsh-ig-provider-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s;display:inline-flex;align-items:center}
126052
+ .dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-primary,inherit)}
126053
+ .dsh-ig-provider-head:hover .dsh-ig-provider-chevron{color:var(--dsw-alias-label-primary,inherit)}
126054
+ .dsh-ig-provider-name{flex:1;min-width:0;font-size:13px;font-weight:550;line-height:20px;color:var(--dsw-alias-label-primary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
126055
+ .dsh-ig-provider-chevron{display:inline-flex;align-items:center;justify-content:center;flex:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s,color .15s}
125850
126056
  .dsh-ig-provider-chevron-open{transform:rotate(180deg)}
125851
- .dsh-ig-provider-body{border-top:0;padding:8px 0 2px}
125852
- .dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;font-size:11.5px;line-height:1.6;padding:2px 9px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}
125853
- .dsh-ig-badge-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
125854
- .dsh-ig-badge-ok{border-color:rgba(34,197,94,.45);color:#15803d;background:rgba(34,197,94,.08)}
125855
- .dsh-ig-badge-missing{border-color:rgba(239,68,68,.4);color:#b91c1c;background:rgba(239,68,68,.06)}
125856
- .dsh-ig-badge-neutral{color:var(--dsw-alias-label-tertiary,#7b818b)}
126057
+ .dsh-ig-provider-body{border-top:0;padding:0 0 14px}
126058
+ .dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;box-sizing:border-box;min-height:22px;padding:1px 8px;border:1px solid transparent;border-radius:6px;font-size:11px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}
126059
+ .dsh-ig-badge-dot{width:5px;height:5px;border-radius:50%;background:currentColor;flex:none}
126060
+ .dsh-ig-badge-ok{border-color:rgba(34,197,94,.2);color:#15803d;background:rgba(34,197,94,.08)}
126061
+ .dsh-ig-badge-missing{border-color:rgba(239,68,68,.18);color:#b91c1c;background:rgba(239,68,68,.06)}
126062
+ .dsh-ig-badge-neutral{border-color:var(--dsw-alias-border-l2,#e8ebf0);color:var(--dsw-alias-label-tertiary,#7b818b);background:var(--dsw-alias-bg-layer-2,transparent)}
125857
126063
  .dsh-ig-badge-checking .dsh-ig-badge-dot{animation:dsh-ig-pulse 1s ease-in-out infinite}
125858
126064
  @keyframes dsh-ig-pulse{50%{opacity:.25}}
125859
126065
 
@@ -126344,7 +126550,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126344
126550
  function emptyProviderRow() {
126345
126551
  return {
126346
126552
  expanded: false,
126553
+ label: "",
126347
126554
  model: "",
126555
+ models: [],
126348
126556
  editModel: "",
126349
126557
  baseURL: "",
126350
126558
  googleNative: false,
@@ -126430,6 +126638,28 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126430
126638
  function parseEditFormat(value) {
126431
126639
  return value === "jsonImageUrlArray" || value === "formReferenceImages" || value === "siliconflowJson" ? value : "multipart";
126432
126640
  }
126641
+ function compatChannelRowKey(channelId) {
126642
+ return `openai-compat:${channelId}`;
126643
+ }
126644
+ function compatRowFields(channel) {
126645
+ return {
126646
+ label: channel.label,
126647
+ model: channel.model,
126648
+ models: resolveOpenAICompatModels({
126649
+ openaiCompatModel: channel.model,
126650
+ openaiCompatModels: channel.models ?? []
126651
+ }),
126652
+ editModel: channel.editModel ?? "",
126653
+ baseURL: channel.baseURL,
126654
+ editFormat: parseEditFormat(channel.editFormat),
126655
+ editExtraText: editExtraTextOf(channel.editExtra),
126656
+ sizeTableText: editExtraTextOf(channel.sizes),
126657
+ generationQuality: channel.quality ?? "",
126658
+ qualityOptionsText: (channel.qualityOptions !== void 0 && channel.qualityOptions.length > 0 ? channel.qualityOptions : DEFAULT_COMPAT_QUALITIES).join(", "),
126659
+ generateExtraText: editExtraTextOf(channel.extraBody),
126660
+ editSizeMode: channel.editSizeMode === "selected" ? "selected" : "auto"
126661
+ };
126662
+ }
126433
126663
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
126434
126664
  function rowsFromSettings(value) {
126435
126665
  const rows = {};
@@ -126444,22 +126674,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126444
126674
  activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
126445
126675
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
126446
126676
  } : {},
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
- } : {},
126677
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126457
126678
  ...provider === "seedream" ? {
126458
126679
  outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
126459
126680
  watermark: value?.seedreamWatermark !== false,
126460
126681
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126461
126682
  } : {}
126462
126683
  };
126684
+ for (const channel of resolveOpenAICompatChannels(value ?? {}).slice(1)) rows[compatChannelRowKey(channel.id)] = {
126685
+ ...emptyProviderRow(),
126686
+ ...compatRowFields(channel)
126687
+ };
126463
126688
  return rows;
126464
126689
  }
126465
126690
  /** Edit each provider independently, pick an explicit default, and verify keys inline. */
@@ -126473,6 +126698,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126473
126698
  const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
126474
126699
  const [providerMessage, setProviderMessage] = (0, react.useState)("");
126475
126700
  const [providerMessageIsError, setProviderMessageIsError] = (0, react.useState)(false);
126701
+ const [cloudTimeoutSeconds, setCloudTimeoutSeconds] = (0, react.useState)(() => (props.scope.getSnapshot().value?.cloudTimeoutMs ?? 15e4) / 1e3);
126702
+ const [cloudTimeoutMessage, setCloudTimeoutMessage] = (0, react.useState)("");
126476
126703
  const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(() => props.scope.getSnapshot().value?.saveToWorkspace ?? true);
126477
126704
  const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)(() => props.scope.getSnapshot().value?.workspaceFolder ?? "dsh-image-gen");
126478
126705
  const [showPill, setShowPill] = (0, react.useState)(() => props.scope.getSnapshot().value?.showProviderPill === true);
@@ -126481,6 +126708,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126481
126708
  const [workspaceMessage, setWorkspaceMessage] = (0, react.useState)("");
126482
126709
  const [workspaceMessageIsError, setWorkspaceMessageIsError] = (0, react.useState)(false);
126483
126710
  const [rows, setRows] = (0, react.useState)(() => rowsFromSettings(props.scope.getSnapshot().value));
126711
+ const [compatChannels, setCompatChannels] = (0, react.useState)(() => resolveOpenAICompatChannels(props.scope.getSnapshot().value ?? {}).slice(1));
126484
126712
  const [keyTick, setKeyTick] = (0, react.useState)(0);
126485
126713
  const [subStatus, setSubStatus] = (0, react.useState)(() => ({
126486
126714
  "chatgpt-sub": { state: "unknown" },
@@ -126690,15 +126918,16 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126690
126918
  (0, react.useEffect)(() => {
126691
126919
  const value = snapshot.value;
126692
126920
  setDefaultProvider(value?.provider ?? "google");
126921
+ setCloudTimeoutSeconds((value?.cloudTimeoutMs ?? 15e4) / 1e3);
126693
126922
  setSaveToWorkspace(value?.saveToWorkspace ?? true);
126694
126923
  setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
126695
126924
  setShowPill(value?.showProviderPill === true);
126696
126925
  setRows((current) => {
126697
- const next = {};
126926
+ const next = { ...current };
126698
126927
  for (const provider of IMAGE_PROVIDERS) next[provider] = {
126699
126928
  ...current[provider],
126700
126929
  model: modelOf(provider, value),
126701
- ...provider === "openai-compat" ? { editModel: value?.openaiCompatEditModel ?? "" } : {},
126930
+ ...provider === "openai-compat" ? compatRowFields(resolveOpenAICompatChannels(value ?? {})[0]) : {},
126702
126931
  baseURL: baseURLOf(provider, value),
126703
126932
  ...provider === "comfyui" ? {
126704
126933
  workflows: resolveComfyUIWorkflows(value ?? {}),
@@ -126711,6 +126940,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126711
126940
  background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
126712
126941
  } : {}
126713
126942
  };
126943
+ for (const channel of resolveOpenAICompatChannels(value ?? {}).slice(1)) {
126944
+ const key = compatChannelRowKey(channel.id);
126945
+ next[key] = {
126946
+ ...current[key] ?? emptyProviderRow(),
126947
+ ...compatRowFields(channel)
126948
+ };
126949
+ }
126714
126950
  return next;
126715
126951
  });
126716
126952
  }, [snapshot]);
@@ -126718,11 +126954,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126718
126954
  const credentialsAvailable = props.credentialsAvailable;
126719
126955
  (0, react.useEffect)(() => {
126720
126956
  let active = true;
126957
+ const targets = [...CLOUD_IMAGE_PROVIDERS.flatMap((provider) => {
126958
+ const keyRef = cloudCredentialRef(provider);
126959
+ return keyRef === void 0 ? [] : [{
126960
+ key: provider,
126961
+ keyRef
126962
+ }];
126963
+ }), ...compatChannels.map((channel) => ({
126964
+ key: compatChannelRowKey(channel.id),
126965
+ keyRef: channel.keyRef
126966
+ }))];
126721
126967
  if (!credentialsAvailable()) {
126722
126968
  setRows((current) => {
126723
126969
  const next = { ...current };
126724
- for (const provider of CLOUD_IMAGE_PROVIDERS) next[provider] = {
126725
- ...current[provider],
126970
+ for (const target of targets) next[target.key] = {
126971
+ ...current[target.key] ?? emptyProviderRow(),
126726
126972
  keyStatus: "unavailable",
126727
126973
  keyInfo: void 0
126728
126974
  };
@@ -126732,23 +126978,21 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126732
126978
  active = false;
126733
126979
  };
126734
126980
  }
126735
- for (const provider of CLOUD_IMAGE_PROVIDERS) {
126736
- const keyRef = cloudCredentialRef(provider);
126737
- if (keyRef === void 0) continue;
126981
+ for (const target of targets) {
126738
126982
  setRows((current) => ({
126739
126983
  ...current,
126740
- [provider]: {
126741
- ...current[provider],
126984
+ [target.key]: {
126985
+ ...current[target.key],
126742
126986
  keyStatus: "checking"
126743
126987
  }
126744
126988
  }));
126745
- credentials.describe([keyRef]).then((response) => {
126989
+ credentials.describe([target.keyRef]).then((response) => {
126746
126990
  if (!active) return;
126747
- const info = response.ok ? response.value?.[keyRef] : void 0;
126991
+ const info = response.ok ? response.value?.[target.keyRef] : void 0;
126748
126992
  setRows((current) => ({
126749
126993
  ...current,
126750
- [provider]: {
126751
- ...current[provider],
126994
+ [target.key]: {
126995
+ ...current[target.key],
126752
126996
  keyStatus: response.ok ? info?.configured ? "configured" : "missing" : "unknown",
126753
126997
  keyInfo: info
126754
126998
  }
@@ -126757,8 +127001,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126757
127001
  if (!active) return;
126758
127002
  setRows((current) => ({
126759
127003
  ...current,
126760
- [provider]: {
126761
- ...current[provider],
127004
+ [target.key]: {
127005
+ ...current[target.key],
126762
127006
  keyStatus: "unknown"
126763
127007
  }
126764
127008
  }));
@@ -126770,13 +127014,14 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126770
127014
  }, [
126771
127015
  credentials,
126772
127016
  credentialsAvailable,
126773
- keyTick
127017
+ keyTick,
127018
+ compatChannels
126774
127019
  ]);
126775
- const updateRow = (provider, patch) => {
127020
+ const updateRow = (target, patch) => {
126776
127021
  setRows((current) => ({
126777
127022
  ...current,
126778
- [provider]: {
126779
- ...current[provider],
127023
+ [target]: {
127024
+ ...current[target],
126780
127025
  ...patch
126781
127026
  }
126782
127027
  }));
@@ -126792,15 +127037,49 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126792
127037
  setProviderMessageIsError(true);
126793
127038
  });
126794
127039
  };
126795
- const saveProviderRow = async (provider) => {
126796
- const row = rows[provider];
126797
- updateRow(provider, {
127040
+ const saveProviderRow = async (provider, channel) => {
127041
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127042
+ const row = rows[target];
127043
+ updateRow(target, {
126798
127044
  saving: true,
126799
127045
  message: "",
126800
127046
  messageIsError: false
126801
127047
  });
126802
127048
  try {
126803
- if (provider === "comfyui") {
127049
+ if (channel !== void 0) {
127050
+ const label = row.label.trim();
127051
+ if (label.length === 0) throw new Error(t("channelNameRequired"));
127052
+ const updated = {
127053
+ ...channel,
127054
+ label,
127055
+ baseURL: row.baseURL.trim(),
127056
+ model: row.model.trim(),
127057
+ models: resolveOpenAICompatModels({
127058
+ openaiCompatModel: row.model,
127059
+ openaiCompatModels: row.models
127060
+ }),
127061
+ editModel: row.editModel.trim(),
127062
+ quality: row.generationQuality.trim(),
127063
+ qualityOptions: [...new Set(row.qualityOptionsText.split(",").map((value) => value.trim()).filter(Boolean))],
127064
+ extraBody: parseExtraFields(row.generateExtraText, t("editExtraInvalid")),
127065
+ editSizeMode: row.editSizeMode,
127066
+ editFormat: row.editFormat,
127067
+ editExtra: parseExtraFields(row.editExtraText, t("editExtraInvalid")),
127068
+ sizes: parseSizeTable(row.sizeTableText, t("sizeTableInvalid"))
127069
+ };
127070
+ const nextChannels = compatChannels.some((entry) => entry.id === channel.id) ? compatChannels.map((entry) => entry.id === channel.id ? updated : entry) : [...compatChannels, updated];
127071
+ await saveSetting("openaiCompatChannels", nextChannels);
127072
+ setCompatChannels(nextChannels);
127073
+ if (row.keyInput.trim().length > 0) {
127074
+ if (!credentialsAvailable()) throw new Error(t("credentialsUnavailable"));
127075
+ const response = await props.credentials.set(channel.keyRef, row.keyInput.trim());
127076
+ if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
127077
+ updateRow(target, {
127078
+ keyInput: "",
127079
+ keyStatus: "configured"
127080
+ });
127081
+ }
127082
+ } else if (provider === "comfyui") {
126804
127083
  const entries = row.workflows.map((entry) => ({
126805
127084
  name: entry.name.trim(),
126806
127085
  json: entry.json,
@@ -126829,6 +127108,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126829
127108
  if (provider === "google") await saveSetting("googleNative", row.googleNative);
126830
127109
  if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126831
127110
  if (provider === "openai-compat") {
127111
+ await saveSetting("openaiCompatLabel", row.label.trim());
127112
+ await saveSetting("openaiCompatModels", resolveOpenAICompatModels({
127113
+ openaiCompatModel: row.model,
127114
+ openaiCompatModels: row.models
127115
+ }));
126832
127116
  await saveSetting("openaiCompatEditModel", row.editModel.trim());
126833
127117
  await saveSetting("openaiCompatEditFormat", row.editFormat);
126834
127118
  await saveSetting("openaiCompatEditExtra", editExtra);
@@ -126849,13 +127133,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126849
127133
  if (!credentialsAvailable()) throw new Error(t("credentialsUnavailable"));
126850
127134
  const response = await props.credentials.set(keyRef, row.keyInput.trim());
126851
127135
  if (!response.ok) throw new Error(response.error?.message ?? t("saveKeyFailed"));
126852
- updateRow(provider, {
127136
+ updateRow(target, {
126853
127137
  keyInput: "",
126854
127138
  keyStatus: "configured"
126855
127139
  });
126856
127140
  }
126857
127141
  }
126858
- updateRow(provider, {
127142
+ updateRow(target, {
126859
127143
  message: t("saved"),
126860
127144
  messageIsError: false,
126861
127145
  testResult: void 0,
@@ -126863,38 +127147,99 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126863
127147
  modelFetchIsError: false
126864
127148
  });
126865
127149
  } catch (cause) {
126866
- updateRow(provider, {
127150
+ updateRow(target, {
127151
+ message: cause instanceof Error ? cause.message : String(cause),
127152
+ messageIsError: true
127153
+ });
127154
+ } finally {
127155
+ updateRow(target, { saving: false });
127156
+ }
127157
+ };
127158
+ const addCompatChannel = () => {
127159
+ const id = `channel-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
127160
+ const channel = {
127161
+ id,
127162
+ label: `OpenAI 兼容 ${compatChannels.length + 2}`,
127163
+ keyRef: openAICompatKeyRef(id),
127164
+ baseURL: "",
127165
+ model: "",
127166
+ models: [],
127167
+ qualityOptions: [...DEFAULT_COMPAT_QUALITIES]
127168
+ };
127169
+ setCompatChannels((current) => [...current, channel]);
127170
+ setRows((current) => ({
127171
+ ...current,
127172
+ [compatChannelRowKey(id)]: {
127173
+ ...emptyProviderRow(),
127174
+ ...compatRowFields(channel),
127175
+ expanded: true
127176
+ }
127177
+ }));
127178
+ };
127179
+ const removeCompatChannel = async (channel) => {
127180
+ if (!window.confirm(t("removeChannelConfirm", { label: channel.label }))) return;
127181
+ const target = compatChannelRowKey(channel.id);
127182
+ if (!(snapshot.value?.openaiCompatChannels?.some((entry) => entry.id === channel.id) === true)) {
127183
+ setCompatChannels((current) => current.filter((entry) => entry.id !== channel.id));
127184
+ setRows((current) => {
127185
+ const next = { ...current };
127186
+ delete next[target];
127187
+ return next;
127188
+ });
127189
+ return;
127190
+ }
127191
+ let removed = false;
127192
+ updateRow(target, {
127193
+ saving: true,
127194
+ message: "",
127195
+ messageIsError: false
127196
+ });
127197
+ try {
127198
+ const nextChannels = compatChannels.filter((entry) => entry.id !== channel.id);
127199
+ await saveSetting("openaiCompatChannels", nextChannels);
127200
+ if (snapshot.value?.openaiCompatActiveChannel === channel.id) await saveSetting("openaiCompatActiveChannel", "default");
127201
+ removed = true;
127202
+ setCompatChannels(nextChannels);
127203
+ setRows((current) => {
127204
+ const next = { ...current };
127205
+ delete next[target];
127206
+ return next;
127207
+ });
127208
+ } catch (cause) {
127209
+ updateRow(target, {
126867
127210
  message: cause instanceof Error ? cause.message : String(cause),
126868
127211
  messageIsError: true
126869
127212
  });
126870
127213
  } finally {
126871
- updateRow(provider, { saving: false });
127214
+ if (!removed) updateRow(target, { saving: false });
126872
127215
  }
126873
127216
  };
126874
127217
  /** These actions use host settings, so drafts must be saved before making a request. */
126875
- const providerActionBlockMessage = (provider) => {
126876
- const row = rows[provider];
127218
+ const providerActionBlockMessage = (provider, channel) => {
127219
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127220
+ const row = rows[target];
126877
127221
  if (row.keyInput.trim().length > 0) return t("saveKeyFirst");
126878
127222
  if (isSubscriptionProvider(provider)) return void 0;
126879
127223
  const address = row.baseURL.trim();
126880
127224
  if (address.length === 0) return t("endpointRequired");
126881
- if (address !== baseURLOf(provider, props.scope.getSnapshot().value).trim()) return t("saveEndpointFirst");
127225
+ if (address !== (channel === void 0 ? baseURLOf(provider, props.scope.getSnapshot().value) : channel.baseURL).trim()) return t("saveEndpointFirst");
126882
127226
  if (provider === "google" && row.googleNative !== (props.scope.getSnapshot().value?.googleNative === true)) return t("saveGoogleProtocolFirst");
126883
127227
  };
126884
127228
  /** Localize the host's missing-address error while preserving upstream details. */
126885
127229
  const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
126886
127230
  /** Probe through the host route so the browser side never touches credential values. */
126887
- const testConnection = async (provider) => {
126888
- const blocked = providerActionBlockMessage(provider);
127231
+ const testConnection = async (provider, channel) => {
127232
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127233
+ const blocked = providerActionBlockMessage(provider, channel);
126889
127234
  if (blocked !== void 0) {
126890
- updateRow(provider, {
127235
+ updateRow(target, {
126891
127236
  message: blocked,
126892
127237
  messageIsError: false,
126893
127238
  testResult: void 0
126894
127239
  });
126895
127240
  return;
126896
127241
  }
126897
- updateRow(provider, {
127242
+ updateRow(target, {
126898
127243
  testing: true,
126899
127244
  testResult: void 0,
126900
127245
  message: "",
@@ -126904,31 +127249,35 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126904
127249
  const response = await fetch(TEST_CONNECTION_ROUTE, {
126905
127250
  method: "POST",
126906
127251
  headers: { "content-type": "application/json" },
126907
- body: JSON.stringify({ provider })
127252
+ body: JSON.stringify({
127253
+ provider,
127254
+ ...channel === void 0 ? {} : { channel: channel.id }
127255
+ })
126908
127256
  });
126909
127257
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126910
- updateRow(provider, { testResult: await response.json() });
127258
+ updateRow(target, { testResult: await response.json() });
126911
127259
  } catch (cause) {
126912
- updateRow(provider, { testResult: {
127260
+ updateRow(target, { testResult: {
126913
127261
  ok: false,
126914
127262
  reason: "error",
126915
127263
  message: cause instanceof Error ? cause.message : String(cause)
126916
127264
  } });
126917
127265
  } finally {
126918
- updateRow(provider, { testing: false });
127266
+ updateRow(target, { testing: false });
126919
127267
  }
126920
127268
  };
126921
127269
  /** 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);
127270
+ const fetchProviderModels = async (provider, channel) => {
127271
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127272
+ const blocked = providerActionBlockMessage(provider, channel);
126924
127273
  if (blocked !== void 0) {
126925
- updateRow(provider, {
127274
+ updateRow(target, {
126926
127275
  modelFetchMessage: blocked,
126927
127276
  modelFetchIsError: false
126928
127277
  });
126929
127278
  return;
126930
127279
  }
126931
- updateRow(provider, {
127280
+ updateRow(target, {
126932
127281
  fetchingModels: true,
126933
127282
  modelFetchMessage: "",
126934
127283
  modelFetchIsError: false
@@ -126939,50 +127288,52 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126939
127288
  headers: { "content-type": "application/json" },
126940
127289
  body: JSON.stringify({
126941
127290
  provider,
126942
- action: "models"
127291
+ action: "models",
127292
+ ...channel === void 0 ? {} : { channel: channel.id }
126943
127293
  })
126944
127294
  });
126945
127295
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
126946
127296
  const payload = await response.json();
126947
127297
  if (payload.ok && Array.isArray(payload.models)) {
126948
127298
  const models = payload.models.filter((id) => typeof id === "string");
126949
- updateRow(provider, {
127299
+ updateRow(target, {
126950
127300
  modelOptions: models,
126951
127301
  modelFetchMessage: models.length > 0 ? t("modelsFound", { n: String(models.length) }) : t("modelsNone"),
126952
127302
  modelFetchIsError: models.length === 0
126953
127303
  });
126954
- } else if (payload.reason === "missing-key") updateRow(provider, {
127304
+ } else if (payload.reason === "missing-key") updateRow(target, {
126955
127305
  modelFetchMessage: t("badgeMissing"),
126956
127306
  modelFetchIsError: true
126957
127307
  });
126958
- else if (payload.reason === "unauthorized") updateRow(provider, {
127308
+ else if (payload.reason === "unauthorized") updateRow(target, {
126959
127309
  modelFetchMessage: t("testUnauthorized"),
126960
127310
  modelFetchIsError: true
126961
127311
  });
126962
- else updateRow(provider, {
127312
+ else updateRow(target, {
126963
127313
  modelFetchMessage: providerErrorMessage(payload.message),
126964
127314
  modelFetchIsError: true
126965
127315
  });
126966
127316
  } catch (cause) {
126967
- updateRow(provider, {
127317
+ updateRow(target, {
126968
127318
  modelFetchMessage: cause instanceof Error ? cause.message : String(cause),
126969
127319
  modelFetchIsError: true
126970
127320
  });
126971
127321
  } finally {
126972
- updateRow(provider, { fetchingModels: false });
127322
+ updateRow(target, { fetchingModels: false });
126973
127323
  }
126974
127324
  };
126975
- const clearProviderKey = async (provider) => {
126976
- const keyRef = cloudCredentialRef(provider);
127325
+ const clearProviderKey = async (provider, channel) => {
127326
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127327
+ const keyRef = channel === void 0 ? cloudCredentialRef(provider) : channel.keyRef;
126977
127328
  if (keyRef === void 0) return;
126978
127329
  if (typeof props.credentials.unset !== "function") {
126979
- updateRow(provider, {
127330
+ updateRow(target, {
126980
127331
  message: t("clearKeyUnsupported"),
126981
127332
  messageIsError: true
126982
127333
  });
126983
127334
  return;
126984
127335
  }
126985
- updateRow(provider, {
127336
+ updateRow(target, {
126986
127337
  saving: true,
126987
127338
  message: "",
126988
127339
  messageIsError: false
@@ -126990,7 +127341,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126990
127341
  try {
126991
127342
  const response = await props.credentials.unset(keyRef);
126992
127343
  if (!response.ok) throw new Error(response.error?.message ?? t("clearKeyFailed"));
126993
- updateRow(provider, {
127344
+ updateRow(target, {
126994
127345
  keyStatus: "missing",
126995
127346
  keyInput: "",
126996
127347
  testResult: void 0,
@@ -126998,12 +127349,12 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126998
127349
  messageIsError: false
126999
127350
  });
127000
127351
  } catch (cause) {
127001
- updateRow(provider, {
127352
+ updateRow(target, {
127002
127353
  message: cause instanceof Error ? cause.message : String(cause),
127003
127354
  messageIsError: true
127004
127355
  });
127005
127356
  } finally {
127006
- updateRow(provider, { saving: false });
127357
+ updateRow(target, { saving: false });
127007
127358
  }
127008
127359
  };
127009
127360
  const testResultText = (result) => {
@@ -127013,7 +127364,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127013
127364
  if (result.reason === "unauthorized") return t("testUnauthorized");
127014
127365
  return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${providerErrorMessage(result.message)}` : ""}`;
127015
127366
  };
127016
- const badgeOf = (provider) => {
127367
+ const badgeOf = (provider, channel) => {
127017
127368
  if (provider === "comfyui") return {
127018
127369
  text: t("comfyuiNoKey"),
127019
127370
  className: "dsh-ig-badge dsh-ig-badge-neutral"
@@ -127033,7 +127384,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127033
127384
  className: "dsh-ig-badge dsh-ig-badge-neutral"
127034
127385
  };
127035
127386
  }
127036
- const status = rows[provider].keyStatus;
127387
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127388
+ const status = rows[target].keyStatus;
127037
127389
  if (status === "checking") return {
127038
127390
  text: t("badgeChecking"),
127039
127391
  className: "dsh-ig-badge dsh-ig-badge-neutral dsh-ig-badge-checking"
@@ -127141,6 +127493,19 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127141
127493
  };
127142
127494
  });
127143
127495
  };
127496
+ const commitCloudTimeout = () => {
127497
+ if (!Number.isInteger(cloudTimeoutSeconds) || cloudTimeoutSeconds < 5 || cloudTimeoutSeconds > 3600) {
127498
+ setCloudTimeoutSeconds((snapshot.value?.cloudTimeoutMs ?? 15e4) / 1e3);
127499
+ return;
127500
+ }
127501
+ const next = cloudTimeoutSeconds * 1e3;
127502
+ if (next === (snapshot.value?.cloudTimeoutMs ?? 15e4)) return;
127503
+ setCloudTimeoutMessage("");
127504
+ saveSetting("cloudTimeoutMs", next).catch((cause) => {
127505
+ setCloudTimeoutSeconds((snapshot.value?.cloudTimeoutMs ?? 15e4) / 1e3);
127506
+ setCloudTimeoutMessage(cause instanceof Error ? cause.message : String(cause));
127507
+ });
127508
+ };
127144
127509
  /** Workspace checkbox persists immediately, like every other toggle on this card. */
127145
127510
  const toggleSaveToWorkspace = (next) => {
127146
127511
  setSaveToWorkspace(next);
@@ -127177,31 +127542,57 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127177
127542
  setUiMessageIsError(true);
127178
127543
  });
127179
127544
  };
127180
- const renderCloudBody = (provider) => {
127181
- const row = rows[provider];
127545
+ const renderCloudBody = (provider, channel) => {
127546
+ const target = channel === void 0 ? provider : compatChannelRowKey(channel.id);
127547
+ const row = rows[target];
127548
+ const update = (patch) => {
127549
+ updateRow(target, patch);
127550
+ };
127551
+ const modelListId = `dsh-ig-${target.replace(/[^A-Za-z0-9_-]/g, "-")}-model-options`;
127182
127552
  const officialEditFormat = provider === "openai-compat" ? compatEditFormat({
127183
127553
  openaiCompatBaseURL: row.baseURL,
127184
127554
  openaiCompatModel: row.editModel.trim() || row.model
127185
127555
  }) : "multipart";
127186
127556
  const automaticEditFormat = officialEditFormat !== "multipart";
127187
127557
  const editFormat = automaticEditFormat ? officialEditFormat : row.editFormat;
127188
- const keyRef = cloudCredentialRef(provider) ?? "";
127558
+ const keyRef = channel === void 0 ? cloudCredentialRef(provider) ?? "" : channel.keyRef;
127189
127559
  const keyReadOnly = row.keyInfo?.writable === false;
127190
127560
  const keyUnavailable = !credentialsAvailable();
127561
+ const providerLabel = provider === "openai-compat" && row.label.trim().length > 0 ? row.label.trim() : providerLabels[provider];
127562
+ const compatModelChoices = provider === "openai-compat" ? resolveOpenAICompatModels({
127563
+ openaiCompatModel: row.model,
127564
+ openaiCompatModels: [...row.models, ...row.modelOptions]
127565
+ }) : [];
127191
127566
  return (0, react_jsx_runtime.jsx)("div", {
127192
127567
  className: "dsh-ig-provider-body",
127193
127568
  children: (0, react_jsx_runtime.jsxs)("form", {
127194
127569
  onSubmit: (event) => {
127195
127570
  event.preventDefault();
127196
- saveProviderRow(provider);
127571
+ saveProviderRow(provider, channel);
127197
127572
  },
127198
127573
  children: [
127574
+ provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127575
+ className: "dsh-ig-field",
127576
+ children: [(0, react_jsx_runtime.jsx)("span", {
127577
+ className: "dsh-ig-label",
127578
+ children: t("channelName")
127579
+ }), (0, react_jsx_runtime.jsx)("input", {
127580
+ className: "dsh-ig-input",
127581
+ value: row.label,
127582
+ onChange: (event) => {
127583
+ update({ label: event.target.value });
127584
+ },
127585
+ placeholder: providerLabels[provider],
127586
+ required: channel !== void 0,
127587
+ disabled: !settingsWritable
127588
+ })]
127589
+ }) : null,
127199
127590
  (0, react_jsx_runtime.jsxs)("label", {
127200
127591
  className: "dsh-ig-field",
127201
127592
  children: [
127202
127593
  (0, react_jsx_runtime.jsx)("span", {
127203
127594
  className: "dsh-ig-label",
127204
- children: t("apiKeyLabel", { provider: providerLabels[provider] })
127595
+ children: t("apiKeyLabel", { provider: providerLabel })
127205
127596
  }),
127206
127597
  (0, react_jsx_runtime.jsx)("input", {
127207
127598
  className: "dsh-ig-input",
@@ -127209,7 +127600,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127209
127600
  autoComplete: "off",
127210
127601
  value: row.keyInput,
127211
127602
  onChange: (event) => {
127212
- updateRow(provider, { keyInput: event.target.value });
127603
+ update({ keyInput: event.target.value });
127213
127604
  },
127214
127605
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
127215
127606
  disabled: !settingsWritable || keyReadOnly || keyUnavailable
@@ -127234,7 +127625,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127234
127625
  type: "url",
127235
127626
  value: row.baseURL,
127236
127627
  onChange: (event) => {
127237
- updateRow(provider, { baseURL: event.target.value });
127628
+ update({ baseURL: event.target.value });
127238
127629
  },
127239
127630
  required: true,
127240
127631
  disabled: !settingsWritable
@@ -127243,7 +127634,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127243
127634
  className: "dsh-ig-btn-reset",
127244
127635
  title: t("resetTitle"),
127245
127636
  onClick: () => {
127246
- updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
127637
+ update({ baseURL: DEFAULT_BASE_URLS[provider] });
127247
127638
  },
127248
127639
  disabled: !settingsWritable,
127249
127640
  children: t("reset")
@@ -127264,7 +127655,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127264
127655
  className: "dsh-ig-input",
127265
127656
  value: row.googleNative ? "native" : "interactions",
127266
127657
  onChange: (event) => {
127267
- updateRow(provider, { googleNative: event.target.value === "native" });
127658
+ update({ googleNative: event.target.value === "native" });
127268
127659
  },
127269
127660
  disabled: !settingsWritable,
127270
127661
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127281,7 +127672,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127281
127672
  children: [
127282
127673
  (0, react_jsx_runtime.jsx)("span", {
127283
127674
  className: "dsh-ig-label",
127284
- children: t("model")
127675
+ children: t(provider === "openai-compat" ? "defaultModel" : "model")
127285
127676
  }),
127286
127677
  modelPullSupported(provider) ? (0, react_jsx_runtime.jsxs)("div", {
127287
127678
  className: "dsh-ig-input-group",
@@ -127290,14 +127681,20 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127290
127681
  className: "dsh-ig-input",
127291
127682
  value: row.model,
127292
127683
  onChange: (event) => {
127293
- updateRow(provider, { model: event.target.value });
127684
+ update({ model: event.target.value });
127294
127685
  },
127295
- list: `dsh-ig-${provider}-model-options`,
127686
+ onBlur: () => {
127687
+ if (provider === "openai-compat") update({ models: resolveOpenAICompatModels({
127688
+ openaiCompatModel: row.model,
127689
+ openaiCompatModels: row.models
127690
+ }) });
127691
+ },
127692
+ list: modelListId,
127296
127693
  required: provider !== "openai-compat",
127297
127694
  disabled: !settingsWritable
127298
127695
  }),
127299
127696
  (0, react_jsx_runtime.jsx)("datalist", {
127300
- id: `dsh-ig-${provider}-model-options`,
127697
+ id: modelListId,
127301
127698
  children: row.modelOptions.map((id) => (0, react_jsx_runtime.jsx)("option", { value: id }, id))
127302
127699
  }),
127303
127700
  (0, react_jsx_runtime.jsx)("button", {
@@ -127305,7 +127702,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127305
127702
  className: "dsh-ig-btn-secondary",
127306
127703
  disabled: row.fetchingModels || row.saving || !settingsWritable,
127307
127704
  onClick: () => {
127308
- fetchProviderModels(provider);
127705
+ fetchProviderModels(provider, channel);
127309
127706
  },
127310
127707
  children: row.fetchingModels ? t("fetchingModels") : t("fetchModels")
127311
127708
  })
@@ -127314,7 +127711,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127314
127711
  className: "dsh-ig-input",
127315
127712
  value: row.model,
127316
127713
  onChange: (event) => {
127317
- updateRow(provider, { model: event.target.value });
127714
+ update({ model: event.target.value });
127318
127715
  },
127319
127716
  required: true,
127320
127717
  disabled: !settingsWritable
@@ -127329,6 +127726,52 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127329
127726
  }) : null
127330
127727
  ]
127331
127728
  }),
127729
+ provider === "openai-compat" && compatModelChoices.length > 0 ? (0, react_jsx_runtime.jsxs)("div", {
127730
+ className: "dsh-ig-field",
127731
+ children: [
127732
+ (0, react_jsx_runtime.jsx)("span", {
127733
+ className: "dsh-ig-label",
127734
+ children: t("availableModels")
127735
+ }),
127736
+ (0, react_jsx_runtime.jsx)("div", {
127737
+ className: "dsh-ig-model-picker",
127738
+ children: compatModelChoices.map((id) => {
127739
+ const isDefault = row.model.trim() === id;
127740
+ const checked = isDefault || row.models.includes(id);
127741
+ return (0, react_jsx_runtime.jsxs)("label", {
127742
+ className: "dsh-ig-check-row",
127743
+ title: id,
127744
+ children: [
127745
+ (0, react_jsx_runtime.jsx)("input", {
127746
+ type: "checkbox",
127747
+ checked,
127748
+ disabled: !settingsWritable || isDefault,
127749
+ onChange: (event) => {
127750
+ const models = event.target.checked ? [...row.models, id] : row.models.filter((model) => model !== id);
127751
+ update({ models: resolveOpenAICompatModels({
127752
+ openaiCompatModel: row.model,
127753
+ openaiCompatModels: models
127754
+ }) });
127755
+ }
127756
+ }),
127757
+ (0, react_jsx_runtime.jsx)("span", {
127758
+ className: "dsh-ig-model-option",
127759
+ children: id
127760
+ }),
127761
+ isDefault ? (0, react_jsx_runtime.jsx)("span", {
127762
+ className: "dsh-ig-model-default",
127763
+ children: t("defaultModelTag")
127764
+ }) : null
127765
+ ]
127766
+ }, id);
127767
+ })
127768
+ }),
127769
+ (0, react_jsx_runtime.jsx)("span", {
127770
+ className: "dsh-ig-hint",
127771
+ children: t("availableModelsHint")
127772
+ })
127773
+ ]
127774
+ }) : null,
127332
127775
  provider === "openai-compat" ? (0, react_jsx_runtime.jsxs)("label", {
127333
127776
  className: "dsh-ig-field",
127334
127777
  children: [
@@ -127340,9 +127783,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127340
127783
  className: "dsh-ig-input",
127341
127784
  value: row.editModel,
127342
127785
  onChange: (event) => {
127343
- updateRow(provider, { editModel: event.target.value });
127786
+ update({ editModel: event.target.value });
127344
127787
  },
127345
- list: "dsh-ig-openai-compat-model-options",
127788
+ list: modelListId,
127346
127789
  disabled: !settingsWritable
127347
127790
  }),
127348
127791
  (0, react_jsx_runtime.jsx)("span", {
@@ -127362,7 +127805,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127362
127805
  className: "dsh-ig-input",
127363
127806
  value: automaticEditFormat ? "__official__" : row.editFormat,
127364
127807
  onChange: (event) => {
127365
- updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
127808
+ update({ editFormat: parseEditFormat(event.target.value) });
127366
127809
  },
127367
127810
  disabled: !settingsWritable || automaticEditFormat,
127368
127811
  children: [
@@ -127406,7 +127849,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127406
127849
  className: "dsh-ig-input",
127407
127850
  value: row.outputFormat,
127408
127851
  onChange: (event) => {
127409
- updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127852
+ update({ outputFormat: event.target.value === "png" ? "png" : "jpeg" });
127410
127853
  },
127411
127854
  disabled: !settingsWritable,
127412
127855
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127434,7 +127877,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127434
127877
  className: "dsh-ig-input",
127435
127878
  value: row.watermark ? "on" : "off",
127436
127879
  onChange: (event) => {
127437
- updateRow(provider, { watermark: event.target.value === "on" });
127880
+ update({ watermark: event.target.value === "on" });
127438
127881
  },
127439
127882
  disabled: !settingsWritable,
127440
127883
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127462,7 +127905,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127462
127905
  className: "dsh-ig-input",
127463
127906
  value: row.background,
127464
127907
  onChange: (event) => {
127465
- updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
127908
+ update({ background: event.target.value === "transparent" ? "transparent" : "opaque" });
127466
127909
  },
127467
127910
  disabled: !settingsWritable,
127468
127911
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127491,7 +127934,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127491
127934
  className: "dsh-ig-input dsh-ig-textarea",
127492
127935
  value: row.sizeTableText,
127493
127936
  onChange: (event) => {
127494
- updateRow(provider, { sizeTableText: event.target.value });
127937
+ update({ sizeTableText: event.target.value });
127495
127938
  },
127496
127939
  rows: 3,
127497
127940
  disabled: !settingsWritable
@@ -127514,7 +127957,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127514
127957
  className: "dsh-ig-input",
127515
127958
  value: row.generationQuality,
127516
127959
  onChange: (event) => {
127517
- updateRow(provider, { generationQuality: event.target.value });
127960
+ update({ generationQuality: event.target.value });
127518
127961
  },
127519
127962
  placeholder: "high",
127520
127963
  disabled: !settingsWritable
@@ -127534,7 +127977,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127534
127977
  className: "dsh-ig-input",
127535
127978
  value: row.qualityOptionsText,
127536
127979
  onChange: (event) => {
127537
- updateRow(provider, { qualityOptionsText: event.target.value });
127980
+ update({ qualityOptionsText: event.target.value });
127538
127981
  },
127539
127982
  disabled: !settingsWritable
127540
127983
  })]
@@ -127550,7 +127993,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127550
127993
  className: "dsh-ig-input dsh-ig-textarea",
127551
127994
  value: row.generateExtraText,
127552
127995
  onChange: (event) => {
127553
- updateRow(provider, { generateExtraText: event.target.value });
127996
+ update({ generateExtraText: event.target.value });
127554
127997
  },
127555
127998
  placeholder: "{\"watermark\":false}",
127556
127999
  rows: 2,
@@ -127572,7 +128015,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127572
128015
  className: "dsh-ig-input",
127573
128016
  value: row.editSizeMode,
127574
128017
  onChange: (event) => {
127575
- updateRow(provider, { editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
128018
+ update({ editSizeMode: event.target.value === "selected" ? "selected" : "auto" });
127576
128019
  },
127577
128020
  disabled: !settingsWritable,
127578
128021
  children: [(0, react_jsx_runtime.jsx)("option", {
@@ -127594,7 +128037,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127594
128037
  className: "dsh-ig-input dsh-ig-textarea",
127595
128038
  value: row.editExtraText,
127596
128039
  onChange: (event) => {
127597
- updateRow(provider, { editExtraText: event.target.value });
128040
+ update({ editExtraText: event.target.value });
127598
128041
  },
127599
128042
  placeholder: t("editExtraPlaceholder"),
127600
128043
  rows: 2,
@@ -127620,7 +128063,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127620
128063
  className: "dsh-ig-btn-secondary",
127621
128064
  disabled: row.testing || row.saving,
127622
128065
  onClick: () => {
127623
- testConnection(provider);
128066
+ testConnection(provider, channel);
127624
128067
  },
127625
128068
  children: row.testing ? t("testing") : t("testConnection")
127626
128069
  }),
@@ -127629,7 +128072,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127629
128072
  className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
127630
128073
  disabled: row.saving,
127631
128074
  onClick: () => {
127632
- clearProviderKey(provider);
128075
+ clearProviderKey(provider, channel);
127633
128076
  },
127634
128077
  children: t("clearKey")
127635
128078
  }) : null,
@@ -127947,47 +128390,123 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127947
128390
  const renderProviderRow = (provider) => {
127948
128391
  const row = rows[provider];
127949
128392
  const badge = badgeOf(provider);
128393
+ const label = provider === "openai-compat" && row.label.trim().length > 0 ? row.label.trim() : providerLabels[provider];
127950
128394
  return (0, react_jsx_runtime.jsxs)("div", {
127951
128395
  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" })
128396
+ children: [(0, react_jsx_runtime.jsxs)("div", {
128397
+ className: "dsh-ig-provider-head-row",
128398
+ children: [(0, react_jsx_runtime.jsxs)("button", {
128399
+ type: "button",
128400
+ className: "dsh-ig-provider-head",
128401
+ "aria-expanded": row.expanded,
128402
+ onClick: () => {
128403
+ updateRow(provider, { expanded: !row.expanded });
128404
+ },
128405
+ children: [
128406
+ (0, react_jsx_runtime.jsx)("span", {
128407
+ className: "dsh-ig-provider-name",
128408
+ children: label
128409
+ }),
128410
+ (0, react_jsx_runtime.jsxs)("span", {
128411
+ className: badge.className,
128412
+ title: badge.text,
128413
+ children: [(0, react_jsx_runtime.jsx)("span", {
128414
+ className: "dsh-ig-badge-dot",
128415
+ "aria-hidden": "true"
128416
+ }), badge.text]
128417
+ }),
128418
+ (0, react_jsx_runtime.jsx)("span", {
128419
+ className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
128420
+ "aria-hidden": "true",
128421
+ children: (0, react_jsx_runtime.jsx)("svg", {
128422
+ width: "12",
128423
+ height: "12",
128424
+ viewBox: "0 0 16 16",
128425
+ fill: "none",
128426
+ stroke: "currentColor",
128427
+ strokeWidth: "2",
128428
+ strokeLinecap: "round",
128429
+ strokeLinejoin: "round",
128430
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
128431
+ })
127985
128432
  })
128433
+ ]
128434
+ }), provider === "openai-compat" ? (0, react_jsx_runtime.jsx)("button", {
128435
+ type: "button",
128436
+ className: "dsh-ig-btn-secondary dsh-ig-channel-add",
128437
+ title: t("addCompatChannel"),
128438
+ "aria-label": t("addCompatChannel"),
128439
+ onClick: addCompatChannel,
128440
+ disabled: !settingsWritable,
128441
+ children: (0, react_jsx_runtime.jsx)("svg", {
128442
+ viewBox: "0 0 16 16",
128443
+ fill: "none",
128444
+ stroke: "currentColor",
128445
+ strokeWidth: "1.6",
128446
+ strokeLinecap: "round",
128447
+ "aria-hidden": "true",
128448
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" })
127986
128449
  })
127987
- ]
128450
+ }) : null]
127988
128451
  }), row.expanded ? provider === "comfyui" ? renderComfyUIBody() : isSubscriptionProvider(provider) ? renderSubscriptionBody(provider) : renderCloudBody(provider) : null]
127989
128452
  }, provider);
127990
128453
  };
128454
+ const renderCompatChannelRow = (channel) => {
128455
+ const target = compatChannelRowKey(channel.id);
128456
+ const row = rows[target];
128457
+ const badge = badgeOf("openai-compat", channel);
128458
+ return (0, react_jsx_runtime.jsxs)("div", {
128459
+ className: "dsh-ig-provider-row dsh-ig-compat-channel-row",
128460
+ children: [(0, react_jsx_runtime.jsxs)("div", {
128461
+ className: "dsh-ig-provider-head-row",
128462
+ children: [(0, react_jsx_runtime.jsxs)("button", {
128463
+ type: "button",
128464
+ className: "dsh-ig-provider-head",
128465
+ "aria-expanded": row.expanded,
128466
+ onClick: () => {
128467
+ updateRow(target, { expanded: !row.expanded });
128468
+ },
128469
+ children: [
128470
+ (0, react_jsx_runtime.jsx)("span", {
128471
+ className: "dsh-ig-provider-name",
128472
+ children: channel.label
128473
+ }),
128474
+ (0, react_jsx_runtime.jsxs)("span", {
128475
+ className: badge.className,
128476
+ title: badge.text,
128477
+ children: [(0, react_jsx_runtime.jsx)("span", {
128478
+ className: "dsh-ig-badge-dot",
128479
+ "aria-hidden": "true"
128480
+ }), badge.text]
128481
+ }),
128482
+ (0, react_jsx_runtime.jsx)("span", {
128483
+ className: `dsh-ig-provider-chevron ${row.expanded ? "dsh-ig-provider-chevron-open" : ""}`,
128484
+ "aria-hidden": "true",
128485
+ children: (0, react_jsx_runtime.jsx)("svg", {
128486
+ width: "12",
128487
+ height: "12",
128488
+ viewBox: "0 0 16 16",
128489
+ fill: "none",
128490
+ stroke: "currentColor",
128491
+ strokeWidth: "2",
128492
+ strokeLinecap: "round",
128493
+ strokeLinejoin: "round",
128494
+ children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
128495
+ })
128496
+ })
128497
+ ]
128498
+ }), (0, react_jsx_runtime.jsx)("button", {
128499
+ type: "button",
128500
+ className: "dsh-ig-btn-secondary dsh-ig-btn-danger",
128501
+ disabled: row.saving || !settingsWritable,
128502
+ onClick: () => {
128503
+ removeCompatChannel(channel);
128504
+ },
128505
+ children: t("removeChannel")
128506
+ })]
128507
+ }), row.expanded ? renderCloudBody("openai-compat", channel) : null]
128508
+ }, target);
128509
+ };
127991
128510
  return (0, react_jsx_runtime.jsxs)("li", {
127992
128511
  className: "dsh-ig-card",
127993
128512
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -128045,9 +128564,46 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128045
128564
  }) : null
128046
128565
  ]
128047
128566
  }),
128567
+ (0, react_jsx_runtime.jsxs)("label", {
128568
+ className: "dsh-ig-field",
128569
+ children: [
128570
+ (0, react_jsx_runtime.jsx)("span", {
128571
+ className: "dsh-ig-label",
128572
+ children: t("cloudTimeout")
128573
+ }),
128574
+ (0, react_jsx_runtime.jsx)("input", {
128575
+ className: "dsh-ig-input",
128576
+ type: "number",
128577
+ min: "5",
128578
+ max: "3600",
128579
+ step: "1",
128580
+ value: cloudTimeoutSeconds,
128581
+ onChange: (event) => {
128582
+ setCloudTimeoutSeconds(Number(event.target.value));
128583
+ },
128584
+ onBlur: () => {
128585
+ commitCloudTimeout();
128586
+ },
128587
+ onKeyDown: (event) => {
128588
+ if (event.key === "Enter") commitCloudTimeout();
128589
+ },
128590
+ required: true,
128591
+ disabled: !settingsWritable
128592
+ }),
128593
+ (0, react_jsx_runtime.jsx)("span", {
128594
+ className: "dsh-ig-hint",
128595
+ children: t("cloudTimeoutHint")
128596
+ }),
128597
+ cloudTimeoutMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
128598
+ className: "dsh-ig-status dsh-ig-status-error",
128599
+ role: "status",
128600
+ children: cloudTimeoutMessage
128601
+ }) : null
128602
+ ]
128603
+ }),
128048
128604
  (0, react_jsx_runtime.jsx)("div", {
128049
128605
  className: "dsh-ig-providers",
128050
- children: IMAGE_PROVIDERS.map((provider) => renderProviderRow(provider))
128606
+ children: IMAGE_PROVIDERS.flatMap((provider) => [renderProviderRow(provider), ...provider === "openai-compat" ? compatChannels.map((channel) => renderCompatChannelRow(channel)) : []])
128051
128607
  }),
128052
128608
  (0, react_jsx_runtime.jsxs)("div", {
128053
128609
  className: "dsh-ig-section",
@@ -128212,6 +128768,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128212
128768
  const activeResult = selectedRevision !== void 0 ? {
128213
128769
  prompt: selectedRevision.prompt,
128214
128770
  provider: selectedRevision.provider,
128771
+ ...selectedRevision.channel === void 0 ? {} : { channel: selectedRevision.channel },
128215
128772
  model: selectedRevision.model,
128216
128773
  output: selectedRevision.output,
128217
128774
  createdAt: selectedRevision.createdAt,
@@ -128242,6 +128799,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128242
128799
  attachment: result.attachment,
128243
128800
  prompt: result.prompt,
128244
128801
  provider: result.provider,
128802
+ ...result.channel === void 0 ? {} : { channel: result.channel },
128245
128803
  model: result.model,
128246
128804
  output: result.output,
128247
128805
  ...result.savedTo ? { savedTo: result.savedTo } : {},
@@ -128380,6 +128938,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128380
128938
  attachment: payload.attachment,
128381
128939
  prompt: payload.prompt,
128382
128940
  provider: payload.provider,
128941
+ ...payload.channel === void 0 ? {} : { channel: payload.channel },
128383
128942
  model: payload.model,
128384
128943
  output: payload.output,
128385
128944
  createdAt: payload.createdAt,
@@ -128394,6 +128953,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
128394
128953
  attachment: revision.attachment,
128395
128954
  prompt: revision.prompt,
128396
128955
  provider: revision.provider,
128956
+ ...revision.channel === void 0 ? {} : { channel: revision.channel },
128397
128957
  model: revision.model,
128398
128958
  createdAt: revision.createdAt,
128399
128959
  aspectRatio: revision.ratio,