@dickpy/dsh-imagegen 1.2.3 → 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",
@@ -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,7 +525,58 @@ 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",
@@ -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
- "detailTitle": "o0mAxG_detailTitle",
789
- "headerActions": "o0mAxG_headerActions",
790
- "title": "o0mAxG_title",
906
+ "detailMeta": "o0mAxG_detailMeta",
907
+ "body": "o0mAxG_body",
908
+ "cardSource": "o0mAxG_cardSource",
909
+ "notice": "o0mAxG_notice",
910
+ "cardCategory": "o0mAxG_cardCategory",
911
+ "detailActions": "o0mAxG_detailActions",
912
+ "overlay": "o0mAxG_overlay",
791
913
  "shell": "o0mAxG_shell",
792
- "card": "o0mAxG_card",
914
+ "cardMeta": "o0mAxG_cardMeta",
915
+ "categoryPill": "o0mAxG_categoryPill",
916
+ "thumbWrap": "o0mAxG_thumbWrap",
917
+ "thumbPlaceholder": "o0mAxG_thumbPlaceholder",
918
+ "cardBody": "o0mAxG_cardBody",
793
919
  "attribution": "o0mAxG_attribution",
920
+ "sourceLink": "o0mAxG_sourceLink",
921
+ "detailInfo": "o0mAxG_detailInfo",
922
+ "detail": "o0mAxG_detail",
923
+ "detailTitle": "o0mAxG_detailTitle",
924
+ "footer": "o0mAxG_footer",
794
925
  "detailPrompt": "o0mAxG_detailPrompt",
926
+ "thumb": "o0mAxG_thumb",
927
+ "detailLink": "o0mAxG_detailLink",
928
+ "title": "o0mAxG_title",
795
929
  "meta": "o0mAxG_meta",
796
- "cardMeta": "o0mAxG_cardMeta",
797
- "detailMeta": "o0mAxG_detailMeta",
798
- "detailActions": "o0mAxG_detailActions",
799
- "cardCategory": "o0mAxG_cardCategory",
800
- "featuredBadge": "o0mAxG_featuredBadge",
801
- "close": "o0mAxG_close",
802
- "notice": "o0mAxG_notice",
803
930
  "toolbar": "o0mAxG_toolbar",
804
- "sourceLink": "o0mAxG_sourceLink",
805
- "detailMedia": "o0mAxG_detailMedia",
806
- "search": "o0mAxG_search",
807
- "cardSource": "o0mAxG_cardSource",
808
- "thumb": "o0mAxG_thumb",
931
+ "detailOverlay": "o0mAxG_detailOverlay",
809
932
  "detailImage": "o0mAxG_detailImage",
810
- "body": "o0mAxG_body",
811
- "cardTitle": "o0mAxG_cardTitle",
812
- "detailLink": "o0mAxG_detailLink",
813
- "header": "o0mAxG_header",
933
+ "categoryRow": "o0mAxG_categoryRow",
934
+ "dsh-imagegen-templates-spin": "o0mAxG_dsh-imagegen-templates-spin",
935
+ "headerActions": "o0mAxG_headerActions",
936
+ "search": "o0mAxG_search",
937
+ "card": "o0mAxG_card",
814
938
  "heading": "o0mAxG_heading",
815
- "categoryPill": "o0mAxG_categoryPill",
816
939
  "spinner": "o0mAxG_spinner",
817
- "detailInfo": "o0mAxG_detailInfo",
818
- "categoryRow": "o0mAxG_categoryRow",
940
+ "close": "o0mAxG_close",
941
+ "header": "o0mAxG_header",
942
+ "featuredBadge": "o0mAxG_featuredBadge",
943
+ "cardTitle": "o0mAxG_cardTitle",
819
944
  "grid": "o0mAxG_grid",
820
- "overlay": "o0mAxG_overlay",
821
- "thumbWrap": "o0mAxG_thumbWrap",
822
- "footer": "o0mAxG_footer",
823
- "detail": "o0mAxG_detail",
824
945
  "state": "o0mAxG_state",
825
- "thumbPlaceholder": "o0mAxG_thumbPlaceholder",
826
- "dsh-imagegen-templates-spin": "o0mAxG_dsh-imagegen-templates-spin",
827
- "detailOverlay": "o0mAxG_detailOverlay",
828
- "cardBody": "o0mAxG_cardBody"
946
+ "detailMedia": "o0mAxG_detailMedia"
829
947
  };
830
948
  //#endregion
831
949
  //#region src/client/TemplateLibrary.tsx
@@ -1301,6 +1419,231 @@ 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.
@@ -1338,165 +1681,165 @@ window.__ModuleLoader__.load({
1338
1681
  document.head.appendChild(tag);
1339
1682
  }
1340
1683
  var panel_module_css_default = {
1341
- "card": "Yvqh9W_card",
1684
+ "image": "Yvqh9W_image",
1342
1685
  "galleryTagInput": "Yvqh9W_galleryTagInput",
1343
- "galleryWorkspace": "Yvqh9W_galleryWorkspace",
1344
- "generateButton": "Yvqh9W_generateButton",
1345
- "dshImageGenSpin": "Yvqh9W_dshImageGenSpin",
1346
- "comparisonFullscreen": "Yvqh9W_comparisonFullscreen",
1347
- "historyActions": "Yvqh9W_historyActions",
1348
- "zoomHint": "Yvqh9W_zoomHint",
1349
- "galleryMasonry": "Yvqh9W_galleryMasonry",
1350
- "galleryImageButton": "Yvqh9W_galleryImageButton",
1351
- "panelHeading": "Yvqh9W_panelHeading",
1352
- "canvasStateHint": "Yvqh9W_canvasStateHint",
1353
- "historyTitle": "Yvqh9W_historyTitle",
1354
- "updateActions": "Yvqh9W_updateActions",
1355
1686
  "lightboxCaptionRow": "Yvqh9W_lightboxCaptionRow",
1356
- "reference": "Yvqh9W_reference",
1357
- "modelMenu": "Yvqh9W_modelMenu",
1358
- "galleryTagFilterList": "Yvqh9W_galleryTagFilterList",
1359
- "gallerySelect": "Yvqh9W_gallerySelect",
1687
+ "bigSpinner": "Yvqh9W_bigSpinner",
1688
+ "galleryTagEdit": "Yvqh9W_galleryTagEdit",
1689
+ "canvasState": "Yvqh9W_canvasState",
1690
+ "compareControl": "Yvqh9W_compareControl",
1691
+ "galleryTagFilter": "Yvqh9W_galleryTagFilter",
1360
1692
  "taskTrayCount": "Yvqh9W_taskTrayCount",
1361
- "galleryImage": "Yvqh9W_galleryImage",
1362
- "historyItem": "Yvqh9W_historyItem",
1693
+ "historyEmpty": "Yvqh9W_historyEmpty",
1694
+ "gallerySelectionClear": "Yvqh9W_gallerySelectionClear",
1695
+ "historyPrompt": "Yvqh9W_historyPrompt",
1696
+ "galleryFilterNote": "Yvqh9W_galleryFilterNote",
1697
+ "imageCaption": "Yvqh9W_imageCaption",
1698
+ "compareModelChoices": "Yvqh9W_compareModelChoices",
1699
+ "entryLabel": "Yvqh9W_entryLabel",
1700
+ "panelHeader": "Yvqh9W_panelHeader",
1701
+ "comparisonGrid": "Yvqh9W_comparisonGrid",
1363
1702
  "galleryCard": "Yvqh9W_galleryCard",
1364
- "historySearch": "Yvqh9W_historySearch",
1365
- "lightboxImage": "Yvqh9W_lightboxImage",
1366
- "lightboxTools": "Yvqh9W_lightboxTools",
1703
+ "galleryTagFilterList": "Yvqh9W_galleryTagFilterList",
1704
+ "galleryCardInfo": "Yvqh9W_galleryCardInfo",
1705
+ "galleryViewToggle": "Yvqh9W_galleryViewToggle",
1367
1706
  "history": "Yvqh9W_history",
1368
- "lightboxIndex": "Yvqh9W_lightboxIndex",
1369
- "galleryToast": "Yvqh9W_galleryToast",
1370
- "updateText": "Yvqh9W_updateText",
1371
- "historyPrompt": "Yvqh9W_historyPrompt",
1707
+ "comparisonFullscreenGrid": "Yvqh9W_comparisonFullscreenGrid",
1708
+ "taskRow": "Yvqh9W_taskRow",
1709
+ "prompt": "Yvqh9W_prompt",
1710
+ "taskTrayHeader": "Yvqh9W_taskTrayHeader",
1372
1711
  "optionGrid": "Yvqh9W_optionGrid",
1373
- "grid": "Yvqh9W_grid",
1374
- "lightboxMeta": "Yvqh9W_lightboxMeta",
1375
- "galleryRatio": "Yvqh9W_galleryRatio",
1376
- "gallerySelectionBar": "Yvqh9W_gallerySelectionBar",
1377
- "galleryCardInfo": "Yvqh9W_galleryCardInfo",
1378
1712
  "taskTray": "Yvqh9W_taskTray",
1379
1713
  "canvasStateTitle": "Yvqh9W_canvasStateTitle",
1380
- "historyMain": "Yvqh9W_historyMain",
1381
- "historyThumbPlaceholder": "Yvqh9W_historyThumbPlaceholder",
1382
- "historyMeta": "Yvqh9W_historyMeta",
1383
- "hiddenFile": "Yvqh9W_hiddenFile",
1384
- "modeRow": "Yvqh9W_modeRow",
1385
- "imageCard": "Yvqh9W_imageCard",
1386
- "lightbox": "Yvqh9W_lightbox",
1387
- "galleryHeading": "Yvqh9W_galleryHeading",
1388
- "connectionStatus": "Yvqh9W_connectionStatus",
1389
- "compareToggle": "Yvqh9W_compareToggle",
1390
- "optionPill": "Yvqh9W_optionPill",
1391
- "historyHeader": "Yvqh9W_historyHeader",
1392
- "historyList": "Yvqh9W_historyList",
1393
- "taskRows": "Yvqh9W_taskRows",
1394
- "lightboxDownload": "Yvqh9W_lightboxDownload",
1395
- "referenceActions": "Yvqh9W_referenceActions",
1396
- "historyFilters": "Yvqh9W_historyFilters",
1397
- "historyClear": "Yvqh9W_historyClear",
1398
- "updateBanner": "Yvqh9W_updateBanner",
1399
- "lightboxTool": "Yvqh9W_lightboxTool",
1400
- "prompt": "Yvqh9W_prompt",
1401
- "lightboxActions": "Yvqh9W_lightboxActions",
1402
- "galleryBulkButton": "Yvqh9W_galleryBulkButton",
1403
- "modePill": "Yvqh9W_modePill",
1714
+ "taskPrompt": "Yvqh9W_taskPrompt",
1404
1715
  "connectionDot": "Yvqh9W_connectionDot",
1405
- "taskRow": "Yvqh9W_taskRow",
1406
- "uploadHint": "Yvqh9W_uploadHint",
1407
- "paramLabel": "Yvqh9W_paramLabel",
1408
- "taskTrayToggle": "Yvqh9W_taskTrayToggle",
1409
- "paramGroup": "Yvqh9W_paramGroup",
1716
+ "galleryFilter": "Yvqh9W_galleryFilter",
1717
+ "dshImageGenSpin": "Yvqh9W_dshImageGenSpin",
1718
+ "galleryRatioList": "Yvqh9W_galleryRatioList",
1719
+ "gallerySort": "Yvqh9W_gallerySort",
1720
+ "grid": "Yvqh9W_grid",
1721
+ "gallerySelectionBar": "Yvqh9W_gallerySelectionBar",
1410
1722
  "templatesButton": "Yvqh9W_templatesButton",
1411
- "modelMenuList": "Yvqh9W_modelMenuList",
1412
- "lightboxZoomLevel": "Yvqh9W_lightboxZoomLevel",
1413
- "panelHeader": "Yvqh9W_panelHeader",
1723
+ "updateActions": "Yvqh9W_updateActions",
1414
1724
  "configGuide": "Yvqh9W_configGuide",
1415
- "optionRow": "Yvqh9W_optionRow",
1416
- "galleryFilter": "Yvqh9W_galleryFilter",
1417
- "taskStatus": "Yvqh9W_taskStatus",
1418
- "lightboxCopy": "Yvqh9W_lightboxCopy",
1419
- "panelTitle": "Yvqh9W_panelTitle",
1420
- "lightboxFigure": "Yvqh9W_lightboxFigure",
1725
+ "modelLabel": "Yvqh9W_modelLabel",
1726
+ "configGuideBody": "Yvqh9W_configGuideBody",
1727
+ "paramLabel": "Yvqh9W_paramLabel",
1728
+ "taskTrayToggle": "Yvqh9W_taskTrayToggle",
1729
+ "canvas": "Yvqh9W_canvas",
1730
+ "entryIcon": "Yvqh9W_entryIcon",
1421
1731
  "generateInner": "Yvqh9W_generateInner",
1422
- "view": "Yvqh9W_view",
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",
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",
1755
+ "promptCount": "Yvqh9W_promptCount",
1756
+ "paramGroup": "Yvqh9W_paramGroup",
1757
+ "generateButton": "Yvqh9W_generateButton",
1758
+ "lightboxCaption": "Yvqh9W_lightboxCaption",
1759
+ "canvasHistoryTag": "Yvqh9W_canvasHistoryTag",
1423
1760
  "uploadBox": "Yvqh9W_uploadBox",
1424
- "galleryCardFooter": "Yvqh9W_galleryCardFooter",
1425
- "bigSpinner": "Yvqh9W_bigSpinner",
1426
- "canvasState": "Yvqh9W_canvasState",
1427
- "imageCaption": "Yvqh9W_imageCaption",
1428
- "historyThumb": "Yvqh9W_historyThumb",
1429
- "footer": "Yvqh9W_footer",
1430
- "canvasMeta": "Yvqh9W_canvasMeta",
1431
- "galleryToolbarActions": "Yvqh9W_galleryToolbarActions",
1432
- "compareControl": "Yvqh9W_compareControl",
1433
- "galleryTagEditor": "Yvqh9W_galleryTagEditor",
1761
+ "lightboxNav": "Yvqh9W_lightboxNav",
1762
+ "updateText": "Yvqh9W_updateText",
1763
+ "galleryTags": "Yvqh9W_galleryTags",
1764
+ "canvasEmptyIcon": "Yvqh9W_canvasEmptyIcon",
1434
1765
  "gallerySelectMode": "Yvqh9W_gallerySelectMode",
1435
- "galleryTagFilter": "Yvqh9W_galleryTagFilter",
1436
- "canvasError": "Yvqh9W_canvasError",
1766
+ "galleryWorkspace": "Yvqh9W_galleryWorkspace",
1767
+ "galleryRatio": "Yvqh9W_galleryRatio",
1768
+ "spinner": "Yvqh9W_spinner",
1769
+ "panel": "Yvqh9W_panel",
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",
1437
1778
  "galleryBadge": "Yvqh9W_galleryBadge",
1438
- "entry": "Yvqh9W_entry",
1779
+ "historyFilters": "Yvqh9W_historyFilters",
1780
+ "gallerySelect": "Yvqh9W_gallerySelect",
1781
+ "modePill": "Yvqh9W_modePill",
1782
+ "view": "Yvqh9W_view",
1783
+ "taskStatus": "Yvqh9W_taskStatus",
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",
1795
+ "canvasStateHint": "Yvqh9W_canvasStateHint",
1796
+ "referenceActions": "Yvqh9W_referenceActions",
1797
+ "modelMenu": "Yvqh9W_modelMenu",
1798
+ "modelMenuItem": "Yvqh9W_modelMenuItem",
1799
+ "lightbox": "Yvqh9W_lightbox",
1800
+ "lightboxMeta": "Yvqh9W_lightboxMeta",
1801
+ "lightboxDownload": "Yvqh9W_lightboxDownload",
1802
+ "historyMeta": "Yvqh9W_historyMeta",
1803
+ "canvasMeta": "Yvqh9W_canvasMeta",
1804
+ "taskTrayChevron": "Yvqh9W_taskTrayChevron",
1805
+ "historyItem": "Yvqh9W_historyItem",
1806
+ "config": "Yvqh9W_config",
1807
+ "optionRow": "Yvqh9W_optionRow",
1439
1808
  "modelWrap": "Yvqh9W_modelWrap",
1440
- "gallerySearch": "Yvqh9W_gallerySearch",
1809
+ "modelSelect": "Yvqh9W_modelSelect",
1810
+ "lightboxTool": "Yvqh9W_lightboxTool",
1811
+ "dshImageGenToastIn": "Yvqh9W_dshImageGenToastIn",
1812
+ "galleryFilterCount": "Yvqh9W_galleryFilterCount",
1813
+ "galleryFilterDivider": "Yvqh9W_galleryFilterDivider",
1441
1814
  "galleryClear": "Yvqh9W_galleryClear",
1442
- "galleryFilters": "Yvqh9W_galleryFilters",
1443
- "galleryAvatar": "Yvqh9W_galleryAvatar",
1444
- "promptFooter": "Yvqh9W_promptFooter",
1445
- "comparisonBoard": "Yvqh9W_comparisonBoard",
1446
- "historyInfo": "Yvqh9W_historyInfo",
1447
- "comparisonFullscreenGrid": "Yvqh9W_comparisonFullscreenGrid",
1815
+ "galleryCardFooter": "Yvqh9W_galleryCardFooter",
1816
+ "galleryTagEditor": "Yvqh9W_galleryTagEditor",
1817
+ "studio": "Yvqh9W_studio",
1448
1818
  "referenceImage": "Yvqh9W_referenceImage",
1449
- "taskTrayClose": "Yvqh9W_taskTrayClose",
1450
- "lightboxCaption": "Yvqh9W_lightboxCaption",
1451
- "galleryViewToggle": "Yvqh9W_galleryViewToggle",
1452
- "galleryRatioList": "Yvqh9W_galleryRatioList",
1453
- "githubLink": "Yvqh9W_githubLink",
1454
- "galleryFilterNote": "Yvqh9W_galleryFilterNote",
1455
- "panel": "Yvqh9W_panel",
1456
- "canvasEmptyIcon": "Yvqh9W_canvasEmptyIcon",
1457
- "canvasHistoryTag": "Yvqh9W_canvasHistoryTag",
1458
- "image": "Yvqh9W_image",
1459
- "dshImageGenToastIn": "Yvqh9W_dshImageGenToastIn",
1460
- "compareModelChoices": "Yvqh9W_compareModelChoices",
1461
- "galleryToolbar": "Yvqh9W_galleryToolbar",
1819
+ "entry": "Yvqh9W_entry",
1462
1820
  "galleryAdd": "Yvqh9W_galleryAdd",
1463
- "studio": "Yvqh9W_studio",
1464
- "canvasBody": "Yvqh9W_canvasBody",
1465
- "lightboxScaleFrame": "Yvqh9W_lightboxScaleFrame",
1466
1821
  "lightboxClose": "Yvqh9W_lightboxClose",
1467
- "historyAction": "Yvqh9W_historyAction",
1468
- "gallerySelectionClear": "Yvqh9W_gallerySelectionClear",
1469
- "galleryFilterDivider": "Yvqh9W_galleryFilterDivider",
1470
- "canvas": "Yvqh9W_canvas",
1471
- "lightboxNav": "Yvqh9W_lightboxNav",
1472
- "updateRelease": "Yvqh9W_updateRelease",
1473
- "download": "Yvqh9W_download",
1474
- "entryIcon": "Yvqh9W_entryIcon",
1475
- "enhanceButton": "Yvqh9W_enhanceButton",
1476
- "modelLabel": "Yvqh9W_modelLabel",
1477
- "config": "Yvqh9W_config",
1478
- "taskTrayChevron": "Yvqh9W_taskTrayChevron",
1479
- "modelMenuItem": "Yvqh9W_modelMenuItem",
1480
- "taskTrayHeader": "Yvqh9W_taskTrayHeader",
1822
+ "galleryToolbarActions": "Yvqh9W_galleryToolbarActions",
1823
+ "lightboxIndex": "Yvqh9W_lightboxIndex",
1481
1824
  "paramHint": "Yvqh9W_paramHint",
1482
- "entryLabel": "Yvqh9W_entryLabel",
1483
- "uploadIcon": "Yvqh9W_uploadIcon",
1484
- "modelSelect": "Yvqh9W_modelSelect",
1485
- "spinner": "Yvqh9W_spinner",
1486
- "galleryCount": "Yvqh9W_galleryCount",
1487
- "gallerySort": "Yvqh9W_gallerySort",
1488
- "comparisonGrid": "Yvqh9W_comparisonGrid",
1489
- "configScroll": "Yvqh9W_configScroll",
1490
- "galleryTagEdit": "Yvqh9W_galleryTagEdit",
1491
- "galleryFilterCount": "Yvqh9W_galleryFilterCount",
1492
- "lightboxEdit": "Yvqh9W_lightboxEdit",
1493
- "historyEmpty": "Yvqh9W_historyEmpty",
1494
- "promptCount": "Yvqh9W_promptCount",
1495
- "galleryRemove": "Yvqh9W_galleryRemove",
1496
- "configGuideBody": "Yvqh9W_configGuideBody",
1497
- "taskPrompt": "Yvqh9W_taskPrompt",
1498
- "galleryTags": "Yvqh9W_galleryTags",
1825
+ "historyAction": "Yvqh9W_historyAction",
1826
+ "lightboxImage": "Yvqh9W_lightboxImage",
1827
+ "githubLink": "Yvqh9W_githubLink",
1828
+ "hiddenFile": "Yvqh9W_hiddenFile",
1829
+ "lightboxZoomLevel": "Yvqh9W_lightboxZoomLevel",
1830
+ "galleryImage": "Yvqh9W_galleryImage",
1831
+ "galleryBulkButton": "Yvqh9W_galleryBulkButton",
1832
+ "gallerySearch": "Yvqh9W_gallerySearch",
1833
+ "historySearch": "Yvqh9W_historySearch",
1834
+ "lightboxScaleFrame": "Yvqh9W_lightboxScaleFrame",
1835
+ "historyThumb": "Yvqh9W_historyThumb",
1836
+ "galleryFilters": "Yvqh9W_galleryFilters",
1837
+ "lightboxCopy": "Yvqh9W_lightboxCopy",
1838
+ "taskTrayClose": "Yvqh9W_taskTrayClose",
1839
+ "galleryToast": "Yvqh9W_galleryToast",
1499
1840
  "galleryFilterHeading": "Yvqh9W_galleryFilterHeading",
1841
+ "historyThumbPlaceholder": "Yvqh9W_historyThumbPlaceholder",
1842
+ "lightboxFigure": "Yvqh9W_lightboxFigure",
1500
1843
  "lightboxStage": "Yvqh9W_lightboxStage"
1501
1844
  };
1502
1845
  //#endregion
@@ -1604,10 +1947,15 @@ window.__ModuleLoader__.load({
1604
1947
  function useElapsed(running, startedAt) {
1605
1948
  const [elapsed, setElapsed] = (0, react.useState)(0);
1606
1949
  (0, react.useEffect)(() => {
1607
- if (!running || startedAt === null) return;
1608
- const timer = window.setInterval(() => {
1950
+ if (!running || startedAt === null) {
1951
+ setElapsed(0);
1952
+ return;
1953
+ }
1954
+ const update = () => {
1609
1955
  setElapsed(Math.max(1, Math.round((Date.now() - startedAt) / 1e3)));
1610
- }, 1e3);
1956
+ };
1957
+ update();
1958
+ const timer = window.setInterval(update, 1e3);
1611
1959
  return () => window.clearInterval(timer);
1612
1960
  }, [running, startedAt]);
1613
1961
  return elapsed;
@@ -1649,28 +1997,31 @@ window.__ModuleLoader__.load({
1649
1997
  const { api, scope } = props;
1650
1998
  const config = useConfig(scope);
1651
1999
  const enabled = config?.enabled ?? true;
1652
- const configured = (config?.apiUrl ?? "").trim() !== "";
1653
- 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");
1654
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;
1655
2008
  const connected = enabled && configured && apiKeySet;
1656
- const imageModels = normalizeImageModels(config?.imageModels);
1657
2009
  const [tab, setTab] = (0, react.useState)("text");
1658
2010
  const [prompt, setPrompt] = (0, react.useState)("");
1659
2011
  const [size, setSize] = (0, react.useState)("auto");
1660
2012
  const [quality, setQuality] = (0, react.useState)("auto");
1661
2013
  const [count, setCount] = (0, react.useState)(1);
1662
2014
  const [detail, setDetail] = (0, react.useState)("");
1663
- const [model, setModel] = (0, react.useState)(DEFAULT_IMAGE_MODELS[0]);
2015
+ const [model, setModel] = (0, react.useState)("");
1664
2016
  const [compareEnabled, setCompareEnabled] = (0, react.useState)(false);
1665
- const [compareModels, setCompareModels] = (0, react.useState)([...DEFAULT_IMAGE_MODELS]);
2017
+ const [compareModels, setCompareModels] = (0, react.useState)([]);
1666
2018
  const [modelOpen, setModelOpen] = (0, react.useState)(false);
1667
2019
  const [refImage, setRefImage] = (0, react.useState)(null);
1668
2020
  const [images, setImages] = (0, react.useState)([]);
1669
2021
  const [error, setError] = (0, react.useState)(null);
1670
- const [generating, setGenerating] = (0, react.useState)(false);
2022
+ const [submitting, setSubmitting] = (0, react.useState)(false);
1671
2023
  const [enhancing, setEnhancing] = (0, react.useState)(false);
1672
2024
  const [configGuide, setConfigGuide] = (0, react.useState)(null);
1673
- const [startedAt, setStartedAt] = (0, react.useState)(null);
1674
2025
  const [history, setHistory] = (0, react.useState)([]);
1675
2026
  const [viewingHistoryId, setViewingHistoryId] = (0, react.useState)(null);
1676
2027
  const [gallery, setGallery] = (0, react.useState)([]);
@@ -1705,7 +2056,10 @@ window.__ModuleLoader__.load({
1705
2056
  const [comparisonFullscreen, setComparisonFullscreen] = (0, react.useState)(false);
1706
2057
  const fileInput = (0, react.useRef)(null);
1707
2058
  const previewStage = (0, react.useRef)(null);
1708
- 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);
1709
2063
  (0, react.useEffect)(() => {
1710
2064
  setModel((previous) => imageModels.includes(previous) ? previous : imageModels[0]);
1711
2065
  setCompareModels((previous) => {
@@ -1854,7 +2208,7 @@ window.__ModuleLoader__.load({
1854
2208
  };
1855
2209
  /** Run one generation. */
1856
2210
  const handleGenerate = async () => {
1857
- if (generating) return;
2211
+ if (submitting) return;
1858
2212
  if (!enabled) {
1859
2213
  openSettingsGuide("disabled");
1860
2214
  return;
@@ -1880,10 +2234,12 @@ window.__ModuleLoader__.load({
1880
2234
  quality,
1881
2235
  n: count,
1882
2236
  detail,
2237
+ ...defaultChannelId !== void 0 ? { channelId: defaultChannelId } : {},
1883
2238
  ...tab === "edit" && refImage !== null ? { image: refImage.dataUrl } : {},
1884
2239
  ...tab === "edit" && refImage !== null ? { refName: refImage.name } : {}
1885
2240
  };
1886
2241
  setError(null);
2242
+ setSubmitting(true);
1887
2243
  try {
1888
2244
  const targetModels = (compareEnabled ? compareModels : [request.model]).filter((candidate) => imageModels.includes(candidate));
1889
2245
  if (targetModels.length === 0) {
@@ -1901,6 +2257,8 @@ window.__ModuleLoader__.load({
1901
2257
  } : null);
1902
2258
  } catch (caught) {
1903
2259
  setError(errorMessage(caught));
2260
+ } finally {
2261
+ setSubmitting(false);
1904
2262
  }
1905
2263
  };
1906
2264
  /** Open the full-screen image preview at a given index. */
@@ -2163,7 +2521,7 @@ window.__ModuleLoader__.load({
2163
2521
  anchor.click();
2164
2522
  });
2165
2523
  };
2166
- const generateDisabled = generating;
2524
+ const generateDisabled = submitting;
2167
2525
  const viewingEntry = viewingHistoryId === null ? null : history.find((entry) => entry.id === viewingHistoryId) ?? null;
2168
2526
  const viewingGalleryEntry = galleryViewingId === null ? null : gallery.find((entry) => entry.id === galleryViewingId) ?? null;
2169
2527
  const previewImage = preview === null ? null : preview.images[preview.index] ?? null;
@@ -2609,7 +2967,7 @@ window.__ModuleLoader__.load({
2609
2967
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2610
2968
  type: "button",
2611
2969
  className: panel_module_css_default.modelSelect,
2612
- disabled: generating,
2970
+ disabled: submitting,
2613
2971
  "aria-haspopup": "listbox",
2614
2972
  "aria-expanded": modelOpen,
2615
2973
  onClick: () => {
@@ -2974,7 +3332,7 @@ window.__ModuleLoader__.load({
2974
3332
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("tasks.title") }),
2975
3333
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2976
3334
  className: panel_module_css_default.taskTrayCount,
2977
- children: tasks.filter((task) => task.status === "queued" || task.status === "running").length
3335
+ children: activeTasks.length
2978
3336
  }),
2979
3337
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2980
3338
  className: panel_module_css_default.taskTrayChevron,
@@ -3047,16 +3405,17 @@ window.__ModuleLoader__.load({
3047
3405
  }) : null,
3048
3406
  generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3049
3407
  className: panel_module_css_default.canvasState,
3408
+ "data-generation-state": activeTask?.status ?? "submitting",
3050
3409
  role: "status",
3051
3410
  children: [
3052
3411
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.bigSpinner }),
3053
3412
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3054
3413
  className: panel_module_css_default.canvasStateTitle,
3055
- children: tt("canvas.generating")
3414
+ children: submitting && activeTask === void 0 ? tt("canvas.submitting") : activeTask?.status === "queued" ? tt("canvas.queued") : tt("canvas.generating")
3056
3415
  }),
3057
3416
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3058
3417
  className: panel_module_css_default.canvasStateHint,
3059
- children: tt("canvas.elapsed", { seconds: elapsed })
3418
+ children: activeTask?.status === "queued" ? tt("canvas.queueHint", { count: activeTasks.length }) : tt("canvas.elapsed", { seconds: elapsed })
3060
3419
  })
3061
3420
  ]
3062
3421
  }) : null,
@@ -3980,20 +4339,6 @@ window.__ModuleLoader__.load({
3980
4339
  }
3981
4340
  };
3982
4341
  }
3983
- /** A newline/comma-separated model list, persisted as a normalized string array. */
3984
- function stringListField(field) {
3985
- return {
3986
- field,
3987
- format: (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string").join("\n") : "",
3988
- parse: (text) => {
3989
- const values = [...new Set(text.split(/[\n,]/).map((item) => item.trim()).filter(Boolean))];
3990
- return values.length === 0 ? { kind: "clear" } : {
3991
- kind: "set",
3992
- value: values
3993
- };
3994
- }
3995
- };
3996
- }
3997
4342
  /** A boolean field, edited through true/false draft text. */
3998
4343
  function booleanField(field) {
3999
4344
  return {
@@ -4207,23 +4552,354 @@ window.__ModuleLoader__.load({
4207
4552
  sectionValue(field) {
4208
4553
  return this.snapshotOf().value?.[field];
4209
4554
  }
4210
- baseValue(field) {
4211
- 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();
4212
4724
  }
4213
- userLayer() {
4214
- 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;
4215
4762
  }
4216
- stored(field) {
4217
- const user = this.userLayer();
4218
- 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
+ }
4219
4786
  }
4220
4787
  publish() {
4221
4788
  for (const listener of [...this.listeners]) listener();
4222
4789
  }
4223
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
+ }
4224
4900
  //#endregion
4225
4901
  //#region \0dsh-css:E:\dsh-plugin\src\client\settings-card.module.css.mjs
4226
- 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}";
4227
4903
  const tagId$1 = "@dickpy/dsh-imagegen/settings-card.module.css";
4228
4904
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
4229
4905
  const tag = document.createElement("style");
@@ -4233,69 +4909,118 @@ window.__ModuleLoader__.load({
4233
4909
  document.head.appendChild(tag);
4234
4910
  }
4235
4911
  var settings_card_module_css_default = {
4236
- "label": "i1cc5G_label",
4237
- "body": "i1cc5G_body",
4238
- "readOnly": "i1cc5G_readOnly",
4239
- "notExposed": "i1cc5G_notExposed",
4240
- "modelCandidateList": "i1cc5G_modelCandidateList",
4241
- "name": "i1cc5G_name",
4242
- "input": "i1cc5G_input",
4912
+ "optionalContent": "i1cc5G_optionalContent",
4913
+ "editorSectionHeader": "i1cc5G_editorSectionHeader",
4914
+ "channelDotWarn": "i1cc5G_channelDotWarn",
4915
+ "modelSummary": "i1cc5G_modelSummary",
4916
+ "editorField": "i1cc5G_editorField",
4243
4917
  "modelCandidateLabel": "i1cc5G_modelCandidateLabel",
4244
- "chevron": "i1cc5G_chevron",
4245
- "card": "i1cc5G_card",
4246
- "inputInvalid": "i1cc5G_inputInvalid",
4247
- "sectionDivider": "i1cc5G_sectionDivider",
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",
4248
4926
  "header": "i1cc5G_header",
4249
- "sectionTitle": "i1cc5G_sectionTitle",
4250
- "invalid": "i1cc5G_invalid",
4927
+ "head": "i1cc5G_head",
4928
+ "presetInline": "i1cc5G_presetInline",
4929
+ "detectOk": "i1cc5G_detectOk",
4930
+ "modelCandidate": "i1cc5G_modelCandidate",
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",
4251
4939
  "versionValue": "i1cc5G_versionValue",
4252
- "badges": "i1cc5G_badges",
4253
- "manualModelRow": "i1cc5G_manualModelRow",
4254
- "modelSection": "i1cc5G_modelSection",
4255
- "modelChoices": "i1cc5G_modelChoices",
4256
- "chevronOpen": "i1cc5G_chevronOpen",
4940
+ "sectionHint": "i1cc5G_sectionHint",
4941
+ "modelChip": "i1cc5G_modelChip",
4942
+ "footer": "i1cc5G_footer",
4943
+ "save": "i1cc5G_save",
4257
4944
  "badge": "i1cc5G_badge",
4258
- "pending": "i1cc5G_pending",
4259
- "headText": "i1cc5G_headText",
4260
- "modelFetch": "i1cc5G_modelFetch",
4261
- "hint": "i1cc5G_hint",
4262
- "sectionHeader": "i1cc5G_sectionHeader",
4263
- "modelSummary": "i1cc5G_modelSummary",
4945
+ "presetName": "i1cc5G_presetName",
4946
+ "modelChoices": "i1cc5G_modelChoices",
4947
+ "deleteConfirmText": "i1cc5G_deleteConfirmText",
4948
+ "invalid": "i1cc5G_invalid",
4264
4949
  "inlineDisclosure": "i1cc5G_inlineDisclosure",
4265
- "description": "i1cc5G_description",
4266
- "modelFetchRow": "i1cc5G_modelFetchRow",
4950
+ "modelFetch": "i1cc5G_modelFetch",
4951
+ "pending": "i1cc5G_pending",
4952
+ "textarea": "i1cc5G_textarea",
4267
4953
  "select": "i1cc5G_select",
4268
- "footer": "i1cc5G_footer",
4269
- "save": "i1cc5G_save",
4270
- "versionRow": "i1cc5G_versionRow",
4271
- "field": "i1cc5G_field",
4272
- "sectionHint": "i1cc5G_sectionHint",
4954
+ "channelRow": "i1cc5G_channelRow",
4955
+ "label": "i1cc5G_label",
4273
4956
  "failed": "i1cc5G_failed",
4274
- "head": "i1cc5G_head",
4275
- "optionalContent": "i1cc5G_optionalContent",
4276
- "modelCandidate": "i1cc5G_modelCandidate",
4957
+ "channelHost": "i1cc5G_channelHost",
4958
+ "channelDanger": "i1cc5G_channelDanger",
4959
+ "sectionDivider": "i1cc5G_sectionDivider",
4960
+ "editorBackdrop": "i1cc5G_editorBackdrop",
4961
+ "versionRow": "i1cc5G_versionRow",
4962
+ "hint": "i1cc5G_hint",
4963
+ "modelFetchRow": "i1cc5G_modelFetchRow",
4964
+ "editorTools": "i1cc5G_editorTools",
4965
+ "editorFooter": "i1cc5G_editorFooter",
4277
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",
4975
+ "chevron": "i1cc5G_chevron",
4278
4976
  "textareaInvalid": "i1cc5G_textareaInvalid",
4279
- "reset": "i1cc5G_reset",
4280
- "modelChip": "i1cc5G_modelChip",
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",
4994
+ "modelCandidateList": "i1cc5G_modelCandidateList",
4995
+ "channelBadge": "i1cc5G_channelBadge",
4996
+ "description": "i1cc5G_description",
4997
+ "channelMain": "i1cc5G_channelMain",
4998
+ "chevronOpen": "i1cc5G_chevronOpen",
4281
4999
  "disclosure": "i1cc5G_disclosure",
4282
- "versionLabel": "i1cc5G_versionLabel",
4283
- "textarea": "i1cc5G_textarea",
4284
- "addModel": "i1cc5G_addModel"
5000
+ "channelDotReady": "i1cc5G_channelDotReady",
5001
+ "headText": "i1cc5G_headText",
5002
+ "modelRowRemove": "i1cc5G_modelRowRemove"
4285
5003
  };
4286
5004
  //#endregion
4287
5005
  //#region src/client/SettingsCard.tsx
4288
5006
  /**
4289
- * The dsh-imagegen settings card: api_url, api_key (secret, display-only
4290
- * "set" state), and the plugin switches. Registers into the official
4291
- * `settings.plugin.item` slot (the Settings Plugins Configurable tab),
4292
- * independent of the dsh-web-ui family group, bound to the plugin's own
4293
- * 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.
4294
5018
  */
4295
- /** Bridges the imagegen scope onto the card's staged form. */
5019
+ /** Bridges the imagegen scope onto the card's staged forms. */
4296
5020
  var ImageGenSettingsCardController = class {
4297
5021
  scope;
4298
5022
  form;
5023
+ channelsForm;
4299
5024
  /** @param scope - the bound bridge scope for the dsh-imagegen namespace. */
4300
5025
  constructor(scope) {
4301
5026
  this.scope = scope;
@@ -4303,23 +5028,21 @@ window.__ModuleLoader__.load({
4303
5028
  booleanField("enabled"),
4304
5029
  booleanField("announceToAgent"),
4305
5030
  booleanField("allowAgentImageGeneration"),
4306
- textField("apiUrl"),
4307
- secretField("apiKey"),
4308
- stringListField("imageModels"),
4309
5031
  textField("promptApiUrl"),
4310
5032
  secretField("promptApiKey"),
4311
5033
  textField("promptModel")
4312
- ], { secretSettled: () => this.scope.getKeySetSnapshot() });
5034
+ ], { secretSettled: () => this.scope.getSecretSetSnapshot("promptApiKey") });
5035
+ this.channelsForm = new ChannelsForm(scope);
4313
5036
  }
4314
5037
  projection() {
5038
+ const shell = this.form.shell();
4315
5039
  return {
4316
- ...this.form.shell(),
5040
+ ...shell,
5041
+ dirty: shell.dirty || this.channelsForm.snapshot().dirty,
5042
+ channels: this.channelsForm.snapshot(),
4317
5043
  enabled: this.form.field("enabled"),
4318
5044
  announceToAgent: this.form.field("announceToAgent"),
4319
5045
  allowAgentImageGeneration: this.form.field("allowAgentImageGeneration"),
4320
- apiUrl: this.form.field("apiUrl"),
4321
- apiKey: this.form.field("apiKey"),
4322
- imageModels: this.form.field("imageModels"),
4323
5046
  promptApiUrl: this.form.field("promptApiUrl"),
4324
5047
  promptApiKey: this.form.field("promptApiKey"),
4325
5048
  promptModel: this.form.field("promptModel")
@@ -4327,19 +5050,16 @@ window.__ModuleLoader__.load({
4327
5050
  }
4328
5051
  /**
4329
5052
  * Build the face the card's slot registration injects.
4330
- * @returns the card's snapshot, the key-set flag, and the form actions.
5053
+ * @returns the card's snapshot and the form/channel actions.
4331
5054
  */
4332
5055
  inject() {
4333
5056
  const cardStore = this.form.bind(() => this.projection());
4334
- const keySetStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.scope.getKeySetSnapshot());
4335
- this.scope.subscribeKeySet(() => {
4336
- keySetStore.set(this.scope.getKeySetSnapshot());
5057
+ this.channelsForm.subscribe(() => {
5058
+ cardStore.set(this.projection());
4337
5059
  });
4338
5060
  return {
4339
- hooks: {
4340
- imageGenSettingsCard: cardStore,
4341
- imageGenKeySet: keySetStore
4342
- },
5061
+ hooks: { imageGenSettingsCard: cardStore },
5062
+ channels: this.channelsForm.actions(),
4343
5063
  ...this.form.actions()
4344
5064
  };
4345
5065
  }
@@ -4352,24 +5072,35 @@ window.__ModuleLoader__.load({
4352
5072
  function ImageGenSettingsCard(props) {
4353
5073
  const { t } = props;
4354
5074
  const state = props.useImageGenSettingsCard((snapshot) => snapshot);
4355
- const keySet = props.useImageGenKeySet((snapshot) => snapshot);
4356
5075
  const [open, setOpen] = (0, react.useState)(false);
4357
5076
  const [promptModels, setPromptModels] = (0, react.useState)([]);
4358
5077
  const [loadingPromptModels, setLoadingPromptModels] = (0, react.useState)(false);
4359
5078
  const [promptModelsError, setPromptModelsError] = (0, react.useState)(null);
4360
- const [imageModelCandidates, setImageModelCandidates] = (0, react.useState)([]);
4361
- const [loadingImageModels, setLoadingImageModels] = (0, react.useState)(false);
4362
- const [imageModelsError, setImageModelsError] = (0, react.useState)(null);
4363
- const [manualModelsOpen, setManualModelsOpen] = (0, react.useState)(false);
4364
- const [manualModel, setManualModel] = (0, react.useState)("");
4365
- const [enhancementOpen, setEnhancementOpen] = (0, react.useState)(false);
4366
5079
  const [manualPromptModelOpen, setManualPromptModelOpen] = (0, react.useState)(false);
4367
5080
  const [manualPromptModel, setManualPromptModel] = (0, react.useState)("");
5081
+ const [enhancementOpen, setEnhancementOpen] = (0, react.useState)(false);
4368
5082
  const [promptApiOpen, setPromptApiOpen] = (0, react.useState)(false);
4369
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]);
4370
5101
  if (!state.available) return null;
4371
5102
  const title = t("settings.title");
4372
- const blocked = !state.dirty || state.invalid || state.saving;
5103
+ const blocked = !state.dirty || state.invalid || state.saving || state.channels.saving;
4373
5104
  const disabled = !state.writable;
4374
5105
  const fieldProps = {
4375
5106
  overriddenLabel: t("settings.overridden"),
@@ -4409,454 +5140,1053 @@ window.__ModuleLoader__.load({
4409
5140
  })
4410
5141
  }) : null]
4411
5142
  });
5143
+ const channels = state.channels.channels;
5144
+ const editing = editingId === null ? void 0 : channels.find((channel) => channel.id === editingId);
4412
5145
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
4413
5146
  className: settings_card_module_css_default.card,
4414
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4415
- type: "button",
4416
- className: settings_card_module_css_default.header,
4417
- "aria-expanded": open,
4418
- "aria-label": `${t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
4419
- onClick: () => {
4420
- setOpen(!open);
4421
- },
4422
- children: [
4423
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4424
- className: settings_card_module_css_default.headText,
4425
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4426
- className: settings_card_module_css_default.name,
4427
- children: title
4428
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4429
- className: settings_card_module_css_default.description,
4430
- children: t("settings.description")
4431
- })]
4432
- }),
4433
- state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4434
- className: settings_card_module_css_default.pending,
4435
- children: t("settings.unsaved")
4436
- }) : null,
4437
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4438
- className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron,
4439
- children: "▾"
4440
- })
4441
- ]
4442
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4443
- className: settings_card_module_css_default.body,
4444
- children: [
4445
- !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4446
- className: settings_card_module_css_default.readOnly,
4447
- role: "status",
4448
- children: t("settings.readOnly")
4449
- }) : null,
4450
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4451
- id: "dsh-imagegen-settings-apiurl",
4452
- label: t("settings.apiUrl"),
4453
- hint: t("settings.apiUrlHint"),
4454
- placeholder: "https://api.openai.com/v1",
4455
- ...fieldProps,
4456
- ...state.apiUrl,
4457
- onEdit: (text) => {
4458
- props.edit("apiUrl", text);
4459
- },
4460
- onReset: () => {
4461
- props.resetField("apiUrl");
4462
- }
4463
- }),
4464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4465
- id: "dsh-imagegen-settings-apikey",
4466
- label: t("settings.apiKey"),
4467
- hint: keySet ? t("settings.apiKeySet") : t("settings.apiKeyHint"),
4468
- placeholder: "sk-…",
4469
- secret: true,
4470
- ...fieldProps,
4471
- ...state.apiKey,
4472
- overridden: false,
4473
- onEdit: (text) => {
4474
- props.edit("apiKey", text);
4475
- },
4476
- onReset: () => {
4477
- props.resetField("apiKey");
4478
- },
4479
- clearLabel: t("settings.apiKeyClear"),
4480
- onClear: () => {
4481
- props.resetField("apiKey");
4482
- },
4483
- canClear: keySet
4484
- }),
4485
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: settings_card_module_css_default.sectionDivider }),
4486
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4487
- className: settings_card_module_css_default.modelSection,
4488
- "aria-label": t("settings.imageModelsTitle"),
4489
- children: [
4490
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4491
- className: settings_card_module_css_default.sectionHeader,
4492
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4493
- className: settings_card_module_css_default.sectionTitle,
4494
- children: t("settings.imageModelsTitle")
4495
- }), /* @__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", {
4496
5383
  className: settings_card_module_css_default.sectionHint,
4497
- children: t("settings.imageModelsHint")
4498
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4499
- type: "button",
4500
- className: settings_card_module_css_default.modelFetch,
4501
- disabled: disabled || loadingImageModels,
4502
- onClick: () => {
4503
- setLoadingImageModels(true);
4504
- setImageModelsError(null);
4505
- fetch(IMAGE_MODEL_API.models, { method: "POST" }).then(async (response) => {
4506
- const body = await response.json();
4507
- if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
4508
- setImageModelCandidates(body.models ?? []);
4509
- }).catch((error) => {
4510
- setImageModelsError(error instanceof Error ? error.message : String(error));
4511
- }).finally(() => {
4512
- setLoadingImageModels(false);
4513
- });
4514
- },
4515
- children: loadingImageModels ? t("settings.imageModelsLoading") : t("settings.imageModelsFetch")
4516
- })]
4517
- }),
4518
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4519
- className: settings_card_module_css_default.modelSummary,
4520
- children: [splitModels(state.imageModels.text).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4521
- className: settings_card_module_css_default.modelChip,
4522
- 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", {
4523
5395
  type: "button",
4524
- disabled,
4525
- "aria-label": `${t("settings.removeModel")}: ${model}`,
5396
+ className: settings_card_module_css_default.modelFetch,
5397
+ disabled: disabled || loadingPromptModels,
4526
5398
  onClick: () => {
4527
- 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
+ });
4528
5410
  },
4529
- children: "×"
5411
+ children: loadingPromptModels ? t("settings.promptModelsLoading") : t("settings.promptModelsFetch")
4530
5412
  })]
4531
- }, model)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4532
- type: "button",
4533
- className: settings_card_module_css_default.addModel,
4534
- disabled,
4535
- onClick: () => {
4536
- setManualModelsOpen((open) => !open);
4537
- },
4538
- children: manualModelsOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4539
- })]
4540
- }),
4541
- manualModelsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4542
- className: settings_card_module_css_default.manualModelRow,
4543
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4544
- className: settings_card_module_css_default.input,
4545
- value: manualModel,
4546
- placeholder: t("settings.addModelPlaceholder"),
4547
- disabled,
4548
- onChange: (event) => {
4549
- setManualModel(event.target.value);
4550
- },
4551
- onKeyDown: (event) => {
4552
- if (event.key !== "Enter") return;
4553
- event.preventDefault();
4554
- const next = manualModel.trim();
4555
- if (next === "") return;
4556
- props.edit("imageModels", [...splitModels(state.imageModels.text), next].join("\n"));
4557
- setManualModel("");
4558
- }
4559
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4560
- type: "button",
4561
- className: settings_card_module_css_default.addModel,
4562
- disabled: disabled || manualModel.trim() === "",
4563
- onClick: () => {
4564
- props.edit("imageModels", [...splitModels(state.imageModels.text), manualModel.trim()].join("\n"));
4565
- setManualModel("");
4566
- },
4567
- children: t("settings.addModelConfirm")
4568
- })]
4569
- }) : null,
4570
- imageModelCandidates.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4571
- className: settings_card_module_css_default.modelCandidateList,
4572
- role: "group",
4573
- "aria-label": t("settings.imageModelsCandidates"),
4574
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4575
- className: settings_card_module_css_default.modelCandidateLabel,
4576
- children: t("settings.imageModelsCandidates")
4577
- }), imageModelCandidates.map((candidate) => {
4578
- const selected = splitModels(state.imageModels.text).includes(candidate);
4579
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4580
- className: settings_card_module_css_default.modelCandidate,
4581
- "data-selected": selected ? "" : void 0,
4582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4583
- type: "checkbox",
4584
- 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",
4585
5420
  disabled,
4586
- onChange: () => {
4587
- const selectedModels = splitModels(state.imageModels.text);
4588
- const next = selected ? selectedModels.filter((model) => model !== candidate) : [...selectedModels, candidate];
4589
- props.edit("imageModels", next.join("\n"));
4590
- }
4591
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: candidate })]
4592
- }, candidate);
4593
- })]
4594
- }) : null,
4595
- imageModelsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4596
- className: settings_card_module_css_default.failed,
4597
- role: "status",
4598
- children: imageModelsError
4599
- }) : null
4600
- ]
4601
- }),
4602
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4603
- type: "button",
4604
- className: settings_card_module_css_default.disclosure,
4605
- "aria-expanded": enhancementOpen,
4606
- onClick: () => {
4607
- setEnhancementOpen((open) => !open);
4608
- },
4609
- children: [
4610
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptEnhanceTitle") }),
4611
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.optional") }),
4612
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4613
- "aria-hidden": "true",
4614
- children: enhancementOpen ? "⌃" : "⌄"
4615
- })
4616
- ]
4617
- }),
4618
- enhancementOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4619
- className: settings_card_module_css_default.optionalContent,
4620
- "aria-label": t("settings.promptEnhanceTitle"),
4621
- children: [
4622
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4623
- className: settings_card_module_css_default.sectionHint,
4624
- children: t("settings.promptEnhanceHint")
4625
- }),
4626
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4627
- className: settings_card_module_css_default.sectionHeader,
4628
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4629
- className: settings_card_module_css_default.sectionTitle,
4630
- children: t("settings.promptModel")
4631
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4632
- className: settings_card_module_css_default.sectionHint,
4633
- children: t("settings.promptModelDetectionHint")
4634
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4635
- type: "button",
4636
- className: settings_card_module_css_default.modelFetch,
4637
- disabled: disabled || loadingPromptModels,
4638
- onClick: () => {
4639
- setLoadingPromptModels(true);
4640
- setPromptModelsError(null);
4641
- fetch(PROMPT_ENHANCE_API.models, { method: "POST" }).then(async (response) => {
4642
- const body = await response.json();
4643
- if (!response.ok || body.ok !== true) throw new Error(body.message ?? `HTTP ${response.status}`);
4644
- setPromptModels(body.models ?? []);
4645
- }).catch((error) => {
4646
- setPromptModelsError(error instanceof Error ? error.message : String(error));
4647
- }).finally(() => {
4648
- setLoadingPromptModels(false);
4649
- });
4650
- },
4651
- children: loadingPromptModels ? t("settings.promptModelsLoading") : t("settings.promptModelsFetch")
4652
- })]
4653
- }),
4654
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4655
- className: settings_card_module_css_default.modelSummary,
4656
- children: [state.promptModel.text.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4657
- className: settings_card_module_css_default.modelChip,
4658
- 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", {
4659
5428
  type: "button",
5429
+ className: settings_card_module_css_default.addModel,
4660
5430
  disabled,
4661
- "aria-label": `${t("settings.removeModel")}: ${state.promptModel.text}`,
4662
5431
  onClick: () => {
4663
- props.edit("promptModel", "");
5432
+ setManualPromptModelOpen((open) => !open);
5433
+ setEnhancementOpen(true);
4664
5434
  },
4665
- children: "×"
5435
+ children: manualPromptModelOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4666
5436
  })]
4667
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4668
- type: "button",
4669
- className: settings_card_module_css_default.addModel,
4670
- disabled,
4671
- onClick: () => {
4672
- setManualPromptModelOpen((open) => !open);
4673
- },
4674
- children: manualPromptModelOpen ? t("settings.cancelAddModel") : t("settings.addModel")
4675
- })]
4676
- }),
4677
- manualPromptModelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4678
- className: settings_card_module_css_default.manualModelRow,
4679
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4680
- className: settings_card_module_css_default.input,
4681
- value: manualPromptModel,
4682
- placeholder: t("settings.addPromptModelPlaceholder"),
4683
- disabled,
4684
- onChange: (event) => {
4685
- setManualPromptModel(event.target.value);
4686
- }
4687
- }), /* @__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", {
4688
5485
  type: "button",
4689
- className: settings_card_module_css_default.addModel,
4690
- disabled: disabled || manualPromptModel.trim() === "",
5486
+ className: settings_card_module_css_default.inlineDisclosure,
5487
+ "aria-expanded": promptApiOpen,
4691
5488
  onClick: () => {
4692
- props.edit("promptModel", manualPromptModel);
4693
- setManualPromptModel("");
5489
+ setPromptApiOpen((open) => !open);
4694
5490
  },
4695
- children: t("settings.addModelConfirm")
4696
- })]
4697
- }) : null,
4698
- promptModels.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4699
- className: settings_card_module_css_default.modelCandidateList,
4700
- role: "radiogroup",
4701
- "aria-label": t("settings.promptModelsCandidates"),
4702
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4703
- className: settings_card_module_css_default.modelCandidateLabel,
4704
- children: t("settings.promptModelsCandidates")
4705
- }), promptModels.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4706
- type: "button",
4707
- role: "radio",
4708
- className: settings_card_module_css_default.modelCandidate,
4709
- "aria-checked": state.promptModel.text === candidate,
4710
- "data-selected": state.promptModel.text === candidate ? "" : void 0,
4711
- disabled,
4712
- onClick: () => {
4713
- 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);
4714
5556
  },
4715
- children: candidate
4716
- }, candidate))]
4717
- }) : null,
4718
- promptModelsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4719
- className: settings_card_module_css_default.failed,
4720
- role: "status",
4721
- children: promptModelsError
4722
- }) : null,
4723
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4724
- type: "button",
4725
- className: settings_card_module_css_default.inlineDisclosure,
4726
- "aria-expanded": promptApiOpen,
4727
- onClick: () => {
4728
- setPromptApiOpen((open) => !open);
4729
- },
4730
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.promptApiAdvanced") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4731
- "aria-hidden": "true",
4732
- children: promptApiOpen ? "⌃" : "⌄"
4733
- })]
4734
- }),
4735
- promptApiOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4736
- className: settings_card_module_css_default.optionalContent,
4737
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4738
- id: "dsh-imagegen-settings-prompt-apiurl",
4739
- label: t("settings.promptApiUrl"),
4740
- hint: t("settings.promptApiUrlHint"),
4741
- 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"),
4742
5568
  ...fieldProps,
4743
- ...state.promptApiUrl,
5569
+ ...state.announceToAgent,
4744
5570
  onEdit: (text) => {
4745
- props.edit("promptApiUrl", text);
5571
+ props.edit("announceToAgent", text);
4746
5572
  },
4747
5573
  onReset: () => {
4748
- props.resetField("promptApiUrl");
5574
+ props.resetField("announceToAgent");
4749
5575
  }
4750
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
4751
- id: "dsh-imagegen-settings-prompt-apikey",
4752
- label: t("settings.promptApiKey"),
4753
- hint: t("settings.promptApiKeyHint"),
4754
- placeholder: "sk-…",
4755
- 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"),
4756
5584
  ...fieldProps,
4757
- ...state.promptApiKey,
4758
- overridden: false,
5585
+ ...state.allowAgentImageGeneration,
4759
5586
  onEdit: (text) => {
4760
- props.edit("promptApiKey", text);
5587
+ props.edit("allowAgentImageGeneration", text);
4761
5588
  },
4762
5589
  onReset: () => {
4763
- props.resetField("promptApiKey");
5590
+ props.resetField("allowAgentImageGeneration");
4764
5591
  }
4765
- })]
4766
- }) : null
4767
- ]
4768
- }) : null,
4769
- /* @__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", {
4770
5728
  type: "button",
4771
- className: settings_card_module_css_default.disclosure,
4772
- "aria-expanded": moreOpen,
5729
+ className: settings_card_module_css_default.presetRow,
5730
+ disabled: props.disabled,
4773
5731
  onClick: () => {
4774
- setMoreOpen((open) => !open);
5732
+ props.onPick(preset);
4775
5733
  },
4776
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.moreOptions") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4777
- "aria-hidden": "true",
4778
- 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")
4779
5969
  })]
4780
5970
  }),
4781
- moreOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4782
- className: settings_card_module_css_default.optionalContent,
4783
- children: [
4784
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4785
- id: "dsh-imagegen-settings-enabled",
4786
- label: t("settings.enabled"),
4787
- hint: t("settings.enabledHint"),
4788
- inheritLabel: t("settings.inherit"),
4789
- onLabel: t("settings.on"),
4790
- offLabel: t("settings.off"),
4791
- ...fieldProps,
4792
- ...state.enabled,
4793
- onEdit: (text) => {
4794
- props.edit("enabled", text);
4795
- },
4796
- onReset: () => {
4797
- props.resetField("enabled");
4798
- }
4799
- }),
4800
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4801
- id: "dsh-imagegen-settings-announce",
4802
- label: t("settings.announceToAgent"),
4803
- hint: t("settings.announceToAgentHint"),
4804
- inheritLabel: t("settings.inherit"),
4805
- onLabel: t("settings.on"),
4806
- offLabel: t("settings.off"),
4807
- ...fieldProps,
4808
- ...state.announceToAgent,
4809
- onEdit: (text) => {
4810
- 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);
4811
6065
  },
4812
- onReset: () => {
4813
- props.resetField("announceToAgent");
6066
+ onKeyDown: (event) => {
6067
+ if (event.key === "Enter") {
6068
+ event.preventDefault();
6069
+ addManual();
6070
+ }
4814
6071
  }
4815
- }),
4816
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
4817
- id: "dsh-imagegen-settings-agent-generation",
4818
- label: t("settings.allowAgentImageGeneration"),
4819
- hint: t("settings.allowAgentImageGenerationHint"),
4820
- inheritLabel: t("settings.inherit"),
4821
- onLabel: t("settings.on"),
4822
- offLabel: t("settings.off"),
4823
- ...fieldProps,
4824
- ...state.allowAgentImageGeneration,
4825
- onEdit: (text) => {
4826
- 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);
4827
6087
  },
4828
- onReset: () => {
4829
- props.resetField("allowAgentImageGeneration");
4830
- }
4831
- })
4832
- ]
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
+ })]
4833
6147
  }) : null,
6148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: settings_card_module_css_default.editorDivider }),
4834
6149
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4835
- className: settings_card_module_css_default.footer,
6150
+ className: settings_card_module_css_default.editorFooter,
4836
6151
  children: [
4837
- state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4838
- className: settings_card_module_css_default.failed,
4839
- role: "status",
4840
- children: t("settings.saveFailed")
4841
- }) : null,
4842
- /* @__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", {
4843
6157
  type: "button",
4844
- className: settings_card_module_css_default.discard,
4845
- disabled: !state.dirty || state.saving,
4846
- onClick: props.discard,
4847
- 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")
4848
6162
  }),
4849
- /* @__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", {
4850
6178
  type: "button",
4851
- className: settings_card_module_css_default.save,
4852
- disabled: blocked,
4853
- onClick: props.save,
4854
- 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")
4855
6185
  })
4856
6186
  ]
4857
6187
  })
4858
6188
  ]
4859
- }) : null]
6189
+ })
4860
6190
  });
4861
6191
  }
4862
6192
  /** A staged value field; `secret` renders a password control. */
@@ -4894,17 +6224,7 @@ window.__ModuleLoader__.load({
4894
6224
  }) : null
4895
6225
  ]
4896
6226
  }),
4897
- props.multiline === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4898
- id: props.id,
4899
- className: props.invalid ? settings_card_module_css_default.textareaInvalid : settings_card_module_css_default.textarea,
4900
- ...props.invalid ? { "aria-invalid": true } : {},
4901
- value: props.text,
4902
- placeholder: props.placeholder ?? "",
4903
- disabled: props.disabled,
4904
- onChange: (event) => {
4905
- props.onEdit(event.target.value);
4906
- }
4907
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4908
6228
  id: props.id,
4909
6229
  className: props.invalid ? settings_card_module_css_default.inputInvalid : settings_card_module_css_default.input,
4910
6230
  type: props.secret === true ? "password" : "text",
@@ -4924,9 +6244,6 @@ window.__ModuleLoader__.load({
4924
6244
  ]
4925
6245
  });
4926
6246
  }
4927
- function splitModels(value) {
4928
- return [...new Set(value.split(/[\n,]/).map((model) => model.trim()).filter(Boolean))];
4929
- }
4930
6247
  /** A staged boolean field: 继承 / 开 / 关. */
4931
6248
  function BooleanField(props) {
4932
6249
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4983,201 +6300,6 @@ window.__ModuleLoader__.load({
4983
6300
  });
4984
6301
  }
4985
6302
  //#endregion
4986
- //#region src/client/settings-scope.ts
4987
- /**
4988
- * Browser-side settings scope for the dsh-imagegen namespace, served by the
4989
- * plugin's own loopback bridge routes (/api/dsh-imagegen/settings). The
4990
- * official rc.6 settings scope answers "unavailable" for every third-party
4991
- * namespace (the host-apiproxy allowlist is hard-coded), so this package
4992
- * re-serves its namespace through the host settings seam over a same-origin,
4993
- * loopback-only HTTP pair — the same pattern the dsh-web-ui family bridge
4994
- * uses, self-contained per plugin.
4995
- */
4996
- /** Settings wire face over the bridge routes (fetch-backed). */
4997
- function createBridgeApi(fetchFn) {
4998
- const post = async (path, body) => {
4999
- try {
5000
- const response = await fetchFn(path, {
5001
- method: "POST",
5002
- headers: { "content-type": "application/json" },
5003
- body: JSON.stringify(body)
5004
- });
5005
- if (!response.ok) return { result: {
5006
- ok: false,
5007
- code: "internal",
5008
- message: `bridge HTTP ${response.status}`
5009
- } };
5010
- return { result: await response.json() };
5011
- } catch {
5012
- return { result: {
5013
- ok: false,
5014
- code: "internal",
5015
- message: "settings bridge unreachable"
5016
- } };
5017
- }
5018
- };
5019
- return { settings: {
5020
- describe: async (payload) => post(SETTINGS_API.describe, payload),
5021
- mutate: async (payload) => post(SETTINGS_API.mutate, payload)
5022
- } };
5023
- }
5024
- /**
5025
- * A SettingsScope over the bridge face: serialized queue, revision-fenced
5026
- * writes, recovery read after a refusal. Mirrors the official controller's
5027
- * ordering but trusts the Host-seam value without re-running the wire-schema
5028
- * validation — the seam already validated it.
5029
- */
5030
- var BridgeScopeController = class {
5031
- api;
5032
- spec;
5033
- store;
5034
- /** Whether the namespace currently holds a stored secret (e.g. apiKey). */
5035
- keySet;
5036
- /** Individual secret presence bits, keyed by the settings field name. */
5037
- secretSets;
5038
- tail = Promise.resolve();
5039
- disposed = false;
5040
- constructor(api, spec) {
5041
- this.api = api;
5042
- this.spec = spec;
5043
- this.store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
5044
- status: "loading",
5045
- value: void 0,
5046
- base: void 0,
5047
- user: void 0,
5048
- revision: void 0,
5049
- writable: false,
5050
- mode: "host"
5051
- });
5052
- this.keySet = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(false);
5053
- this.secretSets = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({});
5054
- }
5055
- getSnapshot() {
5056
- return this.store.getSnapshot();
5057
- }
5058
- /** Whether a stored secret exists (from the redacted view's secrets list). */
5059
- getKeySetSnapshot() {
5060
- return this.keySet.getSnapshot();
5061
- }
5062
- /** Observe the secret-set flag. */
5063
- subscribeKeySet(listener) {
5064
- return this.keySet.subscribe(listener);
5065
- }
5066
- /** Whether a specific secret field currently has a stored value. */
5067
- getSecretSetSnapshot(field) {
5068
- return this.secretSets.getSnapshot()[field] === true;
5069
- }
5070
- /** Observe changes to individual secret-field presence bits. */
5071
- subscribeSecretSets(listener) {
5072
- return this.secretSets.subscribe(listener);
5073
- }
5074
- subscribe(listener) {
5075
- return this.store.subscribe(listener);
5076
- }
5077
- /** Queue a bridge refresh. */
5078
- load() {
5079
- return this.enqueue(() => this.read());
5080
- }
5081
- set(field, value) {
5082
- return this.enqueue(() => this.write({
5083
- op: "set",
5084
- path: [field],
5085
- value
5086
- }));
5087
- }
5088
- unset(field) {
5089
- return this.enqueue(() => this.write({
5090
- op: "unset",
5091
- path: [field]
5092
- }));
5093
- }
5094
- async dispose() {
5095
- this.disposed = true;
5096
- await this.tail;
5097
- }
5098
- enqueue(operation) {
5099
- if (this.disposed) return Promise.resolve();
5100
- const task = this.tail.then(async () => {
5101
- if (this.disposed) return;
5102
- await operation();
5103
- });
5104
- this.tail = task.catch(() => {});
5105
- return task;
5106
- }
5107
- async read() {
5108
- let response;
5109
- try {
5110
- response = await this.api.describe({});
5111
- } catch {
5112
- if (!this.disposed) this.store.update((draft) => {
5113
- draft.status = "unavailable";
5114
- });
5115
- return;
5116
- }
5117
- if (!response.result.ok || this.disposed) {
5118
- if (!this.disposed) this.store.update((draft) => {
5119
- draft.status = "unavailable";
5120
- });
5121
- return;
5122
- }
5123
- const { namespaces, writable } = response.result.value;
5124
- const view = namespaces?.find((candidate) => candidate.ns === this.spec.namespace);
5125
- if (view === void 0) {
5126
- this.store.update((draft) => {
5127
- draft.status = "unavailable";
5128
- draft.writable = writable === true;
5129
- });
5130
- this.keySet.set(false);
5131
- this.secretSets.set({});
5132
- return;
5133
- }
5134
- this.accept(view, writable);
5135
- }
5136
- async write(op) {
5137
- const revision = this.getSnapshot().revision;
5138
- let response;
5139
- try {
5140
- response = await this.api.mutate({
5141
- ns: this.spec.namespace,
5142
- ops: [op],
5143
- ...revision === void 0 ? {} : { expectedRevision: revision }
5144
- });
5145
- } catch {
5146
- await this.read();
5147
- return;
5148
- }
5149
- if (!response.result.ok || this.disposed) {
5150
- await this.read();
5151
- return;
5152
- }
5153
- this.accept(response.result.value, void 0);
5154
- }
5155
- accept(view, writable) {
5156
- this.store.update((draft) => {
5157
- draft.revision = view.revision;
5158
- draft.base = view.base;
5159
- draft.user = view.user;
5160
- if (writable !== void 0) draft.writable = writable;
5161
- draft.status = "ready";
5162
- draft.value = view.value;
5163
- });
5164
- const secretSets = Object.fromEntries((view.secrets ?? []).map((secret) => [secret.path.join("."), secret.set]));
5165
- this.keySet.set(Object.values(secretSets).some(Boolean));
5166
- this.secretSets.set(secretSets);
5167
- }
5168
- };
5169
- /**
5170
- * Bind the dsh-imagegen settings scope over the bridge routes and start its
5171
- * initial read (the caller mounts nothing until the scope settles).
5172
- * @param fetchFn - the fetch implementation (the global fetch on loopback).
5173
- * @returns the scope; unavailable when the bridge is unreachable.
5174
- */
5175
- function bindImageGenScope(fetchFn = fetch) {
5176
- const controller = new BridgeScopeController(createBridgeApi(fetchFn).settings, { namespace: "dsh-imagegen" });
5177
- controller.load();
5178
- return controller;
5179
- }
5180
- //#endregion
5181
6303
  //#region \0dsh-css:E:\dsh-plugin\src\client\image-toolview.module.css.mjs
5182
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)}";
5183
6305
  const tagId = "@dickpy/dsh-imagegen/image-toolview.module.css";
@@ -5190,15 +6312,15 @@ window.__ModuleLoader__.load({
5190
6312
  }
5191
6313
  var image_toolview_module_css_default = {
5192
6314
  "header": "_3eGrYG_header",
5193
- "images": "_3eGrYG_images",
6315
+ "message": "_3eGrYG_message",
6316
+ "status": "_3eGrYG_status",
6317
+ "loading": "_3eGrYG_loading",
5194
6318
  "image": "_3eGrYG_image",
5195
6319
  "imageLink": "_3eGrYG_imageLink",
6320
+ "images": "_3eGrYG_images",
5196
6321
  "icon": "_3eGrYG_icon",
5197
- "loading": "_3eGrYG_loading",
5198
- "root": "_3eGrYG_root",
5199
6322
  "error": "_3eGrYG_error",
5200
- "status": "_3eGrYG_status",
5201
- "message": "_3eGrYG_message"
6323
+ "root": "_3eGrYG_root"
5202
6324
  };
5203
6325
  //#endregion
5204
6326
  //#region src/client/image-toolview.tsx
@@ -5207,7 +6329,8 @@ window.__ModuleLoader__.load({
5207
6329
  }
5208
6330
  function imageRefsOf(block) {
5209
6331
  if (!isSettled(block)) return [];
5210
- 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] : []);
5211
6334
  }
5212
6335
  function textOf(block) {
5213
6336
  if (!isSettled(block)) return "";
@@ -5282,13 +6405,17 @@ window.__ModuleLoader__.load({
5282
6405
  }
5283
6406
  /** Register the inline image result view for all image-generation result tools. */
5284
6407
  function registerImageToolviews(ctx) {
5285
- const sessions = ctx.get("sessions");
5286
- const load = async (sessionId, ref) => {
5287
- const session = sessions?.binding(sessionId)?.session;
5288
- if (session === void 0) throw new Error("当前会话不可用,无法读取图片附件。");
5289
- const result = await session.readAttachment(ref.attachmentId);
5290
- if (!result.ok) throw new Error(result.error.message);
5291
- 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();
5292
6419
  return URL.createObjectURL(blob);
5293
6420
  };
5294
6421
  const ImageToolView = (props) => {