@dickpy/dsh-imagegen 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -25,6 +25,16 @@ window.__ModuleLoader__.load({
25
25
  };
26
26
  /** Host-mediated candidate discovery for the configured image API. */
27
27
  const IMAGE_MODEL_API = { models: "/api/dsh-imagegen/image-models" };
28
+ /** Host-served built-in provider catalog (channels the user can instantiate). */
29
+ const PRESETS_API = "/api/dsh-imagegen/presets";
30
+ /** Loopback-only image reader for Agent tool-result previews. */
31
+ const AGENT_IMAGE_API = "/api/dsh-imagegen/agent-image";
32
+ /**
33
+ * Host-computed per-channel usage counters (generation-count badges in the
34
+ * settings card): entries are tallied from the persisted history and gallery
35
+ * by channel + model alias.
36
+ */
37
+ const USAGE_API = "/api/dsh-imagegen/usage";
28
38
  /** Host-resident generation queue endpoints. */
29
39
  const TASK_API = {
30
40
  submit: "/api/dsh-imagegen/tasks/submit",
@@ -281,7 +291,7 @@ window.__ModuleLoader__.load({
281
291
  */
282
292
  const zh = {
283
293
  "entry.label": "AI 生图",
284
- "entry.tooltip": "AI 生图面板(gpt-image-2 / grok-imagine-image",
294
+ "entry.tooltip": "AI 生图面板(gpt-image-2 / grok-imagine-image / nanobanana / seedream 系列)",
285
295
  "panel.title": "AI 生图",
286
296
  "panel.githubTip": "觉得好用或有建议?欢迎来 GitHub 提 issues、点个 star 支持一下!",
287
297
  "mode.text": "文生图",
@@ -343,6 +353,9 @@ window.__ModuleLoader__.load({
343
353
  "canvas.emptyTitle": "开始你的创作",
344
354
  "canvas.emptyHint": "在左侧输入提示词并点击「开始生成」,结果将显示在这里",
345
355
  "canvas.error": "生成失败:{error}",
356
+ "canvas.submitting": "正在加入生成队列…",
357
+ "canvas.queued": "已加入生成队列",
358
+ "canvas.queueHint": "当前有 {count} 个任务等待处理",
346
359
  "canvas.generating": "正在生成图片…",
347
360
  "canvas.elapsed": "已用时 {seconds}s",
348
361
  "canvas.images": "本次生成 {count} 张",
@@ -512,11 +525,62 @@ window.__ModuleLoader__.load({
512
525
  "templates.retry": "重试",
513
526
  "templates.attribution": "模板与图片来自 awesome-gpt-image-2 项目,作者链接见各模板详情",
514
527
  "templates.source": "来源:vibeui.top",
515
- "templates.featured": "精选"
528
+ "templates.featured": "精选",
529
+ "channels.title": "渠道",
530
+ "channels.hint": "填写各渠道的 API 地址与密钥即可使用对应模型",
531
+ "channels.empty": "尚无渠道,点击下方按钮添加",
532
+ "channels.addProvider": "添加提供方",
533
+ "channels.addCustom": "添加自定义渠道",
534
+ "channels.untitled": "未命名渠道",
535
+ "channels.keySet": "密钥已设置",
536
+ "channels.keyMissing": "未设置密钥",
537
+ "channels.modelCount": "{n} 个模型",
538
+ "channels.noModels": "未配置模型",
539
+ "channels.defaultLabel": "默认",
540
+ "channels.statusReady": "可用",
541
+ "channels.statusIncomplete": "配置未完成",
542
+ "channels.edit": "编辑",
543
+ "channels.delete": "删除",
544
+ "channels.deleteConfirmTitle": "删除渠道「{name}」?该渠道的密钥将被移除;历史与画廊中的记录仍会保留。",
545
+ "channels.confirm": "确认删除",
546
+ "channels.cancel": "取消",
547
+ "channels.editorTitle": "渠道",
548
+ "channels.editorSaveNote": "改动将随卡片底部「保存」一起生效",
549
+ "channels.displayName": "显示名称",
550
+ "channels.apiUrl": "API 地址",
551
+ "channels.apiKey": "API 密钥",
552
+ "channels.keyReplaceHint": "已配置 — 输入新值可替换",
553
+ "channels.keyMissingHint": "填写密钥",
554
+ "channels.keyClear": "清除密钥",
555
+ "channels.modelCatalogTitle": "模型目录",
556
+ "channels.noModelsHint": "暂无模型:点击「检测」拉取候选,或手动添加;别名(显示名)默认等于上游模型 id,可自行修改。",
557
+ "channels.detect": "重新检测",
558
+ "channels.detecting": "检测中…",
559
+ "channels.detectSuccess": "连通 ✓ · {n} 个候选",
560
+ "channels.detectFailed": "检测失败:{error}",
561
+ "channels.detectOk": "已连接该 API",
562
+ "channels.modelAliasLabel": "显示名(别名)",
563
+ "channels.modelIdLabel": "上游模型 id",
564
+ "channels.generated": "已生成 {n} 次",
565
+ "channels.unknownProtocol": "未知协议,按通用 OpenAI 协议尝试",
566
+ "channels.removeModel": "移除模型",
567
+ "channels.manualAddPlaceholder": "输入模型 id,例如 qwen-image",
568
+ "channels.addModelConfirm": "添加",
569
+ "channels.copyFrom": "从其他渠道复制…",
570
+ "channels.copyApply": "复制",
571
+ "channels.candidatesTitle": "候选模型(点击加入)",
572
+ "channels.candidatesQuick": "只勾已收录协议",
573
+ "channels.deleteThisChannel": "删除此渠道",
574
+ "channels.setDefault": "设为默认渠道",
575
+ "channels.presetPickerTitle": "添加提供方",
576
+ "channels.presetPickerHint": "选择一个内置提供方,仅需填写 API 密钥",
577
+ "channels.presetModelsHint": "{n} 个预置模型",
578
+ "channels.presetCustomHint": "自行填写 API 地址、密钥与模型目录",
579
+ "channels.presetLoadFailed": "加载提供方失败:{error}"
516
580
  };
517
581
  const en = {
518
582
  "entry.label": "AI Image",
519
- "entry.tooltip": "AI image generation studio (gpt-image-2 / grok-imagine-image)",
583
+ "entry.tooltip": "AI image generation studio (gpt-image-2 / grok-imagine-image / nanobanana / seedream family)",
520
584
  "panel.title": "AI Image",
521
585
  "panel.githubTip": "Like it or have suggestions? Head to GitHub to open issues and star us!",
522
586
  "mode.text": "Text to Image",
@@ -578,6 +642,9 @@ window.__ModuleLoader__.load({
578
642
  "canvas.emptyTitle": "Start creating",
579
643
  "canvas.emptyHint": "Enter a prompt on the left and click \"Generate\"; results appear here",
580
644
  "canvas.error": "Generation failed: {error}",
645
+ "canvas.submitting": "Adding to the generation queue…",
646
+ "canvas.queued": "Added to the generation queue",
647
+ "canvas.queueHint": "{count} task(s) waiting or generating",
581
648
  "canvas.generating": "Generating images…",
582
649
  "canvas.elapsed": "Elapsed {seconds}s",
583
650
  "canvas.images": "{count} image(s) generated",
@@ -747,7 +814,58 @@ window.__ModuleLoader__.load({
747
814
  "templates.retry": "Retry",
748
815
  "templates.attribution": "Templates and images come from the awesome-gpt-image-2 project; author links are on each template",
749
816
  "templates.source": "Source: vibeui.top",
750
- "templates.featured": "Featured"
817
+ "templates.featured": "Featured",
818
+ "channels.title": "Channels",
819
+ "channels.hint": "Fill in each channel's API URL and key to use its models",
820
+ "channels.empty": "No channels yet — add one below",
821
+ "channels.addProvider": "Add provider",
822
+ "channels.addCustom": "Add custom channel",
823
+ "channels.untitled": "Untitled channel",
824
+ "channels.keySet": "Key set",
825
+ "channels.keyMissing": "Key missing",
826
+ "channels.modelCount": "{n} model(s)",
827
+ "channels.noModels": "No models",
828
+ "channels.defaultLabel": "Default",
829
+ "channels.statusReady": "Ready",
830
+ "channels.statusIncomplete": "Config incomplete",
831
+ "channels.edit": "Edit",
832
+ "channels.delete": "Delete",
833
+ "channels.deleteConfirmTitle": "Delete channel \"{name}\"? Its key will be removed; history and gallery records are kept.",
834
+ "channels.confirm": "Delete",
835
+ "channels.cancel": "Cancel",
836
+ "channels.editorTitle": "Channel",
837
+ "channels.editorSaveNote": "Changes take effect with the card's \"Save\" button",
838
+ "channels.displayName": "Display name",
839
+ "channels.apiUrl": "API URL",
840
+ "channels.apiKey": "API key",
841
+ "channels.keyReplaceHint": "Configured — type a new value to replace it",
842
+ "channels.keyMissingHint": "Enter a key",
843
+ "channels.keyClear": "Clear key",
844
+ "channels.modelCatalogTitle": "Model catalog",
845
+ "channels.noModelsHint": "No models yet: run \"Detect\" to pull candidates, or add one manually; the alias (display name) defaults to the upstream model id and can be renamed.",
846
+ "channels.detect": "Re-detect",
847
+ "channels.detecting": "Detecting…",
848
+ "channels.detectSuccess": "Connected ✓ · {n} candidates",
849
+ "channels.detectFailed": "Detection failed: {error}",
850
+ "channels.detectOk": "Connected to this API",
851
+ "channels.modelAliasLabel": "Display name (alias)",
852
+ "channels.modelIdLabel": "Upstream model id",
853
+ "channels.generated": "Generated {n} time(s)",
854
+ "channels.unknownProtocol": "Unknown protocol — best-effort OpenAI",
855
+ "channels.removeModel": "Remove model",
856
+ "channels.manualAddPlaceholder": "Enter a model id, e.g. qwen-image",
857
+ "channels.addModelConfirm": "Add",
858
+ "channels.copyFrom": "Copy from another channel…",
859
+ "channels.copyApply": "Copy",
860
+ "channels.candidatesTitle": "Candidates (click to add)",
861
+ "channels.candidatesQuick": "Add only known protocols",
862
+ "channels.deleteThisChannel": "Delete this channel",
863
+ "channels.setDefault": "Set as default channel",
864
+ "channels.presetPickerTitle": "Add provider",
865
+ "channels.presetPickerHint": "Pick a built-in provider — only the API key is needed",
866
+ "channels.presetModelsHint": "{n} preset model(s)",
867
+ "channels.presetCustomHint": "Configure the API URL, key, and model catalog yourself",
868
+ "channels.presetLoadFailed": "Failed to load providers: {error}"
751
869
  };
752
870
  //#endregion
753
871
  //#region src/client/helpers.ts
@@ -785,47 +903,47 @@ window.__ModuleLoader__.load({
785
903
  document.head.appendChild(tag);
786
904
  }
787
905
  var templates_module_css_default = {
788
- "toolbar": "o0mAxG_toolbar",
789
- "thumbWrap": "o0mAxG_thumbWrap",
790
- "cardBody": "o0mAxG_cardBody",
791
- "close": "o0mAxG_close",
792
- "featuredBadge": "o0mAxG_featuredBadge",
793
- "heading": "o0mAxG_heading",
794
- "thumb": "o0mAxG_thumb",
795
- "cardCategory": "o0mAxG_cardCategory",
796
- "footer": "o0mAxG_footer",
797
- "meta": "o0mAxG_meta",
798
- "cardMeta": "o0mAxG_cardMeta",
799
- "detailPrompt": "o0mAxG_detailPrompt",
906
+ "detailMeta": "o0mAxG_detailMeta",
800
907
  "body": "o0mAxG_body",
801
- "cardTitle": "o0mAxG_cardTitle",
802
- "detailOverlay": "o0mAxG_detailOverlay",
908
+ "cardSource": "o0mAxG_cardSource",
909
+ "notice": "o0mAxG_notice",
910
+ "cardCategory": "o0mAxG_cardCategory",
911
+ "detailActions": "o0mAxG_detailActions",
912
+ "overlay": "o0mAxG_overlay",
803
913
  "shell": "o0mAxG_shell",
914
+ "cardMeta": "o0mAxG_cardMeta",
915
+ "categoryPill": "o0mAxG_categoryPill",
916
+ "thumbWrap": "o0mAxG_thumbWrap",
804
917
  "thumbPlaceholder": "o0mAxG_thumbPlaceholder",
805
- "detailMedia": "o0mAxG_detailMedia",
918
+ "cardBody": "o0mAxG_cardBody",
919
+ "attribution": "o0mAxG_attribution",
920
+ "sourceLink": "o0mAxG_sourceLink",
806
921
  "detailInfo": "o0mAxG_detailInfo",
922
+ "detail": "o0mAxG_detail",
807
923
  "detailTitle": "o0mAxG_detailTitle",
808
- "detailMeta": "o0mAxG_detailMeta",
809
- "attribution": "o0mAxG_attribution",
924
+ "footer": "o0mAxG_footer",
925
+ "detailPrompt": "o0mAxG_detailPrompt",
926
+ "thumb": "o0mAxG_thumb",
810
927
  "detailLink": "o0mAxG_detailLink",
928
+ "title": "o0mAxG_title",
929
+ "meta": "o0mAxG_meta",
930
+ "toolbar": "o0mAxG_toolbar",
931
+ "detailOverlay": "o0mAxG_detailOverlay",
932
+ "detailImage": "o0mAxG_detailImage",
933
+ "categoryRow": "o0mAxG_categoryRow",
811
934
  "dsh-imagegen-templates-spin": "o0mAxG_dsh-imagegen-templates-spin",
812
- "detail": "o0mAxG_detail",
813
- "sourceLink": "o0mAxG_sourceLink",
814
- "grid": "o0mAxG_grid",
935
+ "headerActions": "o0mAxG_headerActions",
815
936
  "search": "o0mAxG_search",
816
937
  "card": "o0mAxG_card",
938
+ "heading": "o0mAxG_heading",
939
+ "spinner": "o0mAxG_spinner",
940
+ "close": "o0mAxG_close",
817
941
  "header": "o0mAxG_header",
942
+ "featuredBadge": "o0mAxG_featuredBadge",
943
+ "cardTitle": "o0mAxG_cardTitle",
944
+ "grid": "o0mAxG_grid",
818
945
  "state": "o0mAxG_state",
819
- "detailActions": "o0mAxG_detailActions",
820
- "categoryPill": "o0mAxG_categoryPill",
821
- "title": "o0mAxG_title",
822
- "detailImage": "o0mAxG_detailImage",
823
- "spinner": "o0mAxG_spinner",
824
- "notice": "o0mAxG_notice",
825
- "cardSource": "o0mAxG_cardSource",
826
- "categoryRow": "o0mAxG_categoryRow",
827
- "overlay": "o0mAxG_overlay",
828
- "headerActions": "o0mAxG_headerActions"
946
+ "detailMedia": "o0mAxG_detailMedia"
829
947
  };
830
948
  //#endregion
831
949
  //#region src/client/TemplateLibrary.tsx
@@ -1301,13 +1419,245 @@ window.__ModuleLoader__.load({
1301
1419
  }), document.body);
1302
1420
  }
1303
1421
  //#endregion
1422
+ //#region src/client/settings-scope.ts
1423
+ /**
1424
+ * Browser-side settings scope for the dsh-imagegen namespace, served by the
1425
+ * plugin's own loopback bridge routes (/api/dsh-imagegen/settings). The
1426
+ * official rc.6 settings scope answers "unavailable" for every third-party
1427
+ * namespace (the host-apiproxy allowlist is hard-coded), so this package
1428
+ * re-serves its namespace through the host settings seam over a same-origin,
1429
+ * loopback-only HTTP pair — the same pattern the dsh-web-ui family bridge
1430
+ * uses, self-contained per plugin.
1431
+ */
1432
+ /** Settings wire face over the bridge routes (fetch-backed). */
1433
+ function createBridgeApi(fetchFn) {
1434
+ const post = async (path, body) => {
1435
+ try {
1436
+ const response = await fetchFn(path, {
1437
+ method: "POST",
1438
+ headers: { "content-type": "application/json" },
1439
+ body: JSON.stringify(body)
1440
+ });
1441
+ if (!response.ok) return { result: {
1442
+ ok: false,
1443
+ code: "internal",
1444
+ message: `bridge HTTP ${response.status}`
1445
+ } };
1446
+ return { result: await response.json() };
1447
+ } catch {
1448
+ return { result: {
1449
+ ok: false,
1450
+ code: "internal",
1451
+ message: "settings bridge unreachable"
1452
+ } };
1453
+ }
1454
+ };
1455
+ return { settings: {
1456
+ describe: async (payload) => post(SETTINGS_API.describe, payload),
1457
+ mutate: async (payload) => post(SETTINGS_API.mutate, payload)
1458
+ } };
1459
+ }
1460
+ /**
1461
+ * A SettingsScope over the bridge face: serialized queue, revision-fenced
1462
+ * writes, recovery read after a refusal. Mirrors the official controller's
1463
+ * ordering but trusts the Host-seam value without re-running the wire-schema
1464
+ * validation — the seam already validated it.
1465
+ */
1466
+ var BridgeScopeController = class {
1467
+ api;
1468
+ spec;
1469
+ store;
1470
+ /** Whether the namespace currently holds a stored secret (e.g. apiKey). */
1471
+ keySet;
1472
+ /** Individual secret presence bits, keyed by the settings field name. */
1473
+ secretSets;
1474
+ tail = Promise.resolve();
1475
+ disposed = false;
1476
+ constructor(api, spec) {
1477
+ this.api = api;
1478
+ this.spec = spec;
1479
+ this.store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
1480
+ status: "loading",
1481
+ value: void 0,
1482
+ base: void 0,
1483
+ user: void 0,
1484
+ revision: void 0,
1485
+ writable: false,
1486
+ mode: "host"
1487
+ });
1488
+ this.keySet = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(false);
1489
+ this.secretSets = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({});
1490
+ }
1491
+ getSnapshot() {
1492
+ return this.store.getSnapshot();
1493
+ }
1494
+ /** Whether a stored secret exists (from the redacted view's secrets list). */
1495
+ getKeySetSnapshot() {
1496
+ return this.keySet.getSnapshot();
1497
+ }
1498
+ /** Observe the secret-set flag. */
1499
+ subscribeKeySet(listener) {
1500
+ return this.keySet.subscribe(listener);
1501
+ }
1502
+ /** Whether a specific secret field currently has a stored value. */
1503
+ getSecretSetSnapshot(field) {
1504
+ return this.secretSets.getSnapshot()[field] === true;
1505
+ }
1506
+ /** Observe changes to individual secret-field presence bits. */
1507
+ subscribeSecretSets(listener) {
1508
+ return this.secretSets.subscribe(listener);
1509
+ }
1510
+ subscribe(listener) {
1511
+ return this.store.subscribe(listener);
1512
+ }
1513
+ /** Queue a bridge refresh. */
1514
+ load() {
1515
+ return this.enqueue(() => this.read());
1516
+ }
1517
+ set(field, value) {
1518
+ return this.enqueue(() => this.writeOps([{
1519
+ op: "set",
1520
+ path: [field],
1521
+ value
1522
+ }]));
1523
+ }
1524
+ unset(field) {
1525
+ return this.enqueue(() => this.writeOps([{
1526
+ op: "unset",
1527
+ path: [field]
1528
+ }]));
1529
+ }
1530
+ /** Apply several path ops in one revision-fenced mutate call (atomic save).
1531
+ * Path ops may address plain-object fields (e.g. `channelSecrets.<id>`),
1532
+ * but never navigate *inside* arrays — write array fields wholesale. */
1533
+ mutateOps(ops) {
1534
+ return this.enqueue(() => this.writeOps(ops));
1535
+ }
1536
+ async dispose() {
1537
+ this.disposed = true;
1538
+ await this.tail;
1539
+ }
1540
+ enqueue(operation) {
1541
+ if (this.disposed) return Promise.resolve();
1542
+ const task = this.tail.then(async () => {
1543
+ if (this.disposed) return;
1544
+ await operation();
1545
+ });
1546
+ this.tail = task.catch(() => {});
1547
+ return task;
1548
+ }
1549
+ async read() {
1550
+ let response;
1551
+ try {
1552
+ response = await this.api.describe({});
1553
+ } catch {
1554
+ if (!this.disposed) this.store.update((draft) => {
1555
+ draft.status = "unavailable";
1556
+ });
1557
+ return;
1558
+ }
1559
+ if (!response.result.ok || this.disposed) {
1560
+ if (!this.disposed) this.store.update((draft) => {
1561
+ draft.status = "unavailable";
1562
+ });
1563
+ return;
1564
+ }
1565
+ const { namespaces, writable } = response.result.value;
1566
+ const view = namespaces?.find((candidate) => candidate.ns === this.spec.namespace);
1567
+ if (view === void 0) {
1568
+ this.store.update((draft) => {
1569
+ draft.status = "unavailable";
1570
+ draft.writable = writable === true;
1571
+ });
1572
+ this.keySet.set(false);
1573
+ this.secretSets.set({});
1574
+ return;
1575
+ }
1576
+ this.accept(view, writable);
1577
+ }
1578
+ async writeOps(ops) {
1579
+ const revision = this.getSnapshot().revision;
1580
+ let response;
1581
+ try {
1582
+ response = await this.api.mutate({
1583
+ ns: this.spec.namespace,
1584
+ ops,
1585
+ ...revision === void 0 ? {} : { expectedRevision: revision }
1586
+ });
1587
+ } catch {
1588
+ await this.read();
1589
+ return;
1590
+ }
1591
+ if (!response.result.ok || this.disposed) {
1592
+ await this.read();
1593
+ return;
1594
+ }
1595
+ this.accept(response.result.value, void 0);
1596
+ }
1597
+ accept(view, writable) {
1598
+ this.store.update((draft) => {
1599
+ draft.revision = view.revision;
1600
+ draft.base = view.base;
1601
+ draft.user = view.user;
1602
+ if (writable !== void 0) draft.writable = writable;
1603
+ draft.status = "ready";
1604
+ draft.value = view.value;
1605
+ });
1606
+ const secretSets = Object.fromEntries((view.secrets ?? []).map((secret) => [secret.path.join("."), secret.set]));
1607
+ this.keySet.set(Object.values(secretSets).some(Boolean));
1608
+ this.secretSets.set(secretSets);
1609
+ }
1610
+ };
1611
+ /**
1612
+ * Bind the dsh-imagegen settings scope over the bridge routes and start its
1613
+ * initial read (the caller mounts nothing until the scope settles).
1614
+ * @param fetchFn - the fetch implementation (the global fetch on loopback).
1615
+ * @returns the scope; unavailable when the bridge is unreachable.
1616
+ */
1617
+ function bindImageGenScope(fetchFn = fetch) {
1618
+ const controller = new BridgeScopeController(createBridgeApi(fetchFn).settings, { namespace: "dsh-imagegen" });
1619
+ controller.load();
1620
+ return controller;
1621
+ }
1622
+ /**
1623
+ * Flatten the configured channels into the model options the panel lists
1624
+ * (aliases; the default channel's models first) plus the default channel id.
1625
+ * Falls back to the legacy flat allow-list while no channels exist (upgrade
1626
+ * path). Pure projection — no host calls.
1627
+ */
1628
+ function imageModelOptions(config) {
1629
+ const channels = config?.channels ?? [];
1630
+ if (channels.length === 0) return { models: Array.isArray(config?.imageModels) ? config.imageModels.filter((model) => typeof model === "string" && model.trim() !== "") : [] };
1631
+ const defaultId = config?.defaultChannelId !== void 0 && channels.some((channel) => channel.id === config.defaultChannelId) ? config.defaultChannelId : channels[0].id;
1632
+ const ordered = [defaultId, ...channels.filter((channel) => channel.id !== defaultId).map((channel) => channel.id)];
1633
+ const models = [];
1634
+ for (const id of ordered) {
1635
+ const channel = channels.find((candidate) => candidate.id === id);
1636
+ for (const model of channel.models) if (model.alias !== "" && !models.includes(model.alias)) models.push(model.alias);
1637
+ }
1638
+ return models.length > 0 ? {
1639
+ models,
1640
+ defaultChannelId: defaultId
1641
+ } : {
1642
+ models: [],
1643
+ defaultChannelId: defaultId
1644
+ };
1645
+ }
1646
+ //#endregion
1304
1647
  //#region src/image-models.ts
1305
1648
  /**
1306
1649
  * Image-model configuration shared by the host, panel, and Agent tools.
1307
1650
  * `/models` exposes candidates only: the configured list is the explicit
1308
1651
  * allow-list because OpenAI-compatible gateways rarely advertise modalities.
1309
1652
  */
1310
- const DEFAULT_IMAGE_MODELS = ["gpt-image-2", "grok-imagine-image"];
1653
+ const DEFAULT_IMAGE_MODELS = [
1654
+ "gpt-image-2",
1655
+ "grok-imagine-image",
1656
+ "nanobanana2",
1657
+ "nanobanana2-lite",
1658
+ "nanobanana-pro",
1659
+ "seedream-5.0-pro"
1660
+ ];
1311
1661
  /** Normalize user-entered model identifiers and retain a usable legacy default. */
1312
1662
  function normalizeImageModels(value) {
1313
1663
  const candidates = Array.isArray(value) ? value : [];
@@ -1331,166 +1681,166 @@ window.__ModuleLoader__.load({
1331
1681
  document.head.appendChild(tag);
1332
1682
  }
1333
1683
  var panel_module_css_default = {
1334
- "galleryFilter": "Yvqh9W_galleryFilter",
1335
- "galleryCard": "Yvqh9W_galleryCard",
1336
- "updateBanner": "Yvqh9W_updateBanner",
1337
- "updateText": "Yvqh9W_updateText",
1338
- "canvasBody": "Yvqh9W_canvasBody",
1339
- "taskPrompt": "Yvqh9W_taskPrompt",
1340
- "uploadIcon": "Yvqh9W_uploadIcon",
1341
- "configScroll": "Yvqh9W_configScroll",
1342
- "galleryImage": "Yvqh9W_galleryImage",
1343
- "configGuideBody": "Yvqh9W_configGuideBody",
1344
- "promptFooter": "Yvqh9W_promptFooter",
1345
- "modelLabel": "Yvqh9W_modelLabel",
1346
- "historyEmpty": "Yvqh9W_historyEmpty",
1347
- "galleryFilterDivider": "Yvqh9W_galleryFilterDivider",
1348
- "historyThumb": "Yvqh9W_historyThumb",
1349
- "galleryViewToggle": "Yvqh9W_galleryViewToggle",
1350
- "comparisonFullscreen": "Yvqh9W_comparisonFullscreen",
1351
- "historyTitle": "Yvqh9W_historyTitle",
1352
- "historyAction": "Yvqh9W_historyAction",
1353
- "modelSelect": "Yvqh9W_modelSelect",
1354
- "lightboxMeta": "Yvqh9W_lightboxMeta",
1355
- "gallerySelectionBar": "Yvqh9W_gallerySelectionBar",
1684
+ "image": "Yvqh9W_image",
1685
+ "galleryTagInput": "Yvqh9W_galleryTagInput",
1686
+ "lightboxCaptionRow": "Yvqh9W_lightboxCaptionRow",
1687
+ "bigSpinner": "Yvqh9W_bigSpinner",
1356
1688
  "galleryTagEdit": "Yvqh9W_galleryTagEdit",
1357
- "historyPrompt": "Yvqh9W_historyPrompt",
1689
+ "canvasState": "Yvqh9W_canvasState",
1690
+ "compareControl": "Yvqh9W_compareControl",
1691
+ "galleryTagFilter": "Yvqh9W_galleryTagFilter",
1358
1692
  "taskTrayCount": "Yvqh9W_taskTrayCount",
1359
- "historyMeta": "Yvqh9W_historyMeta",
1360
- "hiddenFile": "Yvqh9W_hiddenFile",
1361
- "gallerySelectMode": "Yvqh9W_gallerySelectMode",
1362
- "historyThumbPlaceholder": "Yvqh9W_historyThumbPlaceholder",
1693
+ "historyEmpty": "Yvqh9W_historyEmpty",
1694
+ "gallerySelectionClear": "Yvqh9W_gallerySelectionClear",
1695
+ "historyPrompt": "Yvqh9W_historyPrompt",
1696
+ "galleryFilterNote": "Yvqh9W_galleryFilterNote",
1697
+ "imageCaption": "Yvqh9W_imageCaption",
1698
+ "compareModelChoices": "Yvqh9W_compareModelChoices",
1363
1699
  "entryLabel": "Yvqh9W_entryLabel",
1364
- "uploadBox": "Yvqh9W_uploadBox",
1365
- "taskRows": "Yvqh9W_taskRows",
1366
- "historyHeader": "Yvqh9W_historyHeader",
1367
- "optionGrid": "Yvqh9W_optionGrid",
1368
- "lightboxDownload": "Yvqh9W_lightboxDownload",
1700
+ "panelHeader": "Yvqh9W_panelHeader",
1701
+ "comparisonGrid": "Yvqh9W_comparisonGrid",
1702
+ "galleryCard": "Yvqh9W_galleryCard",
1703
+ "galleryTagFilterList": "Yvqh9W_galleryTagFilterList",
1704
+ "galleryCardInfo": "Yvqh9W_galleryCardInfo",
1705
+ "galleryViewToggle": "Yvqh9W_galleryViewToggle",
1706
+ "history": "Yvqh9W_history",
1707
+ "comparisonFullscreenGrid": "Yvqh9W_comparisonFullscreenGrid",
1708
+ "taskRow": "Yvqh9W_taskRow",
1369
1709
  "prompt": "Yvqh9W_prompt",
1370
- "galleryHeading": "Yvqh9W_galleryHeading",
1371
- "canvas": "Yvqh9W_canvas",
1372
- "bigSpinner": "Yvqh9W_bigSpinner",
1373
- "lightboxNav": "Yvqh9W_lightboxNav",
1374
- "modelMenuItem": "Yvqh9W_modelMenuItem",
1375
- "uploadHint": "Yvqh9W_uploadHint",
1376
- "compareModelChoices": "Yvqh9W_compareModelChoices",
1377
- "optionRow": "Yvqh9W_optionRow",
1378
- "paramHint": "Yvqh9W_paramHint",
1379
- "lightboxZoomLevel": "Yvqh9W_lightboxZoomLevel",
1380
- "grid": "Yvqh9W_grid",
1381
- "lightboxTools": "Yvqh9W_lightboxTools",
1382
- "connectionDot": "Yvqh9W_connectionDot",
1383
- "githubLink": "Yvqh9W_githubLink",
1710
+ "taskTrayHeader": "Yvqh9W_taskTrayHeader",
1711
+ "optionGrid": "Yvqh9W_optionGrid",
1712
+ "taskTray": "Yvqh9W_taskTray",
1384
1713
  "canvasStateTitle": "Yvqh9W_canvasStateTitle",
1385
- "canvasError": "Yvqh9W_canvasError",
1386
- "studio": "Yvqh9W_studio",
1387
- "spinner": "Yvqh9W_spinner",
1388
- "imageCaption": "Yvqh9W_imageCaption",
1714
+ "taskPrompt": "Yvqh9W_taskPrompt",
1715
+ "connectionDot": "Yvqh9W_connectionDot",
1716
+ "galleryFilter": "Yvqh9W_galleryFilter",
1389
1717
  "dshImageGenSpin": "Yvqh9W_dshImageGenSpin",
1390
- "galleryRemove": "Yvqh9W_galleryRemove",
1391
- "taskTrayToggle": "Yvqh9W_taskTrayToggle",
1718
+ "galleryRatioList": "Yvqh9W_galleryRatioList",
1719
+ "gallerySort": "Yvqh9W_gallerySort",
1720
+ "grid": "Yvqh9W_grid",
1721
+ "gallerySelectionBar": "Yvqh9W_gallerySelectionBar",
1722
+ "templatesButton": "Yvqh9W_templatesButton",
1392
1723
  "updateActions": "Yvqh9W_updateActions",
1393
- "lightbox": "Yvqh9W_lightbox",
1394
- "lightboxFigure": "Yvqh9W_lightboxFigure",
1395
- "comparisonBoard": "Yvqh9W_comparisonBoard",
1396
- "modelMenuList": "Yvqh9W_modelMenuList",
1397
- "lightboxImage": "Yvqh9W_lightboxImage",
1398
- "galleryTagEditor": "Yvqh9W_galleryTagEditor",
1399
- "comparisonGrid": "Yvqh9W_comparisonGrid",
1724
+ "configGuide": "Yvqh9W_configGuide",
1725
+ "modelLabel": "Yvqh9W_modelLabel",
1726
+ "configGuideBody": "Yvqh9W_configGuideBody",
1727
+ "paramLabel": "Yvqh9W_paramLabel",
1728
+ "taskTrayToggle": "Yvqh9W_taskTrayToggle",
1729
+ "canvas": "Yvqh9W_canvas",
1730
+ "entryIcon": "Yvqh9W_entryIcon",
1731
+ "generateInner": "Yvqh9W_generateInner",
1732
+ "historyInfo": "Yvqh9W_historyInfo",
1733
+ "updateBanner": "Yvqh9W_updateBanner",
1734
+ "zoomHint": "Yvqh9W_zoomHint",
1735
+ "lightboxActions": "Yvqh9W_lightboxActions",
1736
+ "galleryAvatar": "Yvqh9W_galleryAvatar",
1737
+ "galleryHeading": "Yvqh9W_galleryHeading",
1738
+ "download": "Yvqh9W_download",
1739
+ "historyActions": "Yvqh9W_historyActions",
1740
+ "panelTitle": "Yvqh9W_panelTitle",
1741
+ "galleryCount": "Yvqh9W_galleryCount",
1742
+ "reference": "Yvqh9W_reference",
1743
+ "galleryMasonry": "Yvqh9W_galleryMasonry",
1744
+ "galleryRemove": "Yvqh9W_galleryRemove",
1745
+ "historyTitle": "Yvqh9W_historyTitle",
1746
+ "connectionStatus": "Yvqh9W_connectionStatus",
1747
+ "historyMain": "Yvqh9W_historyMain",
1400
1748
  "galleryToolbar": "Yvqh9W_galleryToolbar",
1749
+ "configScroll": "Yvqh9W_configScroll",
1750
+ "comparisonFullscreen": "Yvqh9W_comparisonFullscreen",
1751
+ "historyHeader": "Yvqh9W_historyHeader",
1752
+ "imageCard": "Yvqh9W_imageCard",
1753
+ "updateRelease": "Yvqh9W_updateRelease",
1754
+ "canvasBody": "Yvqh9W_canvasBody",
1401
1755
  "promptCount": "Yvqh9W_promptCount",
1402
- "config": "Yvqh9W_config",
1403
- "modelWrap": "Yvqh9W_modelWrap",
1404
- "panelHeader": "Yvqh9W_panelHeader",
1405
- "taskRow": "Yvqh9W_taskRow",
1406
- "templatesButton": "Yvqh9W_templatesButton",
1407
1756
  "paramGroup": "Yvqh9W_paramGroup",
1408
- "galleryFilterNote": "Yvqh9W_galleryFilterNote",
1409
- "galleryCount": "Yvqh9W_galleryCount",
1410
- "galleryWorkspace": "Yvqh9W_galleryWorkspace",
1411
- "gallerySearch": "Yvqh9W_gallerySearch",
1412
- "historyActions": "Yvqh9W_historyActions",
1413
1757
  "generateButton": "Yvqh9W_generateButton",
1414
- "lightboxCaptionRow": "Yvqh9W_lightboxCaptionRow",
1415
- "historySearch": "Yvqh9W_historySearch",
1416
- "panelTitle": "Yvqh9W_panelTitle",
1417
- "modeRow": "Yvqh9W_modeRow",
1418
- "galleryImageButton": "Yvqh9W_galleryImageButton",
1419
- "lightboxIndex": "Yvqh9W_lightboxIndex",
1420
- "canvasState": "Yvqh9W_canvasState",
1758
+ "lightboxCaption": "Yvqh9W_lightboxCaption",
1759
+ "canvasHistoryTag": "Yvqh9W_canvasHistoryTag",
1760
+ "uploadBox": "Yvqh9W_uploadBox",
1761
+ "lightboxNav": "Yvqh9W_lightboxNav",
1762
+ "updateText": "Yvqh9W_updateText",
1763
+ "galleryTags": "Yvqh9W_galleryTags",
1764
+ "canvasEmptyIcon": "Yvqh9W_canvasEmptyIcon",
1765
+ "gallerySelectMode": "Yvqh9W_gallerySelectMode",
1766
+ "galleryWorkspace": "Yvqh9W_galleryWorkspace",
1767
+ "galleryRatio": "Yvqh9W_galleryRatio",
1768
+ "spinner": "Yvqh9W_spinner",
1769
+ "panel": "Yvqh9W_panel",
1421
1770
  "historyClear": "Yvqh9W_historyClear",
1771
+ "lightboxTools": "Yvqh9W_lightboxTools",
1772
+ "card": "Yvqh9W_card",
1773
+ "compareToggle": "Yvqh9W_compareToggle",
1774
+ "lightboxEdit": "Yvqh9W_lightboxEdit",
1775
+ "uploadHint": "Yvqh9W_uploadHint",
1776
+ "enhanceButton": "Yvqh9W_enhanceButton",
1777
+ "footer": "Yvqh9W_footer",
1778
+ "galleryBadge": "Yvqh9W_galleryBadge",
1779
+ "historyFilters": "Yvqh9W_historyFilters",
1780
+ "gallerySelect": "Yvqh9W_gallerySelect",
1422
1781
  "modePill": "Yvqh9W_modePill",
1423
- "galleryClear": "Yvqh9W_galleryClear",
1424
- "generateInner": "Yvqh9W_generateInner",
1425
- "galleryCardFooter": "Yvqh9W_galleryCardFooter",
1426
1782
  "view": "Yvqh9W_view",
1427
- "lightboxCopy": "Yvqh9W_lightboxCopy",
1428
- "gallerySelect": "Yvqh9W_gallerySelect",
1429
- "galleryCardInfo": "Yvqh9W_galleryCardInfo",
1430
- "referenceActions": "Yvqh9W_referenceActions",
1431
- "dshImageGenToastIn": "Yvqh9W_dshImageGenToastIn",
1432
- "historyList": "Yvqh9W_historyList",
1433
- "entryIcon": "Yvqh9W_entryIcon",
1434
- "taskTray": "Yvqh9W_taskTray",
1435
- "galleryRatio": "Yvqh9W_galleryRatio",
1436
- "compareControl": "Yvqh9W_compareControl",
1437
- "taskTrayChevron": "Yvqh9W_taskTrayChevron",
1438
- "historyInfo": "Yvqh9W_historyInfo",
1439
- "galleryFilterHeading": "Yvqh9W_galleryFilterHeading",
1440
- "lightboxClose": "Yvqh9W_lightboxClose",
1441
- "galleryTagInput": "Yvqh9W_galleryTagInput",
1442
- "galleryToast": "Yvqh9W_galleryToast",
1443
- "taskTrayHeader": "Yvqh9W_taskTrayHeader",
1444
- "comparisonFullscreenGrid": "Yvqh9W_comparisonFullscreenGrid",
1445
- "lightboxTool": "Yvqh9W_lightboxTool",
1446
1783
  "taskStatus": "Yvqh9W_taskStatus",
1447
- "galleryAdd": "Yvqh9W_galleryAdd",
1784
+ "modeRow": "Yvqh9W_modeRow",
1785
+ "comparisonBoard": "Yvqh9W_comparisonBoard",
1786
+ "taskRows": "Yvqh9W_taskRows",
1787
+ "galleryImageButton": "Yvqh9W_galleryImageButton",
1788
+ "uploadIcon": "Yvqh9W_uploadIcon",
1789
+ "historyList": "Yvqh9W_historyList",
1790
+ "promptFooter": "Yvqh9W_promptFooter",
1791
+ "panelHeading": "Yvqh9W_panelHeading",
1792
+ "optionPill": "Yvqh9W_optionPill",
1793
+ "modelMenuList": "Yvqh9W_modelMenuList",
1794
+ "canvasError": "Yvqh9W_canvasError",
1448
1795
  "canvasStateHint": "Yvqh9W_canvasStateHint",
1449
- "imageCard": "Yvqh9W_imageCard",
1450
- "image": "Yvqh9W_image",
1451
- "galleryTagFilter": "Yvqh9W_galleryTagFilter",
1452
- "historyMain": "Yvqh9W_historyMain",
1453
- "configGuide": "Yvqh9W_configGuide",
1454
- "download": "Yvqh9W_download",
1455
- "galleryBadge": "Yvqh9W_galleryBadge",
1456
- "canvasEmptyIcon": "Yvqh9W_canvasEmptyIcon",
1457
- "taskTrayClose": "Yvqh9W_taskTrayClose",
1458
- "referenceImage": "Yvqh9W_referenceImage",
1459
- "historyItem": "Yvqh9W_historyItem",
1460
- "historyFilters": "Yvqh9W_historyFilters",
1461
- "reference": "Yvqh9W_reference",
1796
+ "referenceActions": "Yvqh9W_referenceActions",
1462
1797
  "modelMenu": "Yvqh9W_modelMenu",
1463
- "lightboxActions": "Yvqh9W_lightboxActions",
1464
- "galleryMasonry": "Yvqh9W_galleryMasonry",
1465
- "connectionStatus": "Yvqh9W_connectionStatus",
1466
- "history": "Yvqh9W_history",
1467
- "updateRelease": "Yvqh9W_updateRelease",
1798
+ "modelMenuItem": "Yvqh9W_modelMenuItem",
1799
+ "lightbox": "Yvqh9W_lightbox",
1800
+ "lightboxMeta": "Yvqh9W_lightboxMeta",
1801
+ "lightboxDownload": "Yvqh9W_lightboxDownload",
1802
+ "historyMeta": "Yvqh9W_historyMeta",
1468
1803
  "canvasMeta": "Yvqh9W_canvasMeta",
1469
- "gallerySort": "Yvqh9W_gallerySort",
1470
- "galleryAvatar": "Yvqh9W_galleryAvatar",
1471
- "lightboxCaption": "Yvqh9W_lightboxCaption",
1804
+ "taskTrayChevron": "Yvqh9W_taskTrayChevron",
1805
+ "historyItem": "Yvqh9W_historyItem",
1806
+ "config": "Yvqh9W_config",
1807
+ "optionRow": "Yvqh9W_optionRow",
1808
+ "modelWrap": "Yvqh9W_modelWrap",
1809
+ "modelSelect": "Yvqh9W_modelSelect",
1810
+ "lightboxTool": "Yvqh9W_lightboxTool",
1811
+ "dshImageGenToastIn": "Yvqh9W_dshImageGenToastIn",
1472
1812
  "galleryFilterCount": "Yvqh9W_galleryFilterCount",
1473
- "compareToggle": "Yvqh9W_compareToggle",
1474
- "enhanceButton": "Yvqh9W_enhanceButton",
1813
+ "galleryFilterDivider": "Yvqh9W_galleryFilterDivider",
1814
+ "galleryClear": "Yvqh9W_galleryClear",
1815
+ "galleryCardFooter": "Yvqh9W_galleryCardFooter",
1816
+ "galleryTagEditor": "Yvqh9W_galleryTagEditor",
1817
+ "studio": "Yvqh9W_studio",
1818
+ "referenceImage": "Yvqh9W_referenceImage",
1475
1819
  "entry": "Yvqh9W_entry",
1476
- "card": "Yvqh9W_card",
1820
+ "galleryAdd": "Yvqh9W_galleryAdd",
1821
+ "lightboxClose": "Yvqh9W_lightboxClose",
1477
1822
  "galleryToolbarActions": "Yvqh9W_galleryToolbarActions",
1478
- "panelHeading": "Yvqh9W_panelHeading",
1479
- "canvasHistoryTag": "Yvqh9W_canvasHistoryTag",
1480
- "optionPill": "Yvqh9W_optionPill",
1481
- "galleryTagFilterList": "Yvqh9W_galleryTagFilterList",
1823
+ "lightboxIndex": "Yvqh9W_lightboxIndex",
1824
+ "paramHint": "Yvqh9W_paramHint",
1825
+ "historyAction": "Yvqh9W_historyAction",
1826
+ "lightboxImage": "Yvqh9W_lightboxImage",
1827
+ "githubLink": "Yvqh9W_githubLink",
1828
+ "hiddenFile": "Yvqh9W_hiddenFile",
1829
+ "lightboxZoomLevel": "Yvqh9W_lightboxZoomLevel",
1830
+ "galleryImage": "Yvqh9W_galleryImage",
1482
1831
  "galleryBulkButton": "Yvqh9W_galleryBulkButton",
1483
- "footer": "Yvqh9W_footer",
1484
- "paramLabel": "Yvqh9W_paramLabel",
1485
- "zoomHint": "Yvqh9W_zoomHint",
1832
+ "gallerySearch": "Yvqh9W_gallerySearch",
1833
+ "historySearch": "Yvqh9W_historySearch",
1834
+ "lightboxScaleFrame": "Yvqh9W_lightboxScaleFrame",
1835
+ "historyThumb": "Yvqh9W_historyThumb",
1486
1836
  "galleryFilters": "Yvqh9W_galleryFilters",
1487
- "lightboxEdit": "Yvqh9W_lightboxEdit",
1488
- "galleryRatioList": "Yvqh9W_galleryRatioList",
1489
- "gallerySelectionClear": "Yvqh9W_gallerySelectionClear",
1490
- "galleryTags": "Yvqh9W_galleryTags",
1491
- "lightboxStage": "Yvqh9W_lightboxStage",
1492
- "panel": "Yvqh9W_panel",
1493
- "lightboxScaleFrame": "Yvqh9W_lightboxScaleFrame"
1837
+ "lightboxCopy": "Yvqh9W_lightboxCopy",
1838
+ "taskTrayClose": "Yvqh9W_taskTrayClose",
1839
+ "galleryToast": "Yvqh9W_galleryToast",
1840
+ "galleryFilterHeading": "Yvqh9W_galleryFilterHeading",
1841
+ "historyThumbPlaceholder": "Yvqh9W_historyThumbPlaceholder",
1842
+ "lightboxFigure": "Yvqh9W_lightboxFigure",
1843
+ "lightboxStage": "Yvqh9W_lightboxStage"
1494
1844
  };
1495
1845
  //#endregion
1496
1846
  //#region src/client/ImageGenPanel.tsx
@@ -1597,10 +1947,15 @@ window.__ModuleLoader__.load({
1597
1947
  function useElapsed(running, startedAt) {
1598
1948
  const [elapsed, setElapsed] = (0, react.useState)(0);
1599
1949
  (0, react.useEffect)(() => {
1600
- if (!running || startedAt === null) return;
1601
- const timer = window.setInterval(() => {
1950
+ if (!running || startedAt === null) {
1951
+ setElapsed(0);
1952
+ return;
1953
+ }
1954
+ const update = () => {
1602
1955
  setElapsed(Math.max(1, Math.round((Date.now() - startedAt) / 1e3)));
1603
- }, 1e3);
1956
+ };
1957
+ update();
1958
+ const timer = window.setInterval(update, 1e3);
1604
1959
  return () => window.clearInterval(timer);
1605
1960
  }, [running, startedAt]);
1606
1961
  return elapsed;
@@ -1642,28 +1997,31 @@ window.__ModuleLoader__.load({
1642
1997
  const { api, scope } = props;
1643
1998
  const config = useConfig(scope);
1644
1999
  const enabled = config?.enabled ?? true;
1645
- const configured = (config?.apiUrl ?? "").trim() !== "";
1646
- const apiKeySet = useSecretSet(scope, "apiKey");
2000
+ const modelOptions = imageModelOptions(config);
2001
+ const imageModels = (config?.channels ?? []).length > 0 ? modelOptions.models : normalizeImageModels(config?.imageModels);
2002
+ const defaultChannelId = modelOptions.defaultChannelId;
2003
+ const configured = (defaultChannelId !== void 0 && (config?.channels ?? []).length > 0 ? config.channels.find((channel) => channel.id === defaultChannelId)?.apiUrl ?? "" : config?.apiUrl ?? "").trim() !== "";
2004
+ const legacyKeySet = useSecretSet(scope, "apiKey");
1647
2005
  const promptKeySet = useSecretSet(scope, "promptApiKey");
2006
+ const channelKeySet = (config?.channels ?? []).some((channel) => scope.getSecretSetSnapshot(`channelSecrets.${channel.id}`));
2007
+ const apiKeySet = (config?.channels ?? []).length > 0 ? channelKeySet : legacyKeySet;
1648
2008
  const connected = enabled && configured && apiKeySet;
1649
- const imageModels = normalizeImageModels(config?.imageModels);
1650
2009
  const [tab, setTab] = (0, react.useState)("text");
1651
2010
  const [prompt, setPrompt] = (0, react.useState)("");
1652
2011
  const [size, setSize] = (0, react.useState)("auto");
1653
2012
  const [quality, setQuality] = (0, react.useState)("auto");
1654
2013
  const [count, setCount] = (0, react.useState)(1);
1655
2014
  const [detail, setDetail] = (0, react.useState)("");
1656
- const [model, setModel] = (0, react.useState)(DEFAULT_IMAGE_MODELS[0]);
2015
+ const [model, setModel] = (0, react.useState)("");
1657
2016
  const [compareEnabled, setCompareEnabled] = (0, react.useState)(false);
1658
- const [compareModels, setCompareModels] = (0, react.useState)([...DEFAULT_IMAGE_MODELS]);
2017
+ const [compareModels, setCompareModels] = (0, react.useState)([]);
1659
2018
  const [modelOpen, setModelOpen] = (0, react.useState)(false);
1660
2019
  const [refImage, setRefImage] = (0, react.useState)(null);
1661
2020
  const [images, setImages] = (0, react.useState)([]);
1662
2021
  const [error, setError] = (0, react.useState)(null);
1663
- const [generating, setGenerating] = (0, react.useState)(false);
2022
+ const [submitting, setSubmitting] = (0, react.useState)(false);
1664
2023
  const [enhancing, setEnhancing] = (0, react.useState)(false);
1665
2024
  const [configGuide, setConfigGuide] = (0, react.useState)(null);
1666
- const [startedAt, setStartedAt] = (0, react.useState)(null);
1667
2025
  const [history, setHistory] = (0, react.useState)([]);
1668
2026
  const [viewingHistoryId, setViewingHistoryId] = (0, react.useState)(null);
1669
2027
  const [gallery, setGallery] = (0, react.useState)([]);
@@ -1698,7 +2056,10 @@ window.__ModuleLoader__.load({
1698
2056
  const [comparisonFullscreen, setComparisonFullscreen] = (0, react.useState)(false);
1699
2057
  const fileInput = (0, react.useRef)(null);
1700
2058
  const previewStage = (0, react.useRef)(null);
1701
- const elapsed = useElapsed(generating, startedAt);
2059
+ const activeTasks = tasks.filter((task) => task.status === "queued" || task.status === "running");
2060
+ const activeTask = activeTasks.find((task) => task.status === "running") ?? activeTasks[0];
2061
+ const generating = submitting || activeTasks.length > 0;
2062
+ const elapsed = useElapsed(generating, activeTask?.startedAt ?? activeTask?.createdAt ?? null);
1702
2063
  (0, react.useEffect)(() => {
1703
2064
  setModel((previous) => imageModels.includes(previous) ? previous : imageModels[0]);
1704
2065
  setCompareModels((previous) => {
@@ -1847,7 +2208,7 @@ window.__ModuleLoader__.load({
1847
2208
  };
1848
2209
  /** Run one generation. */
1849
2210
  const handleGenerate = async () => {
1850
- if (generating) return;
2211
+ if (submitting) return;
1851
2212
  if (!enabled) {
1852
2213
  openSettingsGuide("disabled");
1853
2214
  return;
@@ -1873,10 +2234,12 @@ window.__ModuleLoader__.load({
1873
2234
  quality,
1874
2235
  n: count,
1875
2236
  detail,
2237
+ ...defaultChannelId !== void 0 ? { channelId: defaultChannelId } : {},
1876
2238
  ...tab === "edit" && refImage !== null ? { image: refImage.dataUrl } : {},
1877
2239
  ...tab === "edit" && refImage !== null ? { refName: refImage.name } : {}
1878
2240
  };
1879
2241
  setError(null);
2242
+ setSubmitting(true);
1880
2243
  try {
1881
2244
  const targetModels = (compareEnabled ? compareModels : [request.model]).filter((candidate) => imageModels.includes(candidate));
1882
2245
  if (targetModels.length === 0) {
@@ -1894,6 +2257,8 @@ window.__ModuleLoader__.load({
1894
2257
  } : null);
1895
2258
  } catch (caught) {
1896
2259
  setError(errorMessage(caught));
2260
+ } finally {
2261
+ setSubmitting(false);
1897
2262
  }
1898
2263
  };
1899
2264
  /** Open the full-screen image preview at a given index. */
@@ -2156,7 +2521,7 @@ window.__ModuleLoader__.load({
2156
2521
  anchor.click();
2157
2522
  });
2158
2523
  };
2159
- const generateDisabled = generating;
2524
+ const generateDisabled = submitting;
2160
2525
  const viewingEntry = viewingHistoryId === null ? null : history.find((entry) => entry.id === viewingHistoryId) ?? null;
2161
2526
  const viewingGalleryEntry = galleryViewingId === null ? null : gallery.find((entry) => entry.id === galleryViewingId) ?? null;
2162
2527
  const previewImage = preview === null ? null : preview.images[preview.index] ?? null;
@@ -2602,7 +2967,7 @@ window.__ModuleLoader__.load({
2602
2967
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2603
2968
  type: "button",
2604
2969
  className: panel_module_css_default.modelSelect,
2605
- disabled: generating,
2970
+ disabled: submitting,
2606
2971
  "aria-haspopup": "listbox",
2607
2972
  "aria-expanded": modelOpen,
2608
2973
  onClick: () => {
@@ -2870,7 +3235,7 @@ window.__ModuleLoader__.load({
2870
3235
  children: [
2871
3236
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2872
3237
  className: panel_module_css_default.galleryAvatar,
2873
- children: entry.model.startsWith("grok") ? "G" : "D"
3238
+ children: entry.model.toLowerCase().startsWith("nanobanana") ? "N" : entry.model.toLowerCase().startsWith("seedream") ? "S" : entry.model.startsWith("grok") ? "G" : "D"
2874
3239
  }),
2875
3240
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2876
3241
  className: panel_module_css_default.galleryCardInfo,
@@ -2967,7 +3332,7 @@ window.__ModuleLoader__.load({
2967
3332
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("tasks.title") }),
2968
3333
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2969
3334
  className: panel_module_css_default.taskTrayCount,
2970
- children: tasks.filter((task) => task.status === "queued" || task.status === "running").length
3335
+ children: activeTasks.length
2971
3336
  }),
2972
3337
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2973
3338
  className: panel_module_css_default.taskTrayChevron,
@@ -3040,16 +3405,17 @@ window.__ModuleLoader__.load({
3040
3405
  }) : null,
3041
3406
  generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3042
3407
  className: panel_module_css_default.canvasState,
3408
+ "data-generation-state": activeTask?.status ?? "submitting",
3043
3409
  role: "status",
3044
3410
  children: [
3045
3411
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.bigSpinner }),
3046
3412
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3047
3413
  className: panel_module_css_default.canvasStateTitle,
3048
- children: tt("canvas.generating")
3414
+ children: submitting && activeTask === void 0 ? tt("canvas.submitting") : activeTask?.status === "queued" ? tt("canvas.queued") : tt("canvas.generating")
3049
3415
  }),
3050
3416
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3051
3417
  className: panel_module_css_default.canvasStateHint,
3052
- children: tt("canvas.elapsed", { seconds: elapsed })
3418
+ children: activeTask?.status === "queued" ? tt("canvas.queueHint", { count: activeTasks.length }) : tt("canvas.elapsed", { seconds: elapsed })
3053
3419
  })
3054
3420
  ]
3055
3421
  }) : null,
@@ -3973,20 +4339,6 @@ window.__ModuleLoader__.load({
3973
4339
  }
3974
4340
  };
3975
4341
  }
3976
- /** A newline/comma-separated model list, persisted as a normalized string array. */
3977
- function stringListField(field) {
3978
- return {
3979
- field,
3980
- format: (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string").join("\n") : "",
3981
- parse: (text) => {
3982
- const values = [...new Set(text.split(/[\n,]/).map((item) => item.trim()).filter(Boolean))];
3983
- return values.length === 0 ? { kind: "clear" } : {
3984
- kind: "set",
3985
- value: values
3986
- };
3987
- }
3988
- };
3989
- }
3990
4342
  /** A boolean field, edited through true/false draft text. */
3991
4343
  function booleanField(field) {
3992
4344
  return {
@@ -4200,23 +4552,354 @@ window.__ModuleLoader__.load({
4200
4552
  sectionValue(field) {
4201
4553
  return this.snapshotOf().value?.[field];
4202
4554
  }
4203
- baseValue(field) {
4204
- return this.snapshotOf().base?.[field];
4555
+ baseValue(field) {
4556
+ return this.snapshotOf().base?.[field];
4557
+ }
4558
+ userLayer() {
4559
+ return this.snapshotOf().user;
4560
+ }
4561
+ stored(field) {
4562
+ const user = this.userLayer();
4563
+ return user !== void 0 && Object.hasOwn(user, field);
4564
+ }
4565
+ publish() {
4566
+ for (const listener of [...this.listeners]) listener();
4567
+ }
4568
+ };
4569
+ //#endregion
4570
+ //#region src/client/channels-form.ts
4571
+ /**
4572
+ * Staged form model for the channel list of the settings card. Mirrors the
4573
+ * CardForm staging pattern (dirty → one save) but for a structured value, so
4574
+ * the card can edit N channels, per-channel keys, and the default-channel
4575
+ * flag, then persist everything in one revision-fenced mutate call.
4576
+ *
4577
+ * Storage rules (dictated by dsh-settings semantics):
4578
+ * - the whole `channels` array is written wholesale via `path: ['channels']`;
4579
+ * - every channel's API key lives at `channelSecrets.<channelId>` (a secret
4580
+ * dict), written per-key so untouched keys are never clobbered by a save
4581
+ * the reader could not see (keys are redacted out of the wire view);
4582
+ * - path ops never navigate *inside* the channels array.
4583
+ */
4584
+ /** Deep equality over JSON-compatible data (the change predicate). */
4585
+ function deepEqualJson(a, b) {
4586
+ if (a === b) return true;
4587
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
4588
+ if (Array.isArray(a) || Array.isArray(b)) {
4589
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
4590
+ return a.every((entry, index) => deepEqualJson(entry, b[index]));
4591
+ }
4592
+ const left = a;
4593
+ const right = b;
4594
+ const keys = Object.keys(left);
4595
+ if (keys.length !== Object.keys(right).length) return false;
4596
+ return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
4597
+ }
4598
+ /** Trim and normalize a draft channel (models never carry empty aliases). */
4599
+ function stripChannel(channel) {
4600
+ const models = channel.models.map((model) => ({
4601
+ alias: model.alias.trim(),
4602
+ id: model.id.trim() === "" ? model.alias.trim() : model.id.trim()
4603
+ })).filter((model) => model.alias !== "");
4604
+ return {
4605
+ id: channel.id,
4606
+ preset: channel.preset,
4607
+ name: channel.name.trim(),
4608
+ apiUrl: channel.apiUrl.trim(),
4609
+ models: [...new Map(models.map((model) => [model.alias, model])).values()]
4610
+ };
4611
+ }
4612
+ var ChannelsForm = class {
4613
+ scope;
4614
+ stagedChannels = null;
4615
+ stagedKeys = /* @__PURE__ */ new Map();
4616
+ stagedDefault = null;
4617
+ listeners = /* @__PURE__ */ new Set();
4618
+ saving = false;
4619
+ failed = false;
4620
+ constructor(scope) {
4621
+ this.scope = scope;
4622
+ scope.subscribe(() => {
4623
+ this.publish();
4624
+ });
4625
+ scope.subscribeSecretSets(() => {
4626
+ this.publish();
4627
+ });
4628
+ }
4629
+ /** Publish a projection of this form, rebuilt on every scope or draft change. */
4630
+ bind(project) {
4631
+ const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
4632
+ this.listeners.add(() => {
4633
+ store.set(project());
4634
+ });
4635
+ return store;
4636
+ }
4637
+ /** Subscribe to staged and persisted channel changes. */
4638
+ subscribe(listener) {
4639
+ this.listeners.add(listener);
4640
+ return () => {
4641
+ this.listeners.delete(listener);
4642
+ };
4643
+ }
4644
+ /** The staged channel list, or the scope value when nothing is staged. */
4645
+ channelsValue() {
4646
+ const view = this.scope.getSnapshot().value;
4647
+ return this.stagedChannels ?? (Array.isArray(view?.channels) ? view.channels.map(toDraft) : []);
4648
+ }
4649
+ /** Whether a channel currently holds a stored or staged secret. */
4650
+ keyHeld(id) {
4651
+ const edit = this.stagedKeys.get(id);
4652
+ if (edit !== void 0) return edit.kind === "set" && edit.value !== "";
4653
+ return this.scope.getSecretSetSnapshot(`channelSecrets.${id}`);
4654
+ }
4655
+ defaultValue() {
4656
+ if (this.stagedDefault !== null) return this.stagedDefault;
4657
+ const view = this.scope.getSnapshot().value;
4658
+ const channels = this.channelsValue();
4659
+ if (view?.defaultChannelId !== void 0 && channels.some((channel) => channel.id === view.defaultChannelId)) return view.defaultChannelId;
4660
+ return channels[0]?.id ?? "";
4661
+ }
4662
+ dirtyValue() {
4663
+ const channels = this.channelsValue();
4664
+ const stagedChanged = this.stagedChannels !== null && !deepEqualJson(this.stagedChannels, scopeChannelsOf(this.scope));
4665
+ const scopeDefault = this.scope.getSnapshot().value?.defaultChannelId ?? channels[0]?.id ?? "";
4666
+ const defaultChanged = this.stagedDefault !== null && this.stagedDefault !== scopeDefault;
4667
+ return stagedChanged || defaultChanged || this.stagedKeys.size > 0;
4668
+ }
4669
+ /** The card-facing snapshot. */
4670
+ snapshot() {
4671
+ const channels = this.channelsValue();
4672
+ const keySet = {};
4673
+ for (const channel of channels) keySet[channel.id] = this.keyHeld(channel.id);
4674
+ return {
4675
+ channels,
4676
+ keySet,
4677
+ defaultChannelId: this.defaultValue(),
4678
+ dirty: this.dirtyValue(),
4679
+ writable: this.scope.getSnapshot().writable !== false,
4680
+ saving: this.saving,
4681
+ failed: this.failed
4682
+ };
4683
+ }
4684
+ /** The actions the card's slot registration injects. */
4685
+ actions() {
4686
+ return {
4687
+ setChannels: (channels) => {
4688
+ this.stageChannels(channels);
4689
+ },
4690
+ setChannelKey: (id, value) => {
4691
+ this.stageKey(id, value);
4692
+ },
4693
+ setDefaultChannel: (id) => {
4694
+ this.stagedDefault = id;
4695
+ this.failed = false;
4696
+ this.publish();
4697
+ },
4698
+ commit: () => this.commit(),
4699
+ discard: () => {
4700
+ if (this.stagedChannels === null && this.stagedKeys.size === 0 && this.stagedDefault === null && !this.failed) return;
4701
+ this.stagedChannels = null;
4702
+ this.stagedKeys.clear();
4703
+ this.stagedDefault = null;
4704
+ this.failed = false;
4705
+ this.publish();
4706
+ }
4707
+ };
4708
+ }
4709
+ stageChannels(channels) {
4710
+ const cleaned = channels.map(stripChannel);
4711
+ this.stagedChannels = cleaned;
4712
+ this.failed = false;
4713
+ this.publish();
4714
+ }
4715
+ stageKey(id, value) {
4716
+ if (value === void 0 || value.trim() === "") {
4717
+ if (this.keyHeld(id)) this.stagedKeys.set(id, { kind: "clear" });
4718
+ } else this.stagedKeys.set(id, {
4719
+ kind: "set",
4720
+ value: value.trim()
4721
+ });
4722
+ this.failed = false;
4723
+ this.publish();
4205
4724
  }
4206
- userLayer() {
4207
- return this.snapshotOf().user;
4725
+ /** Build the single batch of path ops a save performs. */
4726
+ planOps() {
4727
+ const ops = [];
4728
+ if (this.stagedChannels !== null) {
4729
+ ops.push({
4730
+ op: "set",
4731
+ path: ["channels"],
4732
+ value: this.stagedChannels
4733
+ });
4734
+ ops.push({
4735
+ op: "unset",
4736
+ path: ["apiUrl"]
4737
+ });
4738
+ ops.push({
4739
+ op: "unset",
4740
+ path: ["apiKey"]
4741
+ });
4742
+ ops.push({
4743
+ op: "unset",
4744
+ path: ["imageModels"]
4745
+ });
4746
+ }
4747
+ for (const [id, edit] of this.stagedKeys) if (edit.kind === "set") ops.push({
4748
+ op: "set",
4749
+ path: ["channelSecrets", id],
4750
+ value: edit.value
4751
+ });
4752
+ else ops.push({
4753
+ op: "unset",
4754
+ path: ["channelSecrets", id]
4755
+ });
4756
+ if (this.stagedDefault !== null) ops.push({
4757
+ op: "set",
4758
+ path: ["defaultChannelId"],
4759
+ value: this.stagedDefault
4760
+ });
4761
+ return ops;
4208
4762
  }
4209
- stored(field) {
4210
- const user = this.userLayer();
4211
- return user !== void 0 && Object.hasOwn(user, field);
4763
+ /**
4764
+ * Write every staged edit, then re-seed from what the Host accepted.
4765
+ * @returns settlement after the write settles.
4766
+ */
4767
+ async commit() {
4768
+ if (this.saving) return;
4769
+ const ops = this.planOps();
4770
+ if (ops.length === 0) return;
4771
+ this.saving = true;
4772
+ this.failed = false;
4773
+ this.publish();
4774
+ try {
4775
+ await this.scope.mutateOps(ops);
4776
+ this.stagedChannels = null;
4777
+ this.stagedKeys.clear();
4778
+ this.stagedDefault = null;
4779
+ this.failed = false;
4780
+ } catch {
4781
+ this.failed = true;
4782
+ } finally {
4783
+ this.saving = false;
4784
+ this.publish();
4785
+ }
4212
4786
  }
4213
4787
  publish() {
4214
4788
  for (const listener of [...this.listeners]) listener();
4215
4789
  }
4216
4790
  };
4791
+ /** Project a stored channel into a draft (secrets never travel in channels). */
4792
+ function toDraft(channel) {
4793
+ return {
4794
+ id: channel.id,
4795
+ preset: channel.preset,
4796
+ name: channel.name,
4797
+ apiUrl: channel.apiUrl,
4798
+ models: channel.models.map((model) => ({ ...model }))
4799
+ };
4800
+ }
4801
+ /** The scope's current channels value (a plain array), for change detection. */
4802
+ function scopeChannelsOf(scope) {
4803
+ const view = scope.getSnapshot().value;
4804
+ return Array.isArray(view?.channels) ? view.channels : [];
4805
+ }
4806
+ //#endregion
4807
+ //#region src/model-catalog.ts
4808
+ const ENTRIES = {
4809
+ "gpt-image": {
4810
+ label: "gpt-image",
4811
+ labelZh: "GPT 图像",
4812
+ known: true,
4813
+ supportsEdit: true,
4814
+ supportsAspectRatio: false,
4815
+ qualityTiers: [
4816
+ "1K",
4817
+ "2K",
4818
+ "4K"
4819
+ ]
4820
+ },
4821
+ "dall-e": {
4822
+ label: "DALL·E",
4823
+ labelZh: "DALL·E",
4824
+ known: true,
4825
+ supportsEdit: true,
4826
+ supportsAspectRatio: false,
4827
+ qualityTiers: ["auto"]
4828
+ },
4829
+ grok: {
4830
+ label: "grok",
4831
+ labelZh: "Grok",
4832
+ known: true,
4833
+ supportsEdit: true,
4834
+ supportsAspectRatio: true,
4835
+ qualityTiers: ["1K", "2K"]
4836
+ },
4837
+ nanobanana: {
4838
+ label: "nanobanana",
4839
+ labelZh: "Nano Banana",
4840
+ known: true,
4841
+ supportsEdit: true,
4842
+ supportsAspectRatio: true,
4843
+ qualityTiers: [
4844
+ "1K",
4845
+ "2K",
4846
+ "4K"
4847
+ ]
4848
+ },
4849
+ seedream: {
4850
+ label: "seedream",
4851
+ labelZh: "Seedream",
4852
+ known: true,
4853
+ supportsEdit: true,
4854
+ supportsAspectRatio: true,
4855
+ qualityTiers: ["1K", "2K"]
4856
+ }
4857
+ };
4858
+ /** Official Gemini image ids served by Nano Banana gateways. */
4859
+ const NANOBANANA_GEMINI_IDS = /* @__PURE__ */ new Set([
4860
+ "gemini-3-pro-image",
4861
+ "gemini-3-pro-image-preview",
4862
+ "gemini-3.1-flash-image",
4863
+ "gemini-3.1-flash-image-preview",
4864
+ "gemini-3.1-flash-lite-image",
4865
+ "gemini-2.5-flash-image"
4866
+ ]);
4867
+ /** Classify one upstream model id into its request-shaping family. */
4868
+ function describeModel(model) {
4869
+ const id = model.trim();
4870
+ if (/^gpt-image/i.test(id)) return {
4871
+ family: "gpt-image",
4872
+ ...ENTRIES["gpt-image"]
4873
+ };
4874
+ if (/^dall-e/i.test(id)) return {
4875
+ family: "dall-e",
4876
+ ...ENTRIES["dall-e"]
4877
+ };
4878
+ if (/^grok-imagine(?:-|$)/.test(id)) return {
4879
+ family: "grok",
4880
+ ...ENTRIES.grok
4881
+ };
4882
+ if (/^nanobanana/i.test(id) || NANOBANANA_GEMINI_IDS.has(id)) return {
4883
+ family: "nanobanana",
4884
+ ...ENTRIES.nanobanana
4885
+ };
4886
+ if (/^(?:doubao-)?seedream/i.test(id)) return {
4887
+ family: "seedream",
4888
+ ...ENTRIES.seedream
4889
+ };
4890
+ return {
4891
+ family: "unknown",
4892
+ label: "unknown",
4893
+ labelZh: "未知协议",
4894
+ known: false,
4895
+ supportsEdit: true,
4896
+ supportsAspectRatio: false,
4897
+ qualityTiers: []
4898
+ };
4899
+ }
4217
4900
  //#endregion
4218
4901
  //#region \0dsh-css:E:\dsh-plugin\src\client\settings-card.module.css.mjs
4219
- const css$1 = ".i1cc5G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.i1cc5G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_card:has(.i1cc5G_body){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.i1cc5G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.i1cc5G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.i1cc5G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.i1cc5G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.i1cc5G_chevron,.i1cc5G_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.i1cc5G_chevronOpen{transform:rotate(180deg)}.i1cc5G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.i1cc5G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.i1cc5G_versionRow{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.i1cc5G_versionLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}.i1cc5G_versionValue{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family-mono,monospace);border-radius:999px;padding:1px 8px;font-size:12px;line-height:1.5}.i1cc5G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.i1cc5G_field+.i1cc5G_field{border-top:1px solid var(--dsw-alias-border-l2)}.i1cc5G_head{align-items:center;gap:8px;display:flex}.i1cc5G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.i1cc5G_badges{align-items:center;gap:8px;display:inline-flex}.i1cc5G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.i1cc5G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.i1cc5G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.i1cc5G_reset:disabled{cursor:default;opacity:.5}.i1cc5G_input,.i1cc5G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.i1cc5G_input:focus-visible,.i1cc5G_select:focus-visible{border-color:var(--dsw-alias-brand-primary)}.i1cc5G_input:disabled,.i1cc5G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.i1cc5G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.i1cc5G_textarea,.i1cc5G_textareaInvalid{resize:vertical;background:var(--dsw-alias-bg-layer-3);min-height:70px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:8px 12px;font-size:13px;line-height:1.5}.i1cc5G_textarea{border:1px solid var(--dsw-alias-border-l2)}.i1cc5G_textareaInvalid{border:1px solid var(--dsw-alias-label-error)}.i1cc5G_textarea:focus-visible{border-color:var(--dsw-alias-brand-primary)}.i1cc5G_textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.i1cc5G_hint,.i1cc5G_invalid{margin:0;font-size:12px;line-height:1.5}.i1cc5G_hint{color:var(--dsw-alias-label-tertiary)}.i1cc5G_invalid{color:var(--dsw-alias-label-error)}.i1cc5G_readOnly,.i1cc5G_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.i1cc5G_sectionDivider{background:var(--dsw-alias-border-l2);height:1px;margin:18px 0 14px}.i1cc5G_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:1.4}.i1cc5G_sectionHint{color:var(--dsw-alias-label-tertiary);margin:-4px 0 2px;font-size:12px;line-height:1.5}.i1cc5G_modelSection{padding:2px 0 14px}.i1cc5G_sectionHeader{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.i1cc5G_sectionHeader .i1cc5G_sectionHint{max-width:430px}.i1cc5G_modelSummary{flex-wrap:wrap;align-items:center;gap:6px;margin-top:12px;display:flex}.i1cc5G_modelChip{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-width:100%;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family-mono,monospace);border-radius:6px;align-items:center;gap:5px;padding:3px 5px 3px 8px;font-size:12px;line-height:1.5;display:inline-flex}.i1cc5G_modelChip>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.i1cc5G_modelChip button{appearance:none;width:18px;height:18px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:4px;padding:0;line-height:18px}.i1cc5G_modelChip button:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.i1cc5G_modelChip button:disabled{cursor:default;opacity:.45}.i1cc5G_addModel{appearance:none;border:1px dashed var(--dsw-alias-border-l2);min-height:28px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 9px;font-size:12px}.i1cc5G_addModel:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.i1cc5G_addModel:disabled{opacity:.45;cursor:default}.i1cc5G_manualModelRow{gap:8px;margin-top:10px;display:flex}.i1cc5G_manualModelRow .i1cc5G_input{flex:1;min-width:0}.i1cc5G_disclosure{appearance:none;border:0;border-top:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;align-items:center;gap:8px;padding:13px 0;font-size:13px;font-weight:500;display:flex}.i1cc5G_disclosure>span:nth-child(2){color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;font-weight:400}.i1cc5G_disclosure>span:last-child{color:var(--dsw-alias-label-tertiary)}.i1cc5G_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.i1cc5G_optionalContent{padding:0 0 8px}.i1cc5G_inlineDisclosure{appearance:none;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;margin-top:12px;padding:0;font-size:12px;display:inline-flex}.i1cc5G_inlineDisclosure:hover{color:var(--dsw-alias-label-primary)}.i1cc5G_modelFetchRow{align-items:center;gap:8px;display:flex}.i1cc5G_modelFetch,.i1cc5G_modelChoices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);min-height:32px;color:var(--dsw-alias-label-secondary);font:inherit;border-radius:7px;font-size:12px}.i1cc5G_modelFetch{cursor:pointer;padding:0 10px}.i1cc5G_modelFetch:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.i1cc5G_modelFetch:disabled{opacity:.5;cursor:default}.i1cc5G_modelChoices{flex:1;min-width:0;padding:0 8px}.i1cc5G_modelCandidateList{flex-wrap:wrap;gap:6px 10px;margin-top:10px;display:flex}.i1cc5G_modelCandidateLabel{width:100%;color:var(--dsw-alias-label-tertiary);font-size:12px}.i1cc5G_modelCandidate{appearance:none;min-width:0;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-3);font-size:12px;line-height:1.5;font:inherit;border:1px solid #0000;border-radius:5px;align-items:center;gap:5px;padding:3px 6px;display:inline-flex}.i1cc5G_modelCandidate input{margin:0}.i1cc5G_modelCandidate[data-selected]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.i1cc5G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.i1cc5G_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.i1cc5G_discard,.i1cc5G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.i1cc5G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.i1cc5G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.i1cc5G_discard:disabled,.i1cc5G_save:disabled{opacity:.4;cursor:default}.i1cc5G_discard:focus-visible,.i1cc5G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.i1cc5G_card,.i1cc5G_chevron,.i1cc5G_chevronOpen{transition:none}}";
4902
+ const css$1 = ".i1cc5G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.i1cc5G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_card:has(.i1cc5G_body){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.i1cc5G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.i1cc5G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.i1cc5G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.i1cc5G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.i1cc5G_chevron,.i1cc5G_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.i1cc5G_chevronOpen{transform:rotate(180deg)}.i1cc5G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.i1cc5G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.i1cc5G_versionRow{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.i1cc5G_versionLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}.i1cc5G_versionValue{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family-mono,monospace);border-radius:999px;padding:1px 8px;font-size:12px;line-height:1.5}.i1cc5G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.i1cc5G_field+.i1cc5G_field{border-top:1px solid var(--dsw-alias-border-l2)}.i1cc5G_head{align-items:center;gap:8px;display:flex}.i1cc5G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.i1cc5G_badges{align-items:center;gap:8px;display:inline-flex}.i1cc5G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.i1cc5G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.i1cc5G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.i1cc5G_reset:disabled{cursor:default;opacity:.5}.i1cc5G_input,.i1cc5G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.i1cc5G_input:focus-visible,.i1cc5G_select:focus-visible{border-color:var(--dsw-alias-brand-primary)}.i1cc5G_input:disabled,.i1cc5G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.i1cc5G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.i1cc5G_textarea,.i1cc5G_textareaInvalid{resize:vertical;background:var(--dsw-alias-bg-layer-3);min-height:70px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:8px 12px;font-size:13px;line-height:1.5}.i1cc5G_textarea{border:1px solid var(--dsw-alias-border-l2)}.i1cc5G_textareaInvalid{border:1px solid var(--dsw-alias-label-error)}.i1cc5G_textarea:focus-visible{border-color:var(--dsw-alias-brand-primary)}.i1cc5G_textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.i1cc5G_hint,.i1cc5G_invalid{margin:0;font-size:12px;line-height:1.5}.i1cc5G_hint{color:var(--dsw-alias-label-tertiary)}.i1cc5G_invalid{color:var(--dsw-alias-label-error)}.i1cc5G_readOnly,.i1cc5G_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.i1cc5G_sectionDivider{background:var(--dsw-alias-border-l2);height:1px;margin:18px 0 14px}.i1cc5G_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:1.4}.i1cc5G_sectionHint{color:var(--dsw-alias-label-tertiary);margin:-4px 0 2px;font-size:12px;line-height:1.5}.i1cc5G_modelSection{padding:2px 0 14px}.i1cc5G_sectionHeader{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.i1cc5G_sectionHeader .i1cc5G_sectionHint{max-width:430px}.i1cc5G_modelSummary{flex-wrap:wrap;align-items:center;gap:6px;margin-top:12px;display:flex}.i1cc5G_modelChip{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-width:100%;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family-mono,monospace);border-radius:6px;align-items:center;gap:5px;padding:3px 5px 3px 8px;font-size:12px;line-height:1.5;display:inline-flex}.i1cc5G_modelChip>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.i1cc5G_modelChip button{appearance:none;width:18px;height:18px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:4px;padding:0;line-height:18px}.i1cc5G_modelChip button:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.i1cc5G_modelChip button:disabled{cursor:default;opacity:.45}.i1cc5G_addModel{appearance:none;border:1px dashed var(--dsw-alias-border-l2);min-height:28px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 9px;font-size:12px}.i1cc5G_addModel:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.i1cc5G_addModel:disabled{opacity:.45;cursor:default}.i1cc5G_manualModelRow{gap:8px;margin-top:10px;display:flex}.i1cc5G_manualModelRow .i1cc5G_input{flex:1;min-width:0}.i1cc5G_disclosure{appearance:none;border:0;border-top:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;align-items:center;gap:8px;padding:13px 0;font-size:13px;font-weight:500;display:flex}.i1cc5G_disclosure>span:nth-child(2){color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;font-weight:400}.i1cc5G_disclosure>span:last-child{color:var(--dsw-alias-label-tertiary)}.i1cc5G_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.i1cc5G_optionalContent{padding:0 0 8px}.i1cc5G_inlineDisclosure{appearance:none;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;margin-top:12px;padding:0;font-size:12px;display:inline-flex}.i1cc5G_inlineDisclosure:hover{color:var(--dsw-alias-label-primary)}.i1cc5G_modelFetchRow{align-items:center;gap:8px;display:flex}.i1cc5G_modelFetch,.i1cc5G_modelChoices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);min-height:32px;color:var(--dsw-alias-label-secondary);font:inherit;border-radius:7px;font-size:12px}.i1cc5G_modelFetch{cursor:pointer;padding:0 10px}.i1cc5G_modelFetch:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.i1cc5G_modelFetch:disabled{opacity:.5;cursor:default}.i1cc5G_modelChoices{flex:1;min-width:0;padding:0 8px}.i1cc5G_modelCandidateList{flex-wrap:wrap;gap:6px 10px;margin-top:10px;display:flex}.i1cc5G_modelCandidateLabel{width:100%;color:var(--dsw-alias-label-tertiary);font-size:12px}.i1cc5G_modelCandidate{appearance:none;min-width:0;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-3);font-size:12px;line-height:1.5;font:inherit;border:1px solid #0000;border-radius:5px;align-items:center;gap:5px;padding:3px 6px;display:inline-flex}.i1cc5G_modelCandidate input{margin:0}.i1cc5G_modelCandidate[data-selected]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.i1cc5G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.i1cc5G_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.i1cc5G_discard,.i1cc5G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.i1cc5G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.i1cc5G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.i1cc5G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.i1cc5G_discard:disabled,.i1cc5G_save:disabled{opacity:.4;cursor:default}.i1cc5G_discard:focus-visible,.i1cc5G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.i1cc5G_card,.i1cc5G_chevron,.i1cc5G_chevronOpen{transition:none}}.i1cc5G_channelSection{padding:6px 0 2px}.i1cc5G_channelEmpty{color:var(--dsw-alias-label-tertiary);margin:10px 0 0;font-size:12px;line-height:1.5}.i1cc5G_channelList{flex-direction:column;gap:6px;margin:10px 0 0;padding:0;list-style:none;display:flex}.i1cc5G_channelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;align-items:center;gap:10px;padding:8px 10px;display:flex}.i1cc5G_channelRow[data-action]{flex-wrap:wrap}.i1cc5G_channelDotReady,.i1cc5G_channelDotWarn{border-radius:50%;flex:none;width:9px;height:9px}.i1cc5G_channelDotReady{background:var(--dsw-color-success,#2fbf71)}.i1cc5G_channelDotWarn{background:var(--dsw-color-danger,#e5484d)}.i1cc5G_channelMain{appearance:none;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex-direction:column;flex:1;gap:3px;padding:0;display:flex}.i1cc5G_channelName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;line-height:1.4;overflow:hidden}.i1cc5G_channelMeta{flex-wrap:wrap;align-items:center;gap:4px 8px;display:flex}.i1cc5G_channelHost{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-family:var(--dsw-font-family-mono,monospace);text-overflow:ellipsis;white-space:nowrap;max-width:180px;overflow:hidden}.i1cc5G_channelBadge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:0 7px;font-size:11px;font-weight:500;line-height:17px}.i1cc5G_channelBadge[data-warn]{color:var(--dsw-alias-label-error)}.i1cc5G_channelBadge[data-default]{background:var(--dsw-alias-bg-module-poped,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-brand-primary)}.i1cc5G_channelAction{appearance:none;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;padding:4px 8px;font-size:12px;line-height:1.5}.i1cc5G_channelAction:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.i1cc5G_channelAction[data-danger]{color:var(--dsw-alias-label-error)}.i1cc5G_channelAction:disabled{opacity:.45;cursor:default}.i1cc5G_channelDanger{appearance:none;border:1px solid var(--dsw-alias-label-error);color:var(--dsw-alias-label-error);font:inherit;cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:3px 10px;font-size:12px}.i1cc5G_channelDanger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-label-error) 12%, transparent)}.i1cc5G_channelDanger:disabled{opacity:.45;cursor:default}.i1cc5G_deleteConfirmText{min-width:0;color:var(--dsw-alias-label-error);flex:1;font-size:12px;line-height:1.5}.i1cc5G_channelAddRow{gap:8px;margin-top:10px;display:flex}.i1cc5G_channelAdd{appearance:none;border:1px dashed var(--dsw-alias-border-l2);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:12px}.i1cc5G_channelAdd:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.i1cc5G_channelAdd:disabled{opacity:.45;cursor:default}.i1cc5G_spacer{flex:1}.i1cc5G_editorBackdrop{z-index:120;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#000) 45%, transparent);justify-content:center;align-items:flex-start;padding:9vh 16px 16px;display:flex;position:fixed;inset:0}.i1cc5G_editorPanel{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;width:min(560px,100%);max-height:82vh;padding:16px;overflow-y:auto;box-shadow:0 18px 50px #00000059}.i1cc5G_editorHeader{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:flex-start;gap:12px;padding-bottom:10px;display:flex}.i1cc5G_editorClose{appearance:none;width:26px;height:26px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;font-size:16px;line-height:26px}.i1cc5G_editorClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.i1cc5G_editorField{flex-direction:column;gap:6px;padding:12px 0 0;display:flex}.i1cc5G_editorDivider{background:var(--dsw-alias-border-l2);height:1px;margin:14px 0 4px}.i1cc5G_editorSectionHeader{justify-content:space-between;align-items:center;gap:12px;padding:10px 0 4px;display:flex}.i1cc5G_editorSectionHeader .i1cc5G_label{flex:1}.i1cc5G_editorTools{flex-direction:column;gap:8px;margin-top:10px;display:flex}.i1cc5G_editorFooter{border-top:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding-top:12px;display:flex}.i1cc5G_detectOk{color:var(--dsw-color-success,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}.i1cc5G_modelRows{flex-direction:column;gap:8px;margin:8px 0 0;padding:0;list-style:none;display:flex}.i1cc5G_modelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;justify-content:space-between;align-items:center;gap:10px;padding:8px 10px;display:flex}.i1cc5G_modelRowInputs{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.i1cc5G_modelRowInputs .i1cc5G_input{flex:1;min-width:0}.i1cc5G_modelArrow{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px}.i1cc5G_modelRowBadges{flex:none;align-items:center;gap:6px;display:flex}.i1cc5G_modelBadge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:0 7px;font-size:11px;line-height:17px}.i1cc5G_modelBadge[data-verified]{color:var(--dsw-color-success,var(--dsw-alias-label-secondary))}.i1cc5G_modelBadge[data-warn]{color:var(--dsw-alias-label-error)}.i1cc5G_modelRowRemove{appearance:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:4px;flex:none;padding:0;line-height:20px}.i1cc5G_modelRowRemove:hover:not(:disabled){color:var(--dsw-alias-label-error);background:var(--dsw-alias-bg-module-platform)}.i1cc5G_modelRowRemove:disabled{opacity:.45;cursor:default}.i1cc5G_modelCandidate>.i1cc5G_modelBadge{flex:none}.i1cc5G_channelControls{position:relative}.i1cc5G_presetInline{z-index:10;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;width:min(420px,100vw - 48px);max-height:min(440px,60vh);margin:0;padding:12px;position:absolute;bottom:calc(100% + 8px);left:0;overflow-y:auto;box-shadow:0 12px 30px #0000004d}.i1cc5G_presetInlineHeader{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.i1cc5G_presetList{flex-direction:column;gap:8px;margin-top:12px;display:flex}.i1cc5G_presetRow{appearance:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;border-radius:10px;flex-direction:column;align-items:flex-start;gap:3px;padding:10px 12px;display:flex}.i1cc5G_presetRow:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary)}.i1cc5G_presetRow[data-custom]{border-style:dashed}.i1cc5G_presetRow:disabled{opacity:.5;cursor:default}.i1cc5G_presetName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:1.4}.i1cc5G_presetMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-family:var(--dsw-font-family-mono,monospace)}.i1cc5G_presetHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}";
4220
4903
  const tagId$1 = "@dickpy/dsh-imagegen/settings-card.module.css";
4221
4904
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
4222
4905
  const tag = document.createElement("style");
@@ -4226,69 +4909,118 @@ window.__ModuleLoader__.load({
4226
4909
  document.head.appendChild(tag);
4227
4910
  }
4228
4911
  var settings_card_module_css_default = {
4229
- "sectionTitle": "i1cc5G_sectionTitle",
4230
- "disclosure": "i1cc5G_disclosure",
4231
- "save": "i1cc5G_save",
4232
- "badges": "i1cc5G_badges",
4233
- "sectionHeader": "i1cc5G_sectionHeader",
4912
+ "optionalContent": "i1cc5G_optionalContent",
4913
+ "editorSectionHeader": "i1cc5G_editorSectionHeader",
4914
+ "channelDotWarn": "i1cc5G_channelDotWarn",
4234
4915
  "modelSummary": "i1cc5G_modelSummary",
4235
- "input": "i1cc5G_input",
4916
+ "editorField": "i1cc5G_editorField",
4236
4917
  "modelCandidateLabel": "i1cc5G_modelCandidateLabel",
4237
- "select": "i1cc5G_select",
4238
- "textareaInvalid": "i1cc5G_textareaInvalid",
4239
- "modelChip": "i1cc5G_modelChip",
4918
+ "reset": "i1cc5G_reset",
4919
+ "body": "i1cc5G_body",
4920
+ "field": "i1cc5G_field",
4921
+ "badges": "i1cc5G_badges",
4922
+ "addModel": "i1cc5G_addModel",
4923
+ "editorClose": "i1cc5G_editorClose",
4924
+ "presetInlineHeader": "i1cc5G_presetInlineHeader",
4925
+ "versionLabel": "i1cc5G_versionLabel",
4240
4926
  "header": "i1cc5G_header",
4241
- "hint": "i1cc5G_hint",
4242
- "discard": "i1cc5G_discard",
4243
- "headText": "i1cc5G_headText",
4244
- "modelChoices": "i1cc5G_modelChoices",
4927
+ "head": "i1cc5G_head",
4928
+ "presetInline": "i1cc5G_presetInline",
4929
+ "detectOk": "i1cc5G_detectOk",
4245
4930
  "modelCandidate": "i1cc5G_modelCandidate",
4246
- "description": "i1cc5G_description",
4247
- "textarea": "i1cc5G_textarea",
4248
- "readOnly": "i1cc5G_readOnly",
4249
- "inlineDisclosure": "i1cc5G_inlineDisclosure",
4250
- "reset": "i1cc5G_reset",
4251
- "sectionHint": "i1cc5G_sectionHint",
4252
- "manualModelRow": "i1cc5G_manualModelRow",
4253
- "label": "i1cc5G_label",
4254
- "optionalContent": "i1cc5G_optionalContent",
4931
+ "modelBadge": "i1cc5G_modelBadge",
4932
+ "modelRowInputs": "i1cc5G_modelRowInputs",
4933
+ "channelName": "i1cc5G_channelName",
4934
+ "channelSection": "i1cc5G_channelSection",
4935
+ "input": "i1cc5G_input",
4936
+ "channelList": "i1cc5G_channelList",
4937
+ "channelAddRow": "i1cc5G_channelAddRow",
4938
+ "editorPanel": "i1cc5G_editorPanel",
4255
4939
  "versionValue": "i1cc5G_versionValue",
4256
- "notExposed": "i1cc5G_notExposed",
4257
- "versionLabel": "i1cc5G_versionLabel",
4258
- "field": "i1cc5G_field",
4259
- "body": "i1cc5G_body",
4940
+ "sectionHint": "i1cc5G_sectionHint",
4941
+ "modelChip": "i1cc5G_modelChip",
4942
+ "footer": "i1cc5G_footer",
4943
+ "save": "i1cc5G_save",
4260
4944
  "badge": "i1cc5G_badge",
4261
- "modelSection": "i1cc5G_modelSection",
4262
- "modelFetch": "i1cc5G_modelFetch",
4263
- "name": "i1cc5G_name",
4945
+ "presetName": "i1cc5G_presetName",
4946
+ "modelChoices": "i1cc5G_modelChoices",
4947
+ "deleteConfirmText": "i1cc5G_deleteConfirmText",
4264
4948
  "invalid": "i1cc5G_invalid",
4265
- "inputInvalid": "i1cc5G_inputInvalid",
4949
+ "inlineDisclosure": "i1cc5G_inlineDisclosure",
4950
+ "modelFetch": "i1cc5G_modelFetch",
4266
4951
  "pending": "i1cc5G_pending",
4267
- "addModel": "i1cc5G_addModel",
4268
- "modelFetchRow": "i1cc5G_modelFetchRow",
4269
- "footer": "i1cc5G_footer",
4270
- "card": "i1cc5G_card",
4952
+ "textarea": "i1cc5G_textarea",
4953
+ "select": "i1cc5G_select",
4954
+ "channelRow": "i1cc5G_channelRow",
4955
+ "label": "i1cc5G_label",
4956
+ "failed": "i1cc5G_failed",
4957
+ "channelHost": "i1cc5G_channelHost",
4958
+ "channelDanger": "i1cc5G_channelDanger",
4959
+ "sectionDivider": "i1cc5G_sectionDivider",
4960
+ "editorBackdrop": "i1cc5G_editorBackdrop",
4271
4961
  "versionRow": "i1cc5G_versionRow",
4962
+ "hint": "i1cc5G_hint",
4963
+ "modelFetchRow": "i1cc5G_modelFetchRow",
4964
+ "editorTools": "i1cc5G_editorTools",
4965
+ "editorFooter": "i1cc5G_editorFooter",
4966
+ "discard": "i1cc5G_discard",
4967
+ "channelEmpty": "i1cc5G_channelEmpty",
4968
+ "modelRows": "i1cc5G_modelRows",
4969
+ "modelRow": "i1cc5G_modelRow",
4970
+ "modelArrow": "i1cc5G_modelArrow",
4971
+ "modelRowBadges": "i1cc5G_modelRowBadges",
4972
+ "presetList": "i1cc5G_presetList",
4973
+ "presetHint": "i1cc5G_presetHint",
4974
+ "manualModelRow": "i1cc5G_manualModelRow",
4272
4975
  "chevron": "i1cc5G_chevron",
4273
- "chevronOpen": "i1cc5G_chevronOpen",
4976
+ "textareaInvalid": "i1cc5G_textareaInvalid",
4977
+ "editorHeader": "i1cc5G_editorHeader",
4978
+ "readOnly": "i1cc5G_readOnly",
4979
+ "sectionHeader": "i1cc5G_sectionHeader",
4980
+ "channelMeta": "i1cc5G_channelMeta",
4981
+ "presetMeta": "i1cc5G_presetMeta",
4982
+ "editorDivider": "i1cc5G_editorDivider",
4983
+ "card": "i1cc5G_card",
4984
+ "modelSection": "i1cc5G_modelSection",
4985
+ "presetRow": "i1cc5G_presetRow",
4986
+ "spacer": "i1cc5G_spacer",
4987
+ "channelAction": "i1cc5G_channelAction",
4988
+ "inputInvalid": "i1cc5G_inputInvalid",
4989
+ "channelControls": "i1cc5G_channelControls",
4990
+ "sectionTitle": "i1cc5G_sectionTitle",
4991
+ "channelAdd": "i1cc5G_channelAdd",
4992
+ "name": "i1cc5G_name",
4993
+ "notExposed": "i1cc5G_notExposed",
4274
4994
  "modelCandidateList": "i1cc5G_modelCandidateList",
4275
- "failed": "i1cc5G_failed",
4276
- "sectionDivider": "i1cc5G_sectionDivider",
4277
- "head": "i1cc5G_head"
4995
+ "channelBadge": "i1cc5G_channelBadge",
4996
+ "description": "i1cc5G_description",
4997
+ "channelMain": "i1cc5G_channelMain",
4998
+ "chevronOpen": "i1cc5G_chevronOpen",
4999
+ "disclosure": "i1cc5G_disclosure",
5000
+ "channelDotReady": "i1cc5G_channelDotReady",
5001
+ "headText": "i1cc5G_headText",
5002
+ "modelRowRemove": "i1cc5G_modelRowRemove"
4278
5003
  };
4279
5004
  //#endregion
4280
5005
  //#region src/client/SettingsCard.tsx
4281
5006
  /**
4282
- * The dsh-imagegen settings card: api_url, api_key (secret, display-only
4283
- * "set" state), and the plugin switches. Registers into the official
4284
- * `settings.plugin.item` slot (the Settings Plugins Configurable tab),
4285
- * independent of the dsh-web-ui family group, bound to the plugin's own
4286
- * bridge settings scope.
5007
+ * The dsh-imagegen settings card: channel management (list rows with status
5008
+ * dots, an editor dialog with the model-catalog alias upstream mapping, and
5009
+ * built-in provider presets), plus the prompt-enhancement model and the plugin
5010
+ * switches. Registers into the official `settings.plugin.item` slot (the
5011
+ * Settings Plugins → Configurable tab), independent of the dsh-web-ui family
5012
+ * group, bound to the plugin's own bridge settings scope.
5013
+ *
5014
+ * The interaction mirrors the host's model-provider page: one row per channel
5015
+ * (status dot + edit/delete), two add buttons (built-in provider / custom),
5016
+ * and an editor holding API key, display name, API URL, and the model catalog
5017
+ * with detection.
4287
5018
  */
4288
- /** Bridges the imagegen scope onto the card's staged form. */
5019
+ /** Bridges the imagegen scope onto the card's staged forms. */
4289
5020
  var ImageGenSettingsCardController = class {
4290
5021
  scope;
4291
5022
  form;
5023
+ channelsForm;
4292
5024
  /** @param scope - the bound bridge scope for the dsh-imagegen namespace. */
4293
5025
  constructor(scope) {
4294
5026
  this.scope = scope;
@@ -4296,23 +5028,21 @@ window.__ModuleLoader__.load({
4296
5028
  booleanField("enabled"),
4297
5029
  booleanField("announceToAgent"),
4298
5030
  booleanField("allowAgentImageGeneration"),
4299
- textField("apiUrl"),
4300
- secretField("apiKey"),
4301
- stringListField("imageModels"),
4302
5031
  textField("promptApiUrl"),
4303
5032
  secretField("promptApiKey"),
4304
5033
  textField("promptModel")
4305
- ], { secretSettled: () => this.scope.getKeySetSnapshot() });
5034
+ ], { secretSettled: () => this.scope.getSecretSetSnapshot("promptApiKey") });
5035
+ this.channelsForm = new ChannelsForm(scope);
4306
5036
  }
4307
5037
  projection() {
5038
+ const shell = this.form.shell();
4308
5039
  return {
4309
- ...this.form.shell(),
5040
+ ...shell,
5041
+ dirty: shell.dirty || this.channelsForm.snapshot().dirty,
5042
+ channels: this.channelsForm.snapshot(),
4310
5043
  enabled: this.form.field("enabled"),
4311
5044
  announceToAgent: this.form.field("announceToAgent"),
4312
5045
  allowAgentImageGeneration: this.form.field("allowAgentImageGeneration"),
4313
- apiUrl: this.form.field("apiUrl"),
4314
- apiKey: this.form.field("apiKey"),
4315
- imageModels: this.form.field("imageModels"),
4316
5046
  promptApiUrl: this.form.field("promptApiUrl"),
4317
5047
  promptApiKey: this.form.field("promptApiKey"),
4318
5048
  promptModel: this.form.field("promptModel")
@@ -4320,19 +5050,16 @@ window.__ModuleLoader__.load({
4320
5050
  }
4321
5051
  /**
4322
5052
  * Build the face the card's slot registration injects.
4323
- * @returns the card's snapshot, the key-set flag, and the form actions.
5053
+ * @returns the card's snapshot and the form/channel actions.
4324
5054
  */
4325
5055
  inject() {
4326
5056
  const cardStore = this.form.bind(() => this.projection());
4327
- const keySetStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.scope.getKeySetSnapshot());
4328
- this.scope.subscribeKeySet(() => {
4329
- keySetStore.set(this.scope.getKeySetSnapshot());
5057
+ this.channelsForm.subscribe(() => {
5058
+ cardStore.set(this.projection());
4330
5059
  });
4331
5060
  return {
4332
- hooks: {
4333
- imageGenSettingsCard: cardStore,
4334
- imageGenKeySet: keySetStore
4335
- },
5061
+ hooks: { imageGenSettingsCard: cardStore },
5062
+ channels: this.channelsForm.actions(),
4336
5063
  ...this.form.actions()
4337
5064
  };
4338
5065
  }
@@ -4345,24 +5072,35 @@ window.__ModuleLoader__.load({
4345
5072
  function ImageGenSettingsCard(props) {
4346
5073
  const { t } = props;
4347
5074
  const state = props.useImageGenSettingsCard((snapshot) => snapshot);
4348
- const keySet = props.useImageGenKeySet((snapshot) => snapshot);
4349
5075
  const [open, setOpen] = (0, react.useState)(false);
4350
5076
  const [promptModels, setPromptModels] = (0, react.useState)([]);
4351
5077
  const [loadingPromptModels, setLoadingPromptModels] = (0, react.useState)(false);
4352
5078
  const [promptModelsError, setPromptModelsError] = (0, react.useState)(null);
4353
- const [imageModelCandidates, setImageModelCandidates] = (0, react.useState)([]);
4354
- const [loadingImageModels, setLoadingImageModels] = (0, react.useState)(false);
4355
- const [imageModelsError, setImageModelsError] = (0, react.useState)(null);
4356
- const [manualModelsOpen, setManualModelsOpen] = (0, react.useState)(false);
4357
- const [manualModel, setManualModel] = (0, react.useState)("");
4358
- const [enhancementOpen, setEnhancementOpen] = (0, react.useState)(false);
4359
5079
  const [manualPromptModelOpen, setManualPromptModelOpen] = (0, react.useState)(false);
4360
5080
  const [manualPromptModel, setManualPromptModel] = (0, react.useState)("");
5081
+ const [enhancementOpen, setEnhancementOpen] = (0, react.useState)(false);
4361
5082
  const [promptApiOpen, setPromptApiOpen] = (0, react.useState)(false);
4362
5083
  const [moreOpen, setMoreOpen] = (0, react.useState)(false);
5084
+ const [editingId, setEditingId] = (0, react.useState)(null);
5085
+ const [presetPickerOpen, setPresetPickerOpen] = (0, react.useState)(false);
5086
+ const [presets, setPresets] = (0, react.useState)([]);
5087
+ const [presetError, setPresetError] = (0, react.useState)(null);
5088
+ const [usage, setUsage] = (0, react.useState)(null);
5089
+ const [confirmDeleteId, setConfirmDeleteId] = (0, react.useState)(null);
5090
+ (0, react.useEffect)(() => {
5091
+ if (!state.exposed) return;
5092
+ let alive = true;
5093
+ fetch(USAGE_API, { method: "POST" }).then(async (response) => {
5094
+ const body = await response.json();
5095
+ if (alive && body.ok === true && body.usage !== void 0) setUsage(body.usage);
5096
+ }).catch(() => {});
5097
+ return () => {
5098
+ alive = false;
5099
+ };
5100
+ }, [state.exposed]);
4363
5101
  if (!state.available) return null;
4364
5102
  const title = t("settings.title");
4365
- const blocked = !state.dirty || state.invalid || state.saving;
5103
+ const blocked = !state.dirty || state.invalid || state.saving || state.channels.saving;
4366
5104
  const disabled = !state.writable;
4367
5105
  const fieldProps = {
4368
5106
  overriddenLabel: t("settings.overridden"),
@@ -4402,454 +5140,1053 @@ window.__ModuleLoader__.load({
4402
5140
  })
4403
5141
  }) : null]
4404
5142
  });
5143
+ const channels = state.channels.channels;
5144
+ const editing = editingId === null ? void 0 : channels.find((channel) => channel.id === editingId);
4405
5145
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
4406
5146
  className: settings_card_module_css_default.card,
4407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4408
- type: "button",
4409
- className: settings_card_module_css_default.header,
4410
- "aria-expanded": open,
4411
- "aria-label": `${t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
4412
- onClick: () => {
4413
- setOpen(!open);
4414
- },
4415
- children: [
4416
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4417
- className: settings_card_module_css_default.headText,
4418
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4419
- className: settings_card_module_css_default.name,
4420
- children: title
4421
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4422
- className: settings_card_module_css_default.description,
4423
- children: t("settings.description")
4424
- })]
4425
- }),
4426
- state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4427
- className: settings_card_module_css_default.pending,
4428
- children: t("settings.unsaved")
4429
- }) : null,
4430
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4431
- className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron,
4432
- children: "▾"
4433
- })
4434
- ]
4435
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4436
- className: settings_card_module_css_default.body,
4437
- children: [
4438
- !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4439
- className: settings_card_module_css_default.readOnly,
4440
- role: "status",
4441
- children: t("settings.readOnly")
4442
- }) : null,
4443
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4444
- id: "dsh-imagegen-settings-apiurl",
4445
- label: t("settings.apiUrl"),
4446
- hint: t("settings.apiUrlHint"),
4447
- placeholder: "https://api.openai.com/v1",
4448
- ...fieldProps,
4449
- ...state.apiUrl,
4450
- onEdit: (text) => {
4451
- props.edit("apiUrl", text);
4452
- },
4453
- onReset: () => {
4454
- props.resetField("apiUrl");
4455
- }
4456
- }),
4457
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4458
- id: "dsh-imagegen-settings-apikey",
4459
- label: t("settings.apiKey"),
4460
- hint: keySet ? t("settings.apiKeySet") : t("settings.apiKeyHint"),
4461
- placeholder: "sk-…",
4462
- secret: true,
4463
- ...fieldProps,
4464
- ...state.apiKey,
4465
- overridden: false,
4466
- onEdit: (text) => {
4467
- props.edit("apiKey", text);
4468
- },
4469
- onReset: () => {
4470
- props.resetField("apiKey");
4471
- },
4472
- clearLabel: t("settings.apiKeyClear"),
4473
- onClear: () => {
4474
- props.resetField("apiKey");
4475
- },
4476
- canClear: keySet
4477
- }),
4478
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: settings_card_module_css_default.sectionDivider }),
4479
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4480
- className: settings_card_module_css_default.modelSection,
4481
- "aria-label": t("settings.imageModelsTitle"),
4482
- children: [
4483
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4484
- className: settings_card_module_css_default.sectionHeader,
4485
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4486
- className: settings_card_module_css_default.sectionTitle,
4487
- children: t("settings.imageModelsTitle")
4488
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5147
+ children: [
5148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5149
+ type: "button",
5150
+ className: settings_card_module_css_default.header,
5151
+ "aria-expanded": open,
5152
+ "aria-label": `${t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
5153
+ onClick: () => {
5154
+ setOpen(!open);
5155
+ },
5156
+ children: [
5157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5158
+ className: settings_card_module_css_default.headText,
5159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5160
+ className: settings_card_module_css_default.name,
5161
+ children: title
5162
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5163
+ className: settings_card_module_css_default.description,
5164
+ children: t("settings.description")
5165
+ })]
5166
+ }),
5167
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5168
+ className: settings_card_module_css_default.pending,
5169
+ children: t("settings.unsaved")
5170
+ }) : null,
5171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5172
+ className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron,
5173
+ children: "▾"
5174
+ })
5175
+ ]
5176
+ }),
5177
+ open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5178
+ className: settings_card_module_css_default.body,
5179
+ children: [
5180
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5181
+ className: settings_card_module_css_default.readOnly,
5182
+ role: "status",
5183
+ children: t("settings.readOnly")
5184
+ }) : null,
5185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5186
+ className: settings_card_module_css_default.channelSection,
5187
+ "aria-label": t("channels.title"),
5188
+ children: [
5189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5190
+ className: settings_card_module_css_default.sectionHeader,
5191
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5192
+ className: settings_card_module_css_default.sectionTitle,
5193
+ children: t("channels.title")
5194
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5195
+ className: settings_card_module_css_default.sectionHint,
5196
+ children: t("channels.hint")
5197
+ })] })
5198
+ }),
5199
+ channels.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5200
+ className: settings_card_module_css_default.channelEmpty,
5201
+ children: t("channels.empty")
5202
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
5203
+ className: settings_card_module_css_default.channelList,
5204
+ children: channels.map((channel) => {
5205
+ const keyHeld = state.channels.keySet[channel.id] === true;
5206
+ const ready = keyHeld && channel.models.length > 0;
5207
+ const isDefault = channel.id === state.channels.defaultChannelId;
5208
+ if (confirmDeleteId === channel.id) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
5209
+ className: settings_card_module_css_default.channelRow,
5210
+ "data-action": true,
5211
+ children: [
5212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5213
+ className: settings_card_module_css_default.deleteConfirmText,
5214
+ children: t("channels.deleteConfirmTitle", { name: channel.name || t("channels.untitled") })
5215
+ }),
5216
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5217
+ type: "button",
5218
+ className: settings_card_module_css_default.channelDanger,
5219
+ disabled,
5220
+ onClick: () => {
5221
+ props.channels.setChannels(channels.filter((candidate) => candidate.id !== channel.id));
5222
+ if (isDefault && channels.length > 1) {
5223
+ const next = channels.find((candidate) => candidate.id !== channel.id);
5224
+ if (next !== void 0) props.channels.setDefaultChannel(next.id);
5225
+ }
5226
+ setConfirmDeleteId(null);
5227
+ if (editingId === channel.id) setEditingId(null);
5228
+ },
5229
+ children: t("channels.confirm")
5230
+ }),
5231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5232
+ type: "button",
5233
+ className: settings_card_module_css_default.channelAction,
5234
+ disabled,
5235
+ onClick: () => {
5236
+ setConfirmDeleteId(null);
5237
+ },
5238
+ children: t("channels.cancel")
5239
+ })
5240
+ ]
5241
+ }, channel.id);
5242
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
5243
+ className: settings_card_module_css_default.channelRow,
5244
+ children: [
5245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5246
+ className: ready ? settings_card_module_css_default.channelDotReady : settings_card_module_css_default.channelDotWarn,
5247
+ "aria-hidden": "true",
5248
+ title: t(ready ? "channels.statusReady" : "channels.statusIncomplete")
5249
+ }),
5250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5251
+ type: "button",
5252
+ className: settings_card_module_css_default.channelMain,
5253
+ disabled,
5254
+ onClick: () => {
5255
+ setEditingId(channel.id);
5256
+ },
5257
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5258
+ className: settings_card_module_css_default.channelName,
5259
+ children: isDefault ? `★ ${channel.name || t("channels.untitled")}` : channel.name || t("channels.untitled")
5260
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5261
+ className: settings_card_module_css_default.channelMeta,
5262
+ children: [
5263
+ channel.apiUrl !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5264
+ className: settings_card_module_css_default.channelHost,
5265
+ children: hostOf(channel.apiUrl)
5266
+ }) : null,
5267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5268
+ className: settings_card_module_css_default.channelBadge,
5269
+ "data-warn": keyHeld ? void 0 : "",
5270
+ children: keyHeld ? t("channels.keySet") : t("channels.keyMissing")
5271
+ }),
5272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5273
+ className: settings_card_module_css_default.channelBadge,
5274
+ "data-warn": channel.models.length > 0 ? void 0 : "",
5275
+ children: channel.models.length > 0 ? t("channels.modelCount", { n: channel.models.length }) : t("channels.noModels")
5276
+ }),
5277
+ isDefault ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5278
+ className: settings_card_module_css_default.channelBadge,
5279
+ "data-default": true,
5280
+ children: t("channels.defaultLabel")
5281
+ }) : null
5282
+ ]
5283
+ })]
5284
+ }),
5285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5286
+ type: "button",
5287
+ className: settings_card_module_css_default.channelAction,
5288
+ onClick: () => {
5289
+ setEditingId(channel.id);
5290
+ },
5291
+ children: t("channels.edit")
5292
+ }),
5293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5294
+ type: "button",
5295
+ className: settings_card_module_css_default.channelAction,
5296
+ "data-danger": true,
5297
+ onClick: () => {
5298
+ setConfirmDeleteId(channel.id);
5299
+ },
5300
+ children: t("channels.delete")
5301
+ })
5302
+ ]
5303
+ }, channel.id);
5304
+ })
5305
+ }),
5306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5307
+ className: settings_card_module_css_default.channelControls,
5308
+ children: [open && presetPickerOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PresetPicker, {
5309
+ t,
5310
+ presets,
5311
+ error: presetError,
5312
+ disabled: state.writable === false,
5313
+ onLoad: () => {
5314
+ setPresetError(null);
5315
+ fetch(PRESETS_API, { method: "POST" }).then(async (response) => {
5316
+ const body = await response.json();
5317
+ if (!response.ok || body.ok !== true || body.presets === void 0) throw new Error(body.message ?? `HTTP ${response.status}`);
5318
+ setPresets(body.presets);
5319
+ }).catch((error) => {
5320
+ setPresetError(error instanceof Error ? error.message : String(error));
5321
+ });
5322
+ },
5323
+ onPick: (preset) => {
5324
+ const draft = newChannelDraft(preset);
5325
+ props.channels.setChannels([...channels, draft]);
5326
+ setPresetPickerOpen(false);
5327
+ setEditingId(draft.id);
5328
+ },
5329
+ onCustom: () => {
5330
+ const draft = newChannelDraft(void 0);
5331
+ props.channels.setChannels([...channels, draft]);
5332
+ setPresetPickerOpen(false);
5333
+ setEditingId(draft.id);
5334
+ },
5335
+ onClose: () => {
5336
+ setPresetPickerOpen(false);
5337
+ }
5338
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5339
+ className: settings_card_module_css_default.channelAddRow,
5340
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5341
+ type: "button",
5342
+ className: settings_card_module_css_default.channelAdd,
5343
+ disabled,
5344
+ onClick: () => {
5345
+ setPresetError(null);
5346
+ setPresetPickerOpen(true);
5347
+ },
5348
+ children: ["+ ", t("channels.addProvider")]
5349
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5350
+ type: "button",
5351
+ className: settings_card_module_css_default.channelAdd,
5352
+ disabled,
5353
+ onClick: () => {
5354
+ addCustomChannel(channels, props.channels, setEditingId);
5355
+ },
5356
+ children: ["+ ", t("channels.addCustom")]
5357
+ })]
5358
+ })]
5359
+ })
5360
+ ]
5361
+ }),
5362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5363
+ type: "button",
5364
+ className: settings_card_module_css_default.disclosure,
5365
+ "aria-expanded": enhancementOpen,
5366
+ onClick: () => {
5367
+ setEnhancementOpen((open) => !open);
5368
+ },
5369
+ children: [
5370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptEnhanceTitle") }),
5371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.optional") }),
5372
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5373
+ "aria-hidden": "true",
5374
+ children: enhancementOpen ? "⌃" : "⌄"
5375
+ })
5376
+ ]
5377
+ }),
5378
+ enhancementOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5379
+ className: settings_card_module_css_default.optionalContent,
5380
+ "aria-label": t("settings.promptEnhanceTitle"),
5381
+ children: [
5382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4489
5383
  className: settings_card_module_css_default.sectionHint,
4490
- children: t("settings.imageModelsHint")
4491
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4492
- type: "button",
4493
- className: settings_card_module_css_default.modelFetch,
4494
- disabled: disabled || loadingImageModels,
4495
- onClick: () => {
4496
- setLoadingImageModels(true);
4497
- setImageModelsError(null);
4498
- fetch(IMAGE_MODEL_API.models, { method: "POST" }).then(async (response) => {
4499
- const body = await response.json();
4500
- if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
4501
- setImageModelCandidates(body.models ?? []);
4502
- }).catch((error) => {
4503
- setImageModelsError(error instanceof Error ? error.message : String(error));
4504
- }).finally(() => {
4505
- setLoadingImageModels(false);
4506
- });
4507
- },
4508
- children: loadingImageModels ? t("settings.imageModelsLoading") : t("settings.imageModelsFetch")
4509
- })]
4510
- }),
4511
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4512
- className: settings_card_module_css_default.modelSummary,
4513
- children: [splitModels(state.imageModels.text).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4514
- className: settings_card_module_css_default.modelChip,
4515
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5384
+ children: t("settings.promptEnhanceHint")
5385
+ }),
5386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5387
+ className: settings_card_module_css_default.sectionHeader,
5388
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5389
+ className: settings_card_module_css_default.sectionTitle,
5390
+ children: t("settings.promptModel")
5391
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5392
+ className: settings_card_module_css_default.sectionHint,
5393
+ children: t("settings.promptModelDetectionHint")
5394
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4516
5395
  type: "button",
4517
- disabled,
4518
- "aria-label": `${t("settings.removeModel")}: ${model}`,
5396
+ className: settings_card_module_css_default.modelFetch,
5397
+ disabled: disabled || loadingPromptModels,
4519
5398
  onClick: () => {
4520
- props.edit("imageModels", splitModels(state.imageModels.text).filter((value) => value !== model).join("\n"));
5399
+ setLoadingPromptModels(true);
5400
+ setPromptModelsError(null);
5401
+ fetch(PROMPT_ENHANCE_API.models, { method: "POST" }).then(async (response) => {
5402
+ const body = await response.json();
5403
+ if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
5404
+ setPromptModels(body.models ?? []);
5405
+ }).catch((error) => {
5406
+ setPromptModelsError(error instanceof Error ? error.message : String(error));
5407
+ }).finally(() => {
5408
+ setLoadingPromptModels(false);
5409
+ });
4521
5410
  },
4522
- children: "×"
5411
+ children: loadingPromptModels ? t("settings.promptModelsLoading") : t("settings.promptModelsFetch")
4523
5412
  })]
4524
- }, model)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4525
- type: "button",
4526
- className: settings_card_module_css_default.addModel,
4527
- disabled,
4528
- onClick: () => {
4529
- setManualModelsOpen((open) => !open);
4530
- },
4531
- children: manualModelsOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4532
- })]
4533
- }),
4534
- manualModelsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4535
- className: settings_card_module_css_default.manualModelRow,
4536
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4537
- className: settings_card_module_css_default.input,
4538
- value: manualModel,
4539
- placeholder: t("settings.addModelPlaceholder"),
4540
- disabled,
4541
- onChange: (event) => {
4542
- setManualModel(event.target.value);
4543
- },
4544
- onKeyDown: (event) => {
4545
- if (event.key !== "Enter") return;
4546
- event.preventDefault();
4547
- const next = manualModel.trim();
4548
- if (next === "") return;
4549
- props.edit("imageModels", [...splitModels(state.imageModels.text), next].join("\n"));
4550
- setManualModel("");
4551
- }
4552
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4553
- type: "button",
4554
- className: settings_card_module_css_default.addModel,
4555
- disabled: disabled || manualModel.trim() === "",
4556
- onClick: () => {
4557
- props.edit("imageModels", [...splitModels(state.imageModels.text), manualModel.trim()].join("\n"));
4558
- setManualModel("");
4559
- },
4560
- children: t("settings.addModelConfirm")
4561
- })]
4562
- }) : null,
4563
- imageModelCandidates.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4564
- className: settings_card_module_css_default.modelCandidateList,
4565
- role: "group",
4566
- "aria-label": t("settings.imageModelsCandidates"),
4567
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4568
- className: settings_card_module_css_default.modelCandidateLabel,
4569
- children: t("settings.imageModelsCandidates")
4570
- }), imageModelCandidates.map((candidate) => {
4571
- const selected = splitModels(state.imageModels.text).includes(candidate);
4572
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4573
- className: settings_card_module_css_default.modelCandidate,
4574
- "data-selected": selected ? "" : void 0,
4575
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4576
- type: "checkbox",
4577
- checked: selected,
5413
+ }),
5414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5415
+ className: settings_card_module_css_default.modelSummary,
5416
+ children: [state.promptModel.text.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5417
+ className: settings_card_module_css_default.modelChip,
5418
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: state.promptModel.text }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5419
+ type: "button",
4578
5420
  disabled,
4579
- onChange: () => {
4580
- const selectedModels = splitModels(state.imageModels.text);
4581
- const next = selected ? selectedModels.filter((model) => model !== candidate) : [...selectedModels, candidate];
4582
- props.edit("imageModels", next.join("\n"));
4583
- }
4584
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: candidate })]
4585
- }, candidate);
4586
- })]
4587
- }) : null,
4588
- imageModelsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4589
- className: settings_card_module_css_default.failed,
4590
- role: "status",
4591
- children: imageModelsError
4592
- }) : null
4593
- ]
4594
- }),
4595
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4596
- type: "button",
4597
- className: settings_card_module_css_default.disclosure,
4598
- "aria-expanded": enhancementOpen,
4599
- onClick: () => {
4600
- setEnhancementOpen((open) => !open);
4601
- },
4602
- children: [
4603
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptEnhanceTitle") }),
4604
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.optional") }),
4605
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4606
- "aria-hidden": "true",
4607
- children: enhancementOpen ? "⌃" : "⌄"
4608
- })
4609
- ]
4610
- }),
4611
- enhancementOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4612
- className: settings_card_module_css_default.optionalContent,
4613
- "aria-label": t("settings.promptEnhanceTitle"),
4614
- children: [
4615
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4616
- className: settings_card_module_css_default.sectionHint,
4617
- children: t("settings.promptEnhanceHint")
4618
- }),
4619
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4620
- className: settings_card_module_css_default.sectionHeader,
4621
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4622
- className: settings_card_module_css_default.sectionTitle,
4623
- children: t("settings.promptModel")
4624
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4625
- className: settings_card_module_css_default.sectionHint,
4626
- children: t("settings.promptModelDetectionHint")
4627
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4628
- type: "button",
4629
- className: settings_card_module_css_default.modelFetch,
4630
- disabled: disabled || loadingPromptModels,
4631
- onClick: () => {
4632
- setLoadingPromptModels(true);
4633
- setPromptModelsError(null);
4634
- fetch(PROMPT_ENHANCE_API.models, { method: "POST" }).then(async (response) => {
4635
- const body = await response.json();
4636
- if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
4637
- setPromptModels(body.models ?? []);
4638
- }).catch((error) => {
4639
- setPromptModelsError(error instanceof Error ? error.message : String(error));
4640
- }).finally(() => {
4641
- setLoadingPromptModels(false);
4642
- });
4643
- },
4644
- children: loadingPromptModels ? t("settings.promptModelsLoading") : t("settings.promptModelsFetch")
4645
- })]
4646
- }),
4647
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4648
- className: settings_card_module_css_default.modelSummary,
4649
- children: [state.promptModel.text.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4650
- className: settings_card_module_css_default.modelChip,
4651
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: state.promptModel.text }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5421
+ "aria-label": `${t("settings.removeModel")}: ${state.promptModel.text}`,
5422
+ onClick: () => {
5423
+ props.edit("promptModel", "");
5424
+ },
5425
+ children: "×"
5426
+ })]
5427
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4652
5428
  type: "button",
5429
+ className: settings_card_module_css_default.addModel,
4653
5430
  disabled,
4654
- "aria-label": `${t("settings.removeModel")}: ${state.promptModel.text}`,
4655
5431
  onClick: () => {
4656
- props.edit("promptModel", "");
5432
+ setManualPromptModelOpen((open) => !open);
5433
+ setEnhancementOpen(true);
4657
5434
  },
4658
- children: "×"
5435
+ children: manualPromptModelOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4659
5436
  })]
4660
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4661
- type: "button",
4662
- className: settings_card_module_css_default.addModel,
4663
- disabled,
4664
- onClick: () => {
4665
- setManualPromptModelOpen((open) => !open);
4666
- },
4667
- children: manualPromptModelOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4668
- })]
4669
- }),
4670
- manualPromptModelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4671
- className: settings_card_module_css_default.manualModelRow,
4672
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4673
- className: settings_card_module_css_default.input,
4674
- value: manualPromptModel,
4675
- placeholder: t("settings.addPromptModelPlaceholder"),
4676
- disabled,
4677
- onChange: (event) => {
4678
- setManualPromptModel(event.target.value);
4679
- }
4680
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5437
+ }),
5438
+ manualPromptModelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5439
+ className: settings_card_module_css_default.manualModelRow,
5440
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5441
+ className: settings_card_module_css_default.input,
5442
+ value: manualPromptModel,
5443
+ placeholder: t("settings.addPromptModelPlaceholder"),
5444
+ disabled,
5445
+ onChange: (event) => {
5446
+ setManualPromptModel(event.target.value);
5447
+ }
5448
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5449
+ type: "button",
5450
+ className: settings_card_module_css_default.addModel,
5451
+ disabled: disabled || manualPromptModel.trim() === "",
5452
+ onClick: () => {
5453
+ props.edit("promptModel", manualPromptModel);
5454
+ setManualPromptModel("");
5455
+ },
5456
+ children: t("settings.addModelConfirm")
5457
+ })]
5458
+ }) : null,
5459
+ promptModels.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5460
+ className: settings_card_module_css_default.modelCandidateList,
5461
+ role: "radiogroup",
5462
+ "aria-label": t("settings.promptModelsCandidates"),
5463
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5464
+ className: settings_card_module_css_default.modelCandidateLabel,
5465
+ children: t("settings.promptModelsCandidates")
5466
+ }), promptModels.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5467
+ type: "button",
5468
+ role: "radio",
5469
+ className: settings_card_module_css_default.modelCandidate,
5470
+ "aria-checked": state.promptModel.text === candidate,
5471
+ "data-selected": state.promptModel.text === candidate ? "" : void 0,
5472
+ disabled,
5473
+ onClick: () => {
5474
+ props.edit("promptModel", candidate);
5475
+ },
5476
+ children: candidate
5477
+ }, candidate))]
5478
+ }) : null,
5479
+ promptModelsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5480
+ className: settings_card_module_css_default.failed,
5481
+ role: "status",
5482
+ children: promptModelsError
5483
+ }) : null,
5484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4681
5485
  type: "button",
4682
- className: settings_card_module_css_default.addModel,
4683
- disabled: disabled || manualPromptModel.trim() === "",
5486
+ className: settings_card_module_css_default.inlineDisclosure,
5487
+ "aria-expanded": promptApiOpen,
4684
5488
  onClick: () => {
4685
- props.edit("promptModel", manualPromptModel);
4686
- setManualPromptModel("");
5489
+ setPromptApiOpen((open) => !open);
4687
5490
  },
4688
- children: t("settings.addModelConfirm")
4689
- })]
4690
- }) : null,
4691
- promptModels.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4692
- className: settings_card_module_css_default.modelCandidateList,
4693
- role: "radiogroup",
4694
- "aria-label": t("settings.promptModelsCandidates"),
4695
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4696
- className: settings_card_module_css_default.modelCandidateLabel,
4697
- children: t("settings.promptModelsCandidates")
4698
- }), promptModels.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4699
- type: "button",
4700
- role: "radio",
4701
- className: settings_card_module_css_default.modelCandidate,
4702
- "aria-checked": state.promptModel.text === candidate,
4703
- "data-selected": state.promptModel.text === candidate ? "" : void 0,
4704
- disabled,
4705
- onClick: () => {
4706
- props.edit("promptModel", candidate);
5491
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptApiAdvanced") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5492
+ "aria-hidden": "true",
5493
+ children: promptApiOpen ? "⌃" : "⌄"
5494
+ })]
5495
+ }),
5496
+ promptApiOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5497
+ className: settings_card_module_css_default.optionalContent,
5498
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
5499
+ id: "dsh-imagegen-settings-prompt-apiurl",
5500
+ label: t("settings.promptApiUrl"),
5501
+ hint: t("settings.promptApiUrlHint"),
5502
+ placeholder: "https://api.openai.com/v1",
5503
+ ...fieldProps,
5504
+ ...state.promptApiUrl,
5505
+ onEdit: (text) => {
5506
+ props.edit("promptApiUrl", text);
5507
+ },
5508
+ onReset: () => {
5509
+ props.resetField("promptApiUrl");
5510
+ }
5511
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
5512
+ id: "dsh-imagegen-settings-prompt-apikey",
5513
+ label: t("settings.promptApiKey"),
5514
+ hint: t("settings.promptApiKeyHint"),
5515
+ placeholder: "sk-…",
5516
+ secret: true,
5517
+ ...fieldProps,
5518
+ ...state.promptApiKey,
5519
+ overridden: false,
5520
+ onEdit: (text) => {
5521
+ props.edit("promptApiKey", text);
5522
+ },
5523
+ onReset: () => {
5524
+ props.resetField("promptApiKey");
5525
+ }
5526
+ })]
5527
+ }) : null
5528
+ ]
5529
+ }) : null,
5530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5531
+ type: "button",
5532
+ className: settings_card_module_css_default.disclosure,
5533
+ "aria-expanded": moreOpen,
5534
+ onClick: () => {
5535
+ setMoreOpen((open) => !open);
5536
+ },
5537
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.moreOptions") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5538
+ "aria-hidden": "true",
5539
+ children: moreOpen ? "⌃" : "⌄"
5540
+ })]
5541
+ }),
5542
+ moreOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5543
+ className: settings_card_module_css_default.optionalContent,
5544
+ children: [
5545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
5546
+ id: "dsh-imagegen-settings-enabled",
5547
+ label: t("settings.enabled"),
5548
+ hint: t("settings.enabledHint"),
5549
+ inheritLabel: t("settings.inherit"),
5550
+ onLabel: t("settings.on"),
5551
+ offLabel: t("settings.off"),
5552
+ ...fieldProps,
5553
+ ...state.enabled,
5554
+ onEdit: (text) => {
5555
+ props.edit("enabled", text);
4707
5556
  },
4708
- children: candidate
4709
- }, candidate))]
4710
- }) : null,
4711
- promptModelsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4712
- className: settings_card_module_css_default.failed,
4713
- role: "status",
4714
- children: promptModelsError
4715
- }) : null,
4716
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4717
- type: "button",
4718
- className: settings_card_module_css_default.inlineDisclosure,
4719
- "aria-expanded": promptApiOpen,
4720
- onClick: () => {
4721
- setPromptApiOpen((open) => !open);
4722
- },
4723
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptApiAdvanced") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4724
- "aria-hidden": "true",
4725
- children: promptApiOpen ? "⌃" : "⌄"
4726
- })]
4727
- }),
4728
- promptApiOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4729
- className: settings_card_module_css_default.optionalContent,
4730
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4731
- id: "dsh-imagegen-settings-prompt-apiurl",
4732
- label: t("settings.promptApiUrl"),
4733
- hint: t("settings.promptApiUrlHint"),
4734
- placeholder: "https://api.openai.com/v1",
5557
+ onReset: () => {
5558
+ props.resetField("enabled");
5559
+ }
5560
+ }),
5561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
5562
+ id: "dsh-imagegen-settings-announce",
5563
+ label: t("settings.announceToAgent"),
5564
+ hint: t("settings.announceToAgentHint"),
5565
+ inheritLabel: t("settings.inherit"),
5566
+ onLabel: t("settings.on"),
5567
+ offLabel: t("settings.off"),
4735
5568
  ...fieldProps,
4736
- ...state.promptApiUrl,
5569
+ ...state.announceToAgent,
4737
5570
  onEdit: (text) => {
4738
- props.edit("promptApiUrl", text);
5571
+ props.edit("announceToAgent", text);
4739
5572
  },
4740
5573
  onReset: () => {
4741
- props.resetField("promptApiUrl");
5574
+ props.resetField("announceToAgent");
4742
5575
  }
4743
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4744
- id: "dsh-imagegen-settings-prompt-apikey",
4745
- label: t("settings.promptApiKey"),
4746
- hint: t("settings.promptApiKeyHint"),
4747
- placeholder: "sk-…",
4748
- secret: true,
5576
+ }),
5577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
5578
+ id: "dsh-imagegen-settings-agent-generation",
5579
+ label: t("settings.allowAgentImageGeneration"),
5580
+ hint: t("settings.allowAgentImageGenerationHint"),
5581
+ inheritLabel: t("settings.inherit"),
5582
+ onLabel: t("settings.on"),
5583
+ offLabel: t("settings.off"),
4749
5584
  ...fieldProps,
4750
- ...state.promptApiKey,
4751
- overridden: false,
5585
+ ...state.allowAgentImageGeneration,
4752
5586
  onEdit: (text) => {
4753
- props.edit("promptApiKey", text);
5587
+ props.edit("allowAgentImageGeneration", text);
4754
5588
  },
4755
5589
  onReset: () => {
4756
- props.resetField("promptApiKey");
5590
+ props.resetField("allowAgentImageGeneration");
4757
5591
  }
4758
- })]
4759
- }) : null
4760
- ]
4761
- }) : null,
4762
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5592
+ })
5593
+ ]
5594
+ }) : null,
5595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5596
+ className: settings_card_module_css_default.footer,
5597
+ children: [
5598
+ state.failed || state.channels.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5599
+ className: settings_card_module_css_default.failed,
5600
+ role: "status",
5601
+ children: t("settings.saveFailed")
5602
+ }) : null,
5603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5604
+ type: "button",
5605
+ className: settings_card_module_css_default.discard,
5606
+ disabled: !state.dirty || state.saving || state.channels.saving,
5607
+ onClick: () => {
5608
+ props.discard();
5609
+ props.channels.discard();
5610
+ },
5611
+ children: t("settings.discard")
5612
+ }),
5613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5614
+ type: "button",
5615
+ className: settings_card_module_css_default.save,
5616
+ disabled: blocked,
5617
+ onClick: () => {
5618
+ props.channels.commit();
5619
+ props.save();
5620
+ },
5621
+ children: t(!state.saving && !state.channels.saving ? "settings.save" : "settings.saving")
5622
+ })
5623
+ ]
5624
+ })
5625
+ ]
5626
+ }) : null,
5627
+ open && editing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChannelEditor, {
5628
+ t,
5629
+ channel: editing,
5630
+ keyHeld: state.channels.keySet[editing.id] === true,
5631
+ usage,
5632
+ otherChannels: channels.filter((channel) => channel.id !== editing.id),
5633
+ isDefault: editing.id === state.channels.defaultChannelId,
5634
+ writable: state.writable,
5635
+ onPatch: (patch) => {
5636
+ replaceChannel(channels, editing.id, patch, props.channels);
5637
+ },
5638
+ onSetModels: (models) => {
5639
+ props.channels.setChannels(channels.map((channel) => channel.id === editing.id ? {
5640
+ ...channel,
5641
+ models
5642
+ } : channel));
5643
+ },
5644
+ onSetKey: (value) => {
5645
+ props.channels.setChannelKey(editing.id, value);
5646
+ },
5647
+ onSetDefault: () => {
5648
+ props.channels.setDefaultChannel(editing.id);
5649
+ },
5650
+ onRemove: () => {
5651
+ props.channels.setChannels(channels.filter((channel) => channel.id !== editing.id));
5652
+ if (editing.id === state.channels.defaultChannelId && channels.length > 1) {
5653
+ const next = channels.find((channel) => channel.id !== editing.id);
5654
+ if (next !== void 0) props.channels.setDefaultChannel(next.id);
5655
+ }
5656
+ setEditingId(null);
5657
+ },
5658
+ onClose: () => {
5659
+ setEditingId(null);
5660
+ }
5661
+ }, editing.id) : null
5662
+ ]
5663
+ });
5664
+ }
5665
+ /** Channel row + dialog helpers -------------------------------------------------- */
5666
+ function newChannelDraft(preset) {
5667
+ return {
5668
+ id: clientId(),
5669
+ preset: preset?.id ?? "",
5670
+ name: preset?.name ?? "",
5671
+ apiUrl: preset?.apiUrl ?? "",
5672
+ models: (preset?.models ?? []).map((model) => ({ ...model }))
5673
+ };
5674
+ }
5675
+ function addCustomChannel(channels, form, openEditor) {
5676
+ const draft = newChannelDraft(void 0);
5677
+ form.setChannels([...channels, draft]);
5678
+ openEditor(draft.id);
5679
+ }
5680
+ /** Patch one field (or models) of one staged channel. */
5681
+ function replaceChannel(channels, id, patch, form) {
5682
+ form.setChannels(channels.map((channel) => channel.id === id ? {
5683
+ ...channel,
5684
+ ...patch
5685
+ } : channel));
5686
+ }
5687
+ function hostOf(apiUrl) {
5688
+ try {
5689
+ return new URL(apiUrl).hostname;
5690
+ } catch {
5691
+ return apiUrl;
5692
+ }
5693
+ }
5694
+ function clientId() {
5695
+ return (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : void 0) ?? `ch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
5696
+ }
5697
+ /** Built-in provider picker, expanded inside the settings card. */
5698
+ function PresetPicker(props) {
5699
+ const { t } = props;
5700
+ const loadedRef = (0, react.useRef)(false);
5701
+ (0, react.useEffect)(() => {
5702
+ if (loadedRef.current) return;
5703
+ loadedRef.current = true;
5704
+ props.onLoad();
5705
+ }, []);
5706
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5707
+ className: settings_card_module_css_default.presetInline,
5708
+ "aria-label": t("channels.presetPickerTitle"),
5709
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
5710
+ className: settings_card_module_css_default.presetInlineHeader,
5711
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5712
+ className: settings_card_module_css_default.sectionTitle,
5713
+ children: t("channels.presetPickerTitle")
5714
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5715
+ className: settings_card_module_css_default.sectionHint,
5716
+ children: t("channels.presetPickerHint")
5717
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5718
+ type: "button",
5719
+ className: settings_card_module_css_default.editorClose,
5720
+ "aria-label": t("preview.close"),
5721
+ onClick: props.onClose,
5722
+ children: "×"
5723
+ })]
5724
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5725
+ className: settings_card_module_css_default.presetList,
5726
+ children: [
5727
+ props.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4763
5728
  type: "button",
4764
- className: settings_card_module_css_default.disclosure,
4765
- "aria-expanded": moreOpen,
5729
+ className: settings_card_module_css_default.presetRow,
5730
+ disabled: props.disabled,
4766
5731
  onClick: () => {
4767
- setMoreOpen((open) => !open);
5732
+ props.onPick(preset);
4768
5733
  },
4769
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.moreOptions") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4770
- "aria-hidden": "true",
4771
- children: moreOpen ? "⌃" : "⌄"
5734
+ children: [
5735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5736
+ className: settings_card_module_css_default.presetName,
5737
+ children: preset.name
5738
+ }),
5739
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5740
+ className: settings_card_module_css_default.presetMeta,
5741
+ children: [
5742
+ preset.apiUrl,
5743
+ " · ",
5744
+ t("channels.presetModelsHint", { n: preset.models.length })
5745
+ ]
5746
+ }),
5747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5748
+ className: settings_card_module_css_default.presetHint,
5749
+ children: preset.hint
5750
+ })
5751
+ ]
5752
+ }, preset.id)),
5753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5754
+ type: "button",
5755
+ className: settings_card_module_css_default.presetRow,
5756
+ "data-custom": true,
5757
+ disabled: props.disabled,
5758
+ onClick: props.onCustom,
5759
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5760
+ className: settings_card_module_css_default.presetName,
5761
+ children: ["+ ", t("channels.addCustom")]
5762
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5763
+ className: settings_card_module_css_default.presetHint,
5764
+ children: t("channels.presetCustomHint")
5765
+ })]
5766
+ }),
5767
+ props.error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5768
+ className: settings_card_module_css_default.failed,
5769
+ role: "status",
5770
+ children: t("channels.presetLoadFailed", { error: props.error })
5771
+ }) : null
5772
+ ]
5773
+ })]
5774
+ });
5775
+ }
5776
+ /** Channel editor (modal): key, display name, API URL, model catalog. */
5777
+ function ChannelEditor(props) {
5778
+ const { t, channel } = props;
5779
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
5780
+ const [candidates, setCandidates] = (0, react.useState)(null);
5781
+ const [detecting, setDetecting] = (0, react.useState)(false);
5782
+ const [detectError, setDetectError] = (0, react.useState)(null);
5783
+ const [manualId, setManualId] = (0, react.useState)("");
5784
+ const [removeOpen, setRemoveOpen] = (0, react.useState)(false);
5785
+ const [copyFrom, setCopyFrom] = (0, react.useState)("");
5786
+ const generatedCount = (alias) => {
5787
+ if (props.usage === null) return 0;
5788
+ return (props.usage.byChannel[channel.id] ?? props.usage.byChannel[`name:${channel.name}`] ?? {})[alias] ?? props.usage.totals[alias] ?? 0;
5789
+ };
5790
+ const detect = () => {
5791
+ setDetecting(true);
5792
+ setDetectError(null);
5793
+ const payload = { channelId: channel.id };
5794
+ if (channel.apiUrl.trim() !== "") payload.apiUrl = channel.apiUrl.trim();
5795
+ if (keyDraft.trim() !== "") payload.apiKey = keyDraft.trim();
5796
+ fetch(IMAGE_MODEL_API.models, {
5797
+ method: "POST",
5798
+ headers: { "content-type": "application/json" },
5799
+ body: JSON.stringify(payload)
5800
+ }).then(async (response) => {
5801
+ const body = await response.json();
5802
+ if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
5803
+ setCandidates(body.models ?? []);
5804
+ }).catch((error) => {
5805
+ setDetectError(error instanceof Error ? error.message : String(error));
5806
+ }).finally(() => {
5807
+ setDetecting(false);
5808
+ });
5809
+ };
5810
+ const autoDetected = (0, react.useRef)(false);
5811
+ (0, react.useEffect)(() => {
5812
+ if (autoDetected.current) return;
5813
+ autoDetected.current = true;
5814
+ if (channel.apiUrl.trim() !== "" && (props.keyHeld || keyDraft.trim() !== "")) detect();
5815
+ }, []);
5816
+ const addManual = () => {
5817
+ const id = manualId.trim();
5818
+ if (id === "") return;
5819
+ const next = [...channel.models];
5820
+ const alias = id;
5821
+ if (!next.some((model) => model.alias === alias)) next.push({
5822
+ alias,
5823
+ id
5824
+ });
5825
+ props.onSetModels(next);
5826
+ setManualId("");
5827
+ };
5828
+ const copyFromChannel = () => {
5829
+ const source = props.otherChannels.find((chance) => chance.id === copyFrom);
5830
+ if (source === void 0) return;
5831
+ const merged = [...channel.models];
5832
+ for (const model of source.models) {
5833
+ const alias = model.alias;
5834
+ let unique = alias;
5835
+ let suffix = 2;
5836
+ while (merged.some((entry) => entry.alias === unique)) unique = `${alias} (${suffix++})`;
5837
+ merged.push({
5838
+ alias: unique,
5839
+ id: model.id
5840
+ });
5841
+ }
5842
+ props.onSetModels(merged);
5843
+ setCopyFrom("");
5844
+ };
5845
+ const onlyKnown = () => {
5846
+ const known = (candidates ?? []).filter((id) => describeModel(id).known);
5847
+ const merged = [...channel.models];
5848
+ for (const id of known) {
5849
+ const alias = id;
5850
+ if (!merged.some((model) => model.alias === alias)) merged.push({
5851
+ alias,
5852
+ id
5853
+ });
5854
+ }
5855
+ props.onSetModels(merged);
5856
+ };
5857
+ const knownCount = (candidates ?? []).filter((id) => describeModel(id).known).length;
5858
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5859
+ className: settings_card_module_css_default.editorBackdrop,
5860
+ role: "dialog",
5861
+ "aria-modal": "true",
5862
+ "aria-label": `${t("channels.editorTitle")} · ${channel.name || t("channels.untitled")}`,
5863
+ onClick: props.onClose,
5864
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5865
+ className: settings_card_module_css_default.editorPanel,
5866
+ onClick: (event) => {
5867
+ event.stopPropagation();
5868
+ },
5869
+ children: [
5870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
5871
+ className: settings_card_module_css_default.editorHeader,
5872
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
5873
+ className: settings_card_module_css_default.sectionTitle,
5874
+ children: [
5875
+ t("channels.editorTitle"),
5876
+ " · ",
5877
+ channel.name || t("channels.untitled")
5878
+ ]
5879
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5880
+ className: settings_card_module_css_default.sectionHint,
5881
+ children: t("channels.editorSaveNote")
5882
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5883
+ type: "button",
5884
+ className: settings_card_module_css_default.editorClose,
5885
+ "aria-label": t("preview.close"),
5886
+ onClick: props.onClose,
5887
+ children: "×"
5888
+ })]
5889
+ }),
5890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5891
+ className: settings_card_module_css_default.editorField,
5892
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5893
+ className: settings_card_module_css_default.label,
5894
+ htmlFor: "dsh-imagegen-channel-name",
5895
+ children: t("channels.displayName")
5896
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5897
+ id: "dsh-imagegen-channel-name",
5898
+ className: settings_card_module_css_default.input,
5899
+ value: channel.name,
5900
+ placeholder: t("channels.untitled"),
5901
+ disabled: !props.writable,
5902
+ onChange: (event) => {
5903
+ props.onPatch({ name: event.target.value });
5904
+ }
5905
+ })]
5906
+ }),
5907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5908
+ className: settings_card_module_css_default.editorField,
5909
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5910
+ className: settings_card_module_css_default.label,
5911
+ htmlFor: "dsh-imagegen-channel-url",
5912
+ children: t("channels.apiUrl")
5913
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5914
+ id: "dsh-imagegen-channel-url",
5915
+ className: settings_card_module_css_default.input,
5916
+ value: channel.apiUrl,
5917
+ placeholder: "https://api.example.com/v1",
5918
+ disabled: !props.writable,
5919
+ onChange: (event) => {
5920
+ props.onPatch({ apiUrl: event.target.value });
5921
+ }
5922
+ })]
5923
+ }),
5924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5925
+ className: settings_card_module_css_default.editorField,
5926
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5927
+ className: settings_card_module_css_default.head,
5928
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5929
+ className: settings_card_module_css_default.label,
5930
+ htmlFor: "dsh-imagegen-channel-key",
5931
+ children: t("channels.apiKey")
5932
+ }), props.keyHeld || keyDraft !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5933
+ type: "button",
5934
+ className: settings_card_module_css_default.reset,
5935
+ disabled: !props.writable,
5936
+ onClick: () => {
5937
+ setKeyDraft("");
5938
+ props.onSetKey(void 0);
5939
+ },
5940
+ children: t("channels.keyClear")
5941
+ }) : null]
5942
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5943
+ id: "dsh-imagegen-channel-key",
5944
+ className: settings_card_module_css_default.input,
5945
+ type: "password",
5946
+ autoComplete: "off",
5947
+ value: keyDraft,
5948
+ placeholder: props.keyHeld ? t("channels.keyReplaceHint") : t("channels.keyMissingHint"),
5949
+ disabled: !props.writable,
5950
+ onChange: (event) => {
5951
+ const value = event.target.value;
5952
+ setKeyDraft(value);
5953
+ props.onSetKey(value === "" ? void 0 : value);
5954
+ }
5955
+ })]
5956
+ }),
5957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: settings_card_module_css_default.editorDivider }),
5958
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5959
+ className: settings_card_module_css_default.editorSectionHeader,
5960
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
5961
+ className: settings_card_module_css_default.label,
5962
+ children: t("channels.modelCatalogTitle")
5963
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5964
+ type: "button",
5965
+ className: settings_card_module_css_default.modelFetch,
5966
+ disabled: !props.writable || detecting,
5967
+ onClick: detect,
5968
+ children: detecting ? t("channels.detecting") : t("channels.detect")
4772
5969
  })]
4773
5970
  }),
4774
- moreOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4775
- className: settings_card_module_css_default.optionalContent,
4776
- children: [
4777
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4778
- id: "dsh-imagegen-settings-enabled",
4779
- label: t("settings.enabled"),
4780
- hint: t("settings.enabledHint"),
4781
- inheritLabel: t("settings.inherit"),
4782
- onLabel: t("settings.on"),
4783
- offLabel: t("settings.off"),
4784
- ...fieldProps,
4785
- ...state.enabled,
4786
- onEdit: (text) => {
4787
- props.edit("enabled", text);
4788
- },
4789
- onReset: () => {
4790
- props.resetField("enabled");
4791
- }
4792
- }),
4793
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4794
- id: "dsh-imagegen-settings-announce",
4795
- label: t("settings.announceToAgent"),
4796
- hint: t("settings.announceToAgentHint"),
4797
- inheritLabel: t("settings.inherit"),
4798
- onLabel: t("settings.on"),
4799
- offLabel: t("settings.off"),
4800
- ...fieldProps,
4801
- ...state.announceToAgent,
4802
- onEdit: (text) => {
4803
- props.edit("announceToAgent", text);
5971
+ detectError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5972
+ className: settings_card_module_css_default.failed,
5973
+ role: "status",
5974
+ children: t("channels.detectFailed", { error: detectError })
5975
+ }) : null,
5976
+ candidates !== null && detectError === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5977
+ className: settings_card_module_css_default.detectOk,
5978
+ role: "status",
5979
+ children: t("channels.detectSuccess", { n: candidates.length })
5980
+ }) : null,
5981
+ channel.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5982
+ className: settings_card_module_css_default.sectionHint,
5983
+ children: t("channels.noModelsHint")
5984
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
5985
+ className: settings_card_module_css_default.modelRows,
5986
+ children: channel.models.map((model, index) => {
5987
+ const entry = describeModel(model.id || model.alias);
5988
+ const generated = generatedCount(model.alias);
5989
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
5990
+ className: settings_card_module_css_default.modelRow,
5991
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5992
+ className: settings_card_module_css_default.modelRowInputs,
5993
+ children: [
5994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5995
+ className: settings_card_module_css_default.input,
5996
+ value: model.alias,
5997
+ "aria-label": t("channels.modelAliasLabel"),
5998
+ disabled: !props.writable,
5999
+ onChange: (event) => {
6000
+ const next = [...channel.models];
6001
+ next[index] = {
6002
+ ...model,
6003
+ alias: event.target.value
6004
+ };
6005
+ props.onSetModels(next);
6006
+ }
6007
+ }),
6008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6009
+ className: settings_card_module_css_default.modelArrow,
6010
+ children: "→"
6011
+ }),
6012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6013
+ className: settings_card_module_css_default.input,
6014
+ value: model.id,
6015
+ "aria-label": t("channels.modelIdLabel"),
6016
+ disabled: !props.writable,
6017
+ onChange: (event) => {
6018
+ const next = [...channel.models];
6019
+ next[index] = {
6020
+ ...model,
6021
+ id: event.target.value
6022
+ };
6023
+ props.onSetModels(next);
6024
+ }
6025
+ })
6026
+ ]
6027
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6028
+ className: settings_card_module_css_default.modelRowBadges,
6029
+ children: [
6030
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6031
+ className: settings_card_module_css_default.modelBadge,
6032
+ children: [entry.labelZh, entry.known ? "" : ` · ${t("channels.unknownProtocol")}`]
6033
+ }),
6034
+ generated > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6035
+ className: settings_card_module_css_default.modelBadge,
6036
+ "data-verified": true,
6037
+ children: t("channels.generated", { n: generated })
6038
+ }) : null,
6039
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6040
+ type: "button",
6041
+ className: settings_card_module_css_default.modelRowRemove,
6042
+ disabled: !props.writable,
6043
+ "aria-label": `${t("channels.removeModel")}: ${model.alias}`,
6044
+ onClick: () => {
6045
+ props.onSetModels(channel.models.filter((_, i) => i !== index));
6046
+ },
6047
+ children: "×"
6048
+ })
6049
+ ]
6050
+ })]
6051
+ }, `${model.alias}-${index}`);
6052
+ })
6053
+ }),
6054
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6055
+ className: settings_card_module_css_default.editorTools,
6056
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6057
+ className: settings_card_module_css_default.manualModelRow,
6058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6059
+ className: settings_card_module_css_default.input,
6060
+ value: manualId,
6061
+ placeholder: t("channels.manualAddPlaceholder"),
6062
+ disabled: !props.writable,
6063
+ onChange: (event) => {
6064
+ setManualId(event.target.value);
4804
6065
  },
4805
- onReset: () => {
4806
- props.resetField("announceToAgent");
6066
+ onKeyDown: (event) => {
6067
+ if (event.key === "Enter") {
6068
+ event.preventDefault();
6069
+ addManual();
6070
+ }
4807
6071
  }
4808
- }),
4809
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4810
- id: "dsh-imagegen-settings-agent-generation",
4811
- label: t("settings.allowAgentImageGeneration"),
4812
- hint: t("settings.allowAgentImageGenerationHint"),
4813
- inheritLabel: t("settings.inherit"),
4814
- onLabel: t("settings.on"),
4815
- offLabel: t("settings.off"),
4816
- ...fieldProps,
4817
- ...state.allowAgentImageGeneration,
4818
- onEdit: (text) => {
4819
- props.edit("allowAgentImageGeneration", text);
6072
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6073
+ type: "button",
6074
+ className: settings_card_module_css_default.addModel,
6075
+ disabled: !props.writable || manualId.trim() === "",
6076
+ onClick: addManual,
6077
+ children: t("channels.addModelConfirm")
6078
+ })]
6079
+ }), props.otherChannels.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6080
+ className: settings_card_module_css_default.manualModelRow,
6081
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
6082
+ className: settings_card_module_css_default.modelChoices,
6083
+ value: copyFrom,
6084
+ disabled: !props.writable,
6085
+ onChange: (event) => {
6086
+ setCopyFrom(event.target.value);
4820
6087
  },
4821
- onReset: () => {
4822
- props.resetField("allowAgentImageGeneration");
4823
- }
4824
- })
4825
- ]
6088
+ "aria-label": t("channels.copyFrom"),
6089
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6090
+ value: "",
6091
+ children: t("channels.copyFrom")
6092
+ }), props.otherChannels.map((other) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6093
+ value: other.id,
6094
+ children: other.name || t("channels.untitled")
6095
+ }, other.id))]
6096
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6097
+ type: "button",
6098
+ className: settings_card_module_css_default.addModel,
6099
+ disabled: !props.writable || copyFrom === "",
6100
+ onClick: copyFromChannel,
6101
+ children: t("channels.copyApply")
6102
+ })]
6103
+ }) : null]
6104
+ }),
6105
+ candidates !== null && candidates.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6106
+ className: settings_card_module_css_default.modelCandidateList,
6107
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6108
+ className: settings_card_module_css_default.modelCandidateLabel,
6109
+ children: [t("channels.candidatesTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6110
+ type: "button",
6111
+ className: settings_card_module_css_default.inlineDisclosure,
6112
+ disabled: !props.writable || knownCount === 0,
6113
+ onClick: onlyKnown,
6114
+ children: t("channels.candidatesQuick")
6115
+ })]
6116
+ }), candidates.map((candidate) => {
6117
+ const selected = channel.models.some((model) => model.alias === candidate);
6118
+ const entry = describeModel(candidate);
6119
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6120
+ className: settings_card_module_css_default.modelCandidate,
6121
+ "data-selected": selected ? "" : void 0,
6122
+ children: [
6123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6124
+ type: "checkbox",
6125
+ checked: selected,
6126
+ disabled: !props.writable,
6127
+ onChange: () => {
6128
+ const merged = selected ? channel.models.filter((model) => model.alias !== candidate) : [...channel.models, {
6129
+ alias: candidate,
6130
+ id: candidate
6131
+ }];
6132
+ props.onSetModels(merged);
6133
+ }
6134
+ }),
6135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: candidate }),
6136
+ !entry.known ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6137
+ className: settings_card_module_css_default.modelBadge,
6138
+ "data-warn": true,
6139
+ children: t("channels.unknownProtocol")
6140
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6141
+ className: settings_card_module_css_default.modelBadge,
6142
+ children: entry.labelZh
6143
+ })
6144
+ ]
6145
+ }, candidate);
6146
+ })]
4826
6147
  }) : null,
6148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: settings_card_module_css_default.editorDivider }),
4827
6149
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4828
- className: settings_card_module_css_default.footer,
6150
+ className: settings_card_module_css_default.editorFooter,
4829
6151
  children: [
4830
- state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4831
- className: settings_card_module_css_default.failed,
4832
- role: "status",
4833
- children: t("settings.saveFailed")
4834
- }) : null,
4835
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6152
+ props.isDefault ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6153
+ className: settings_card_module_css_default.channelBadge,
6154
+ "data-default": true,
6155
+ children: t("channels.defaultLabel")
6156
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4836
6157
  type: "button",
4837
- className: settings_card_module_css_default.discard,
4838
- disabled: !state.dirty || state.saving,
4839
- onClick: props.discard,
4840
- children: t("settings.discard")
6158
+ className: settings_card_module_css_default.inlineDisclosure,
6159
+ disabled: !props.writable,
6160
+ onClick: props.onSetDefault,
6161
+ children: t("channels.setDefault")
4841
6162
  }),
4842
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6163
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: settings_card_module_css_default.spacer }),
6164
+ removeOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6165
+ type: "button",
6166
+ className: settings_card_module_css_default.channelDanger,
6167
+ disabled: !props.writable,
6168
+ onClick: props.onRemove,
6169
+ children: t("channels.confirm")
6170
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6171
+ type: "button",
6172
+ className: settings_card_module_css_default.channelAction,
6173
+ onClick: () => {
6174
+ setRemoveOpen(false);
6175
+ },
6176
+ children: t("channels.cancel")
6177
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4843
6178
  type: "button",
4844
- className: settings_card_module_css_default.save,
4845
- disabled: blocked,
4846
- onClick: props.save,
4847
- children: t(!state.saving ? "settings.save" : "settings.saving")
6179
+ className: settings_card_module_css_default.channelAction,
6180
+ "data-danger": true,
6181
+ onClick: () => {
6182
+ setRemoveOpen(true);
6183
+ },
6184
+ children: t("channels.deleteThisChannel")
4848
6185
  })
4849
6186
  ]
4850
6187
  })
4851
6188
  ]
4852
- }) : null]
6189
+ })
4853
6190
  });
4854
6191
  }
4855
6192
  /** A staged value field; `secret` renders a password control. */
@@ -4887,17 +6224,7 @@ window.__ModuleLoader__.load({
4887
6224
  }) : null
4888
6225
  ]
4889
6226
  }),
4890
- props.multiline === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4891
- id: props.id,
4892
- className: props.invalid ? settings_card_module_css_default.textareaInvalid : settings_card_module_css_default.textarea,
4893
- ...props.invalid ? { "aria-invalid": true } : {},
4894
- value: props.text,
4895
- placeholder: props.placeholder ?? "",
4896
- disabled: props.disabled,
4897
- onChange: (event) => {
4898
- props.onEdit(event.target.value);
4899
- }
4900
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4901
6228
  id: props.id,
4902
6229
  className: props.invalid ? settings_card_module_css_default.inputInvalid : settings_card_module_css_default.input,
4903
6230
  type: props.secret === true ? "password" : "text",
@@ -4917,9 +6244,6 @@ window.__ModuleLoader__.load({
4917
6244
  ]
4918
6245
  });
4919
6246
  }
4920
- function splitModels(value) {
4921
- return [...new Set(value.split(/[\n,]/).map((model) => model.trim()).filter(Boolean))];
4922
- }
4923
6247
  /** A staged boolean field: 继承 / 开 / 关. */
4924
6248
  function BooleanField(props) {
4925
6249
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4976,201 +6300,6 @@ window.__ModuleLoader__.load({
4976
6300
  });
4977
6301
  }
4978
6302
  //#endregion
4979
- //#region src/client/settings-scope.ts
4980
- /**
4981
- * Browser-side settings scope for the dsh-imagegen namespace, served by the
4982
- * plugin's own loopback bridge routes (/api/dsh-imagegen/settings). The
4983
- * official rc.6 settings scope answers "unavailable" for every third-party
4984
- * namespace (the host-apiproxy allowlist is hard-coded), so this package
4985
- * re-serves its namespace through the host settings seam over a same-origin,
4986
- * loopback-only HTTP pair — the same pattern the dsh-web-ui family bridge
4987
- * uses, self-contained per plugin.
4988
- */
4989
- /** Settings wire face over the bridge routes (fetch-backed). */
4990
- function createBridgeApi(fetchFn) {
4991
- const post = async (path, body) => {
4992
- try {
4993
- const response = await fetchFn(path, {
4994
- method: "POST",
4995
- headers: { "content-type": "application/json" },
4996
- body: JSON.stringify(body)
4997
- });
4998
- if (!response.ok) return { result: {
4999
- ok: false,
5000
- code: "internal",
5001
- message: `bridge HTTP ${response.status}`
5002
- } };
5003
- return { result: await response.json() };
5004
- } catch {
5005
- return { result: {
5006
- ok: false,
5007
- code: "internal",
5008
- message: "settings bridge unreachable"
5009
- } };
5010
- }
5011
- };
5012
- return { settings: {
5013
- describe: async (payload) => post(SETTINGS_API.describe, payload),
5014
- mutate: async (payload) => post(SETTINGS_API.mutate, payload)
5015
- } };
5016
- }
5017
- /**
5018
- * A SettingsScope over the bridge face: serialized queue, revision-fenced
5019
- * writes, recovery read after a refusal. Mirrors the official controller's
5020
- * ordering but trusts the Host-seam value without re-running the wire-schema
5021
- * validation — the seam already validated it.
5022
- */
5023
- var BridgeScopeController = class {
5024
- api;
5025
- spec;
5026
- store;
5027
- /** Whether the namespace currently holds a stored secret (e.g. apiKey). */
5028
- keySet;
5029
- /** Individual secret presence bits, keyed by the settings field name. */
5030
- secretSets;
5031
- tail = Promise.resolve();
5032
- disposed = false;
5033
- constructor(api, spec) {
5034
- this.api = api;
5035
- this.spec = spec;
5036
- this.store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
5037
- status: "loading",
5038
- value: void 0,
5039
- base: void 0,
5040
- user: void 0,
5041
- revision: void 0,
5042
- writable: false,
5043
- mode: "host"
5044
- });
5045
- this.keySet = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(false);
5046
- this.secretSets = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({});
5047
- }
5048
- getSnapshot() {
5049
- return this.store.getSnapshot();
5050
- }
5051
- /** Whether a stored secret exists (from the redacted view's secrets list). */
5052
- getKeySetSnapshot() {
5053
- return this.keySet.getSnapshot();
5054
- }
5055
- /** Observe the secret-set flag. */
5056
- subscribeKeySet(listener) {
5057
- return this.keySet.subscribe(listener);
5058
- }
5059
- /** Whether a specific secret field currently has a stored value. */
5060
- getSecretSetSnapshot(field) {
5061
- return this.secretSets.getSnapshot()[field] === true;
5062
- }
5063
- /** Observe changes to individual secret-field presence bits. */
5064
- subscribeSecretSets(listener) {
5065
- return this.secretSets.subscribe(listener);
5066
- }
5067
- subscribe(listener) {
5068
- return this.store.subscribe(listener);
5069
- }
5070
- /** Queue a bridge refresh. */
5071
- load() {
5072
- return this.enqueue(() => this.read());
5073
- }
5074
- set(field, value) {
5075
- return this.enqueue(() => this.write({
5076
- op: "set",
5077
- path: [field],
5078
- value
5079
- }));
5080
- }
5081
- unset(field) {
5082
- return this.enqueue(() => this.write({
5083
- op: "unset",
5084
- path: [field]
5085
- }));
5086
- }
5087
- async dispose() {
5088
- this.disposed = true;
5089
- await this.tail;
5090
- }
5091
- enqueue(operation) {
5092
- if (this.disposed) return Promise.resolve();
5093
- const task = this.tail.then(async () => {
5094
- if (this.disposed) return;
5095
- await operation();
5096
- });
5097
- this.tail = task.catch(() => {});
5098
- return task;
5099
- }
5100
- async read() {
5101
- let response;
5102
- try {
5103
- response = await this.api.describe({});
5104
- } catch {
5105
- if (!this.disposed) this.store.update((draft) => {
5106
- draft.status = "unavailable";
5107
- });
5108
- return;
5109
- }
5110
- if (!response.result.ok || this.disposed) {
5111
- if (!this.disposed) this.store.update((draft) => {
5112
- draft.status = "unavailable";
5113
- });
5114
- return;
5115
- }
5116
- const { namespaces, writable } = response.result.value;
5117
- const view = namespaces?.find((candidate) => candidate.ns === this.spec.namespace);
5118
- if (view === void 0) {
5119
- this.store.update((draft) => {
5120
- draft.status = "unavailable";
5121
- draft.writable = writable === true;
5122
- });
5123
- this.keySet.set(false);
5124
- this.secretSets.set({});
5125
- return;
5126
- }
5127
- this.accept(view, writable);
5128
- }
5129
- async write(op) {
5130
- const revision = this.getSnapshot().revision;
5131
- let response;
5132
- try {
5133
- response = await this.api.mutate({
5134
- ns: this.spec.namespace,
5135
- ops: [op],
5136
- ...revision === void 0 ? {} : { expectedRevision: revision }
5137
- });
5138
- } catch {
5139
- await this.read();
5140
- return;
5141
- }
5142
- if (!response.result.ok || this.disposed) {
5143
- await this.read();
5144
- return;
5145
- }
5146
- this.accept(response.result.value, void 0);
5147
- }
5148
- accept(view, writable) {
5149
- this.store.update((draft) => {
5150
- draft.revision = view.revision;
5151
- draft.base = view.base;
5152
- draft.user = view.user;
5153
- if (writable !== void 0) draft.writable = writable;
5154
- draft.status = "ready";
5155
- draft.value = view.value;
5156
- });
5157
- const secretSets = Object.fromEntries((view.secrets ?? []).map((secret) => [secret.path.join("."), secret.set]));
5158
- this.keySet.set(Object.values(secretSets).some(Boolean));
5159
- this.secretSets.set(secretSets);
5160
- }
5161
- };
5162
- /**
5163
- * Bind the dsh-imagegen settings scope over the bridge routes and start its
5164
- * initial read (the caller mounts nothing until the scope settles).
5165
- * @param fetchFn - the fetch implementation (the global fetch on loopback).
5166
- * @returns the scope; unavailable when the bridge is unreachable.
5167
- */
5168
- function bindImageGenScope(fetchFn = fetch) {
5169
- const controller = new BridgeScopeController(createBridgeApi(fetchFn).settings, { namespace: "dsh-imagegen" });
5170
- controller.load();
5171
- return controller;
5172
- }
5173
- //#endregion
5174
6303
  //#region \0dsh-css:E:\dsh-plugin\src\client\image-toolview.module.css.mjs
5175
6304
  const css = "._3eGrYG_root{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:7px;margin:4px 0;padding:8px 10px 10px;display:flex}._3eGrYG_header{min-height:20px;color:var(--dsw-alias-label-secondary);align-items:center;gap:7px;font-size:12px;display:flex}._3eGrYG_icon{color:var(--dsw-alias-brand-primary);font-size:15px;line-height:1}._3eGrYG_status{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}._3eGrYG_message,._3eGrYG_loading,._3eGrYG_error{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.5}._3eGrYG_images{flex-wrap:wrap;gap:8px;display:flex}._3eGrYG_imageLink{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);border-radius:6px;max-width:min(280px,100%);display:block;overflow:hidden}._3eGrYG_imageLink:hover{border-color:var(--dsw-alias-brand-primary)}._3eGrYG_image{object-fit:contain;width:auto;max-width:280px;height:auto;max-height:220px;display:block}._3eGrYG_error{color:var(--dsw-alias-state-error-primary)}";
5176
6305
  const tagId = "@dickpy/dsh-imagegen/image-toolview.module.css";
@@ -5182,16 +6311,16 @@ window.__ModuleLoader__.load({
5182
6311
  document.head.appendChild(tag);
5183
6312
  }
5184
6313
  var image_toolview_module_css_default = {
5185
- "imageLink": "_3eGrYG_imageLink",
5186
6314
  "header": "_3eGrYG_header",
6315
+ "message": "_3eGrYG_message",
6316
+ "status": "_3eGrYG_status",
5187
6317
  "loading": "_3eGrYG_loading",
6318
+ "image": "_3eGrYG_image",
6319
+ "imageLink": "_3eGrYG_imageLink",
6320
+ "images": "_3eGrYG_images",
5188
6321
  "icon": "_3eGrYG_icon",
5189
6322
  "error": "_3eGrYG_error",
5190
- "status": "_3eGrYG_status",
5191
- "message": "_3eGrYG_message",
5192
- "root": "_3eGrYG_root",
5193
- "image": "_3eGrYG_image",
5194
- "images": "_3eGrYG_images"
6323
+ "root": "_3eGrYG_root"
5195
6324
  };
5196
6325
  //#endregion
5197
6326
  //#region src/client/image-toolview.tsx
@@ -5200,7 +6329,8 @@ window.__ModuleLoader__.load({
5200
6329
  }
5201
6330
  function imageRefsOf(block) {
5202
6331
  if (!isSettled(block)) return [];
5203
- return block.content.flatMap((content) => content.type === "image" ? [content.attachment] : []);
6332
+ const resultContent = block.resultView?.card === "generic" ? block.resultView.content ?? [] : [];
6333
+ return [...block.content, ...resultContent].flatMap((content) => content.type === "image" ? [content.attachment] : []);
5204
6334
  }
5205
6335
  function textOf(block) {
5206
6336
  if (!isSettled(block)) return "";
@@ -5275,13 +6405,17 @@ window.__ModuleLoader__.load({
5275
6405
  }
5276
6406
  /** Register the inline image result view for all image-generation result tools. */
5277
6407
  function registerImageToolviews(ctx) {
5278
- const sessions = ctx.get("sessions");
5279
- const load = async (sessionId, ref) => {
5280
- const session = sessions?.binding(sessionId)?.session;
5281
- if (session === void 0) throw new Error("当前会话不可用,无法读取图片附件。");
5282
- const result = await session.readAttachment(ref.attachmentId);
5283
- if (!result.ok) throw new Error(result.error.message);
5284
- const blob = new Blob([new Uint8Array(result.value.data)], { type: result.value.attachment.mediaType });
6408
+ const load = async (_sessionId, ref) => {
6409
+ const query = new URLSearchParams({
6410
+ attachment_id: String(ref.attachmentId),
6411
+ media_type: ref.mediaType,
6412
+ bytes: String(ref.bytes),
6413
+ width: String(ref.width),
6414
+ height: String(ref.height)
6415
+ });
6416
+ const response = await fetch(`${AGENT_IMAGE_API}?${query.toString()}`);
6417
+ if (!response.ok) throw new Error(`无法读取图片附件(HTTP ${response.status})。`);
6418
+ const blob = await response.blob();
5285
6419
  return URL.createObjectURL(blob);
5286
6420
  };
5287
6421
  const ImageToolView = (props) => {