dsh-router-core 0.4.2 → 0.4.4

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.
Files changed (51) hide show
  1. package/README.md +9 -10
  2. package/cordis.patch.yml +10 -0
  3. package/docs/ext.md +64 -2
  4. package/lib/client-registry.js +633 -50
  5. package/lib/client.js +633 -50
  6. package/lib/contract-D4BWZ7Xe.js +13 -0
  7. package/lib/ext-prompt/index.js +527 -0
  8. package/lib/ext-prompt/locale/en.json +6 -0
  9. package/lib/ext-prompt/locale/zh.json +6 -0
  10. package/lib/ext-prompt/package.json +5 -0
  11. package/lib/ext-test/index.js +1 -12
  12. package/lib/index.js +286 -127
  13. package/lib/types/ext/contract.d.ts +113 -0
  14. package/lib/types/ext-prompt/content.d.ts +64 -0
  15. package/lib/types/ext-prompt/index.d.ts +53 -0
  16. package/lib/types/ext-prompt/logo.d.ts +17 -0
  17. package/lib/types/ext-prompt/mount.d.ts +51 -0
  18. package/lib/types/ext-prompt/plugin.d.ts +32 -0
  19. package/lib/types/ext-prompt/render.d.ts +105 -0
  20. package/lib/types/host-version.d.ts +7 -8
  21. package/lib/types/index.d.ts +1 -1
  22. package/lib/types/shared.d.ts +33 -0
  23. package/package.json +17 -7
  24. package/src/client/EndpointTab.tsx +15 -18
  25. package/src/client/ExtDetail.tsx +552 -9
  26. package/src/client/index.tsx +7 -28
  27. package/src/client/router.css +132 -62
  28. package/src/ext/contract.ts +111 -0
  29. package/src/ext-prompt/assembly.test.ts +79 -0
  30. package/src/ext-prompt/content.ts +181 -0
  31. package/src/ext-prompt/ext-prompt.test.ts +432 -0
  32. package/src/ext-prompt/index.ts +104 -0
  33. package/src/ext-prompt/locale/en.json +6 -0
  34. package/src/ext-prompt/locale/zh.json +6 -0
  35. package/src/ext-prompt/logo.svg +14 -0
  36. package/src/ext-prompt/logo.ts +18 -0
  37. package/src/ext-prompt/mount.test.ts +95 -0
  38. package/src/ext-prompt/mount.ts +79 -0
  39. package/src/ext-prompt/plugin.ts +223 -0
  40. package/src/ext-prompt/render.test.ts +256 -0
  41. package/src/ext-prompt/render.ts +216 -0
  42. package/src/ext-prompt/row.json +5 -0
  43. package/src/ext-route.test.ts +78 -0
  44. package/src/host-version.test.ts +21 -28
  45. package/src/host-version.ts +39 -39
  46. package/src/index.ts +216 -94
  47. package/src/llm/adapter.test.ts +19 -42
  48. package/src/llm/adapter.ts +20 -47
  49. package/src/shared.ts +34 -0
  50. package/src/suppliers/registry.test.ts +56 -0
  51. package/src/suppliers/registry.ts +40 -10
@@ -1,4 +1,4 @@
1
- (function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("/* ============================================================\n dsh-router — sidebar entry\n ============================================================ */\n\n.dshr-entry {\n box-sizing: border-box;\n display: flex;\n width: 100%;\n min-height: 36px;\n align-items: center;\n gap: 10px;\n border: none;\n border-radius: 8px;\n padding: 0 10px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-entry:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshr-entry[data-active] {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-active);\n font-weight: 600;\n}\n\n.dshr-entryIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n\n.dshr-entryIcon svg {\n display: block;\n width: 18px;\n height: 18px;\n}\n\n.dshr-entryLabel {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entry {\n width: 36px;\n min-height: 36px;\n justify-content: center;\n margin: 0 auto 12px;\n padding: 0;\n border-radius: 50%;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entryLabel {\n display: none;\n}\n\n/* ============================================================\n dsh-router — 主题变量(设置页 .dshr-settings / 老中心栏 .dshr-shell 共用)\n ============================================================ */\n\n.dshr-settings,\n.dshr-shell {\n --rs-bg: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n --rs-layer-1: var(--dsw-alias-bg-layer-1);\n --rs-layer-2: var(--dsw-alias-bg-layer-2);\n --rs-input: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-2));\n --rs-text: var(--dsw-alias-label-primary);\n --rs-muted: var(--dsw-alias-label-secondary);\n --rs-faint: var(--dsw-alias-label-tertiary);\n --rs-line: var(--dsw-alias-border-l1);\n --rs-line-strong: var(--dsw-alias-border-l2);\n --rs-accent: var(--dsw-alias-state-business-primary);\n --rs-hover: var(--dsw-alias-interactive-bg-hover);\n --rs-danger: var(--dsw-alias-state-error-primary);\n --rs-success: var(--dsw-alias-state-success-primary);\n --rs-ok: var(--rs-success);\n --rs-warn: #9a6a18;\n --rs-mono: var(\n --ds-font-family-code,\n \"SFMono-Regular\",\n Consolas,\n monospace\n );\n --rs-sans: var(\n --dsw-font-family,\n -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif\n );\n box-sizing: border-box;\n min-width: 0;\n color: var(--rs-text);\n font: 13px/1.55 var(--rs-sans);\n -webkit-tap-highlight-color: transparent;\n}\n\n/* ============================================================\n dsh-router — 设置页容器(settings.section 座位)\n ============================================================ */\n\n/* 容器几何(浏览器实测,改之前先量别凭感觉):\n - 内容宽 564px(面板 612 − 左右 padding 24)\n - 外壳 .VOzbGW_options 是 `overflow-y:auto` + `flex:1 1 0%`,高度确定\n (视口 813 时可视高 703~746)\n - 根节点外面套了一层 display:contents(宿主 slot renderer 给的),**没有\n 盒子** —— 但实测 `height:100%` 仍能穿过它解析到外壳高度\n (验过:root 从 1332px 溢出态变成 722px,正好填满容器)\n\n 布局规则:**tab 条固定,只有下面的内容区滚**。\n 所以根节点必须限高(不限高内容会把外壳撑开,变成整页滚),内容区再\n min-height:0 + overflow-y:auto 自己滚。\n 关键字是 min-height:0 —— flex 子项默认 min-height:auto,不给 0 的话\n 子项缩不小,overflow 永远不触发。 */\n.dshr-settings {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after {\n box-sizing: border-box;\n}\n\n/* 主导航 = tab 条。照 DSH 原生的内容区 tab(插件页那套 pbvGtq_tab):\n 容器一条 1px 底部分隔线,激活项用 ::after 画 2px 下划线压在线上。\n 刻意**不用**填充色块——那会跟下面的周期按钮组撞成同一种控件,\n 层级也反了(导航是页面级,周期是筛选级)。 */\n.dshr-tabs {\n display: flex;\n flex: none; /* 固定:内容区滚,tab 条不动 */\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--rs-line);\n /* 右端跟卡片对齐(实测数字,别凭感觉调):\n root = 564;内容区因 padding-right 8px → 内容可视宽 556;\n 卡片再贴着可视区排 → 548。\n tab 条是 .dshr-settings 的直接子项,起点是 root(564),\n 要落到 548 就得让掉 8(padding) + 8(滚动条) = 16px。\n 写成 margin 而不用 padding,是为了不把分隔线画到 padding 区域外。 */\n margin-right: 16px;\n}\n\n.dshr-tab {\n position: relative;\n min-width: 0;\n height: 36px;\n padding: 0 12px;\n border: none;\n border-radius: 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n transition: color 0.12s ease;\n}\n\n.dshr-tab:hover {\n color: var(--rs-text);\n}\n\n/* 激活:主文字色 + 2px 下划线(bottom:-1px 压在容器的分隔线上)。\n 双类是因为上面有 `.dshr-settings button { color: inherit; font: inherit }` (1,1),\n 单类的 .dshr-tab-on 只有 (1,0),颜色和字号会被吃掉。 */\n.dshr-settings .dshr-tab-on {\n color: var(--rs-text);\n font-size: 13px;\n font-weight: 500;\n}\n\n.dshr-settings .dshr-tab-on::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: \"\";\n background: var(--rs-text);\n}\n\n.dshr-tabBack {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-tabBack:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n/* 内容区:吃掉剩余高度并**自己滚**,这样 tab 条永远留在顶部。\n flex:1 + min-height:0 是必须的(见 .dshr-settings 的注释)。 */\n.dshr-settingsBody {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow-y: auto;\n /* 恒定预留滚动条槽位。\n 实测不设它时 clientWidth 在 564(内容短、无滚动条)和 556(内容长、有滚动条)\n 之间跳,卡片宽也跟着 556/548 跳 —— 切 tab 时内容会横向抖一下。\n 不支持 scrollbar-gutter 的浏览器只是退化成「照样占宽」,不会坏。 */\n scrollbar-gutter: stable;\n /* 右内边距:内容别贴到滚动条上。滚动条宽 8px 且占布局宽度\n (offsetWidth 564 vs clientWidth 556),不撑开的话内容右边缘正好压在它上面。 */\n padding-right: 8px;\n}\n\n/* 细滚动条:跟 .dshr-content / .dshr-modalBody 一个套路 */\n.dshr-settingsBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-settingsBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-settingsBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 老的中心栏劫持骨架(settings 不可用时的回退,非主路径)\n ============================================================ */\n\n[data-pane=\"conversation\"] {\n position: relative;\n}\n.dshDesktopConversationSurface {\n position: relative;\n}\n\n[data-dsh-router-view] {\n position: absolute;\n z-index: 60;\n inset: 0;\n display: none;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n}\n\n.dshr-panelView {\n isolation: isolate;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [data-dsh-router-view] {\n display: block;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [data-pane=\"conversation\"]\n > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [class*=\"centerCol\"]\n > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n .dshDesktopConversationSurface\n > :not([data-dsh-router-view]) {\n display: none !important;\n}\n\n.dshr-shell {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--rs-bg);\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after,\n.dshr-shell *,\n.dshr-shell *::before,\n.dshr-shell *::after {\n box-sizing: border-box;\n}\n\n/* 按钮/表单重置 + 聚焦环:两个挂载容器都要覆盖。\n 只写 .dshr-shell 的话设置页整套控件样式全掉(踩过:周期按钮变裸文字)。 */\n.dshr-settings button,\n.dshr-settings input,\n.dshr-settings select,\n.dshr-settings textarea,\n.dshr-shell button,\n.dshr-shell input,\n.dshr-shell select,\n.dshr-shell textarea {\n color: inherit;\n font: inherit;\n}\n\n.dshr-settings button:focus-visible,\n.dshr-settings input:focus-visible,\n.dshr-settings select:focus-visible,\n.dshr-settings textarea:focus-visible,\n.dshr-shell button:focus-visible,\n.dshr-shell input:focus-visible,\n.dshr-shell select:focus-visible,\n.dshr-shell textarea:focus-visible {\n outline: 2px solid var(--rs-accent);\n outline-offset: 2px;\n}\n\n/* Masthead */\n.dshr-masthead {\n display: flex;\n min-height: 56px;\n flex: 0 0 auto;\n align-items: center;\n gap: 16px;\n padding: 0 18px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-brand {\n display: grid;\n min-width: 0;\n flex: 0 1 auto;\n align-content: center;\n gap: 1px 9px;\n}\n\n.dshr-brandTitle {\n overflow: hidden;\n font-size: 15px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-brandSub {\n overflow: hidden;\n color: var(--rs-faint);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-statusCluster {\n display: inline-flex;\n min-width: 0;\n flex: 0 0 auto;\n align-items: center;\n gap: 7px;\n margin-left: auto;\n color: var(--rs-muted);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.dshr-statusEndpoint {\n overflow: hidden;\n max-width: 220px;\n color: var(--rs-faint);\n font: 11px var(--rs-mono);\n text-overflow: ellipsis;\n}\n\n.dshr-headerActions {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n}\n\n.dshr-refreshedAt {\n color: var(--rs-faint);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-iconButton {\n display: inline-flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n cursor: pointer;\n}\n\n.dshr-iconButton:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n.dshr-iconButton:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.dshr-backButton {\n display: inline-flex;\n height: 28px;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-text);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-backButton:hover {\n background: var(--rs-hover);\n}\n.dshr-backButton svg {\n display: block;\n}\n\n/* Alert banner */\n.dshr-alert {\n display: flex;\n flex: 0 0 auto;\n align-items: baseline;\n gap: 10px;\n margin: 12px 18px 0;\n padding: 10px 12px;\n border: 1px solid var(--rs-line-strong);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-alert strong {\n color: var(--rs-danger);\n font-size: 12px;\n}\n.dshr-alert span {\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n/* Nav tabs */\n.dshr-nav {\n display: flex;\n flex: 0 0 auto;\n gap: 2px;\n padding: 0 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-nav button {\n position: relative;\n min-height: 42px;\n padding: 0 12px;\n border: none;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n cursor: pointer;\n}\n\n.dshr-nav button:hover {\n color: var(--rs-text);\n}\n\n.dshr-nav button[aria-current=\"page\"] {\n color: var(--rs-text);\n font-weight: 600;\n}\n\n.dshr-nav button[aria-current=\"page\"]::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: \"\";\n background: var(--rs-accent);\n}\n\n.dshr-navCount {\n display: inline-block;\n margin-left: 5px;\n padding: 0 6px;\n border-radius: 9px;\n color: var(--rs-faint);\n background: var(--rs-layer-2);\n font-size: 11px;\n line-height: 16px;\n}\n\n/* Content */\n.dshr-content {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow: auto;\n}\n\n.dshr-empty {\n display: flex;\n min-height: 120px;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n font-size: 13px;\n}\n\n/* 两行空状态(标题 + 说明)。`.dshr-empty` 本身是横排的单行 flex,直接套两行\n div 会左右排开,所以另给一个纵向堆叠的变体。 */\n.dshr-emptyStack {\n flex-direction: column;\n gap: 6px;\n text-align: center;\n}\n\n/* Accounts table */\n.dshr-tableWrap {\n overflow: auto;\n padding: 12px 18px;\n}\n\n.dshr-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.dshr-table th {\n position: sticky;\n top: 0;\n padding: 8px 10px;\n border-bottom: 1px solid var(--rs-line-strong);\n color: var(--rs-faint);\n background: var(--rs-bg);\n font-size: 11px;\n font-weight: 600;\n text-align: left;\n white-space: nowrap;\n}\n\n.dshr-table td {\n padding: 7px 10px;\n border-bottom: 1px solid var(--rs-line);\n vertical-align: middle;\n}\n\n.dshr-table tbody tr:hover {\n background: var(--rs-hover);\n}\n\n.dshr-mono {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n.dshr-num {\n text-align: right;\n}\n.dshr-muted {\n color: var(--rs-faint);\n}\n.dshr-reason {\n color: var(--rs-muted);\n font-size: 11px;\n}\n\n.dshr-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 9px;\n font-size: 11px;\n line-height: 16px;\n}\n\n.dshr-badge-ok {\n color: var(--rs-success);\n background: color-mix(in srgb, var(--rs-success) 12%, transparent);\n}\n.dshr-badge-warn {\n color: var(--rs-warn);\n background: color-mix(in srgb, var(--rs-warn) 14%, transparent);\n}\n.dshr-badge-danger {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 12%, transparent);\n}\n\n/* Models grid */\n.dshr-modelGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: 10px;\n padding: 14px 18px;\n}\n\n/* 设置页里横向 padding 归零:内容区已经贴着容器边,再加 18px 会把卡片挤成\n 1 列、图表也跟着变窄。上下 padding 保留。 */\n.dshr-settings .dshr-modelGrid,\n.dshr-settings .dshr-tableWrap {\n padding-right: 0;\n padding-left: 0;\n}\n\n.dshr-modelCard {\n display: flex;\n min-width: 0;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-modelCard:hover {\n border-color: var(--rs-line-strong);\n}\n\n.dshr-modelId {\n overflow: hidden;\n font-family: var(--rs-mono);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modelCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n/* ============================================================\n dsh-router — 9router 风格 tab 内容(供应商 / 组合 / 端点与密钥)\n ============================================================ */\n\n/* tab 内容:设置页里**不自己滚** —— 滚动交给 .dshr-settingsBody(tab 条下方\n 那一块)。这里再给 overflow 就会出现嵌套滚动条(内容区一个、tabBody 一个)。\n 老的 .dshr-shell 中心栏挂载方式没有 settingsBody 那层,滚动由它自己承担。 */\n.dshr-tabBody {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 14px;\n padding: 0;\n}\n\n.dshr-shell .dshr-tabBody {\n flex: 1 1 auto;\n min-height: 0;\n padding: 14px 18px;\n overflow-y: auto;\n}\n\n.dshr-card {\n flex: none; /* 不参与 flex 伸缩:高度=内容,避免被压缩裁切 */\n min-width: 0;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n overflow: hidden;\n}\n\n.dshr-cardHead {\n display: flex;\n min-height: 44px;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-cardTitle {\n font-size: 13px;\n font-weight: 650;\n}\n\n.dshr-cardMeta {\n margin-left: auto;\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 组合链 */\n.dshr-comboChain {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-comboStep {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dshr-comboArrow {\n color: var(--rs-faint);\n margin-right: 8px;\n}\n\n.dshr-comboSupplier {\n font-family: var(--rs-mono);\n font-size: 12px;\n}\n\n/* 端点与密钥 */\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px;\n}\n\n.dshr-endpointCode {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-2);\n color: var(--rs-text);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-miniButton {\n display: inline-flex;\n flex: none;\n height: 28px;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-miniButton:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n.dshr-code {\n margin: 0;\n padding: 12px 14px;\n overflow-x: auto;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font: 12px/1.6 var(--rs-mono);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n/* ============================================================\n dsh-router — 供应商详情 / 端点与密钥 / 通用控件\n ============================================================ */\n\n/* 详情页头 */\n.dshr-detailHead {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 36px;\n}\n\n.dshr-detailTitle {\n font-size: 14px;\n font-weight: 650;\n}\n\n/* 可点击卡片 */\n.dshr-card-click {\n cursor: pointer;\n transition: border-color 0.12s ease;\n}\n\n.dshr-card-click:hover {\n border-color: var(--rs-accent);\n}\n\n.dshr-cardHint {\n padding: 0 14px 12px;\n}\n\n/* 供应商小卡片(grid 布局,替代整行大卡片) */\n.dshr-supplierGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));\n gap: 12px;\n}\n\n/* 供应商分组(内置 / 插件分开) */\n.dshr-supplierGroup {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-supplierGroup + .dshr-supplierGroup {\n margin-top: 22px;\n}\n\n.dshr-supplierGroupTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-muted);\n letter-spacing: 0.04em;\n padding-left: 2px;\n}\n\n.dshr-supplierCard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n padding: 16px 18px;\n border: 1px solid var(--rs-line);\n border-radius: 12px;\n background: var(--rs-layer-1);\n cursor: pointer;\n transition: border-color 0.12s ease;\n}\n\n.dshr-supplierCard:hover {\n border-color: var(--rs-accent);\n}\n\n.dshr-supplierName {\n font-size: 14px;\n font-weight: 650;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-supplierRow {\n display: flex;\n align-items: center;\n gap: 9px;\n min-width: 0;\n}\n\n.dshr-supplierIcon {\n width: 24px;\n height: 24px;\n flex: none;\n border-radius: 5px;\n object-fit: contain;\n color: var(--rs-faint);\n}\n\n.dshr-supplierMeta {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-chevron {\n color: var(--rs-faint);\n font-size: 18px;\n line-height: 1;\n margin-left: auto;\n}\n\n/* 登录框 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n background: var(--rs-layer-2);\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 输入框 */\n.dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-text);\n font-size: 12px;\n}\n\n.dshr-input:focus {\n outline: none;\n border-color: var(--rs-accent);\n}\n\n/* 成功提示 */\n.dshr-alert-ok {\n border-color: color-mix(in srgb, var(--rs-ok) 45%, transparent);\n background: color-mix(in srgb, var(--rs-ok) 10%, transparent);\n}\n\n.dshr-createdKey {\n display: block;\n margin: 6px 0;\n padding: 8px 10px;\n border-radius: 6px;\n background: var(--rs-layer-1);\n word-break: break-all;\n}\n\n/* 模型卡片行 */\n.dshr-modelRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dshr-modelCard-off {\n opacity: 0.55;\n}\n\n/* 开关 */\n.dshr-toggle {\n position: relative;\n flex: none;\n width: 30px;\n height: 16px;\n border: none;\n border-radius: 8px;\n background: var(--rs-line);\n cursor: pointer;\n padding: 0;\n transition: background 0.15s ease;\n}\n\n.dshr-toggle-on {\n background: var(--rs-ok);\n}\n\n.dshr-toggleKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n transition: transform 0.15s ease;\n}\n\n.dshr-toggle-on .dshr-toggleKnob {\n transform: translateX(14px);\n}\n\n/* 开关禁用态:不可用(未就绪)且未开启时,灰显 + 不可点 */\n.dshr-toggle:disabled {\n cursor: default;\n opacity: 0.45;\n}\n\n/* 扩展页:未就绪问题描述(红字) */\n.dshr-ext-error {\n color: var(--rs-danger);\n font-size: 11px;\n margin-top: 4px;\n}\n\n/* 扩展供应商式小卡片:未就绪红字(描述只在详情页,卡片不放) */\n.dshr-extCardError {\n color: var(--rs-danger);\n font-size: 11px;\n}\n\n/* 扩展卡片内容区:文字左对齐,左侧留白与标题行的图标位对齐 */\n.dshr-extBody {\n padding: 10px 14px 12px 48px;\n text-align: left;\n}\n\n/* 操作按钮组 */\n.dshr-ops {\n display: flex;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dshr-danger {\n color: var(--rs-danger);\n}\n.dshr-danger:hover {\n color: var(--rs-danger);\n border-color: var(--rs-danger);\n}\n\n/* ============================================================\n dsh-router — 弹窗 + 按钮 + 整体打磨\n ============================================================ */\n\n/* 弹窗 */\n.dshr-modalOverlay {\n position: fixed;\n z-index: 1000;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: color-mix(in srgb, #000 42%, transparent);\n backdrop-filter: blur(2px);\n animation: dshrFadeIn 0.12s ease;\n}\n\n.dshr-modal {\n width: min(560px, 100%);\n max-height: min(80vh, 640px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--rs-line-strong);\n border-radius: 12px;\n background: var(--rs-layer-1);\n box-shadow:\n 0 20px 50px rgb(0 0 0 / 0.35),\n 0 0 0 1px rgb(0 0 0 / 0.05);\n animation: dshrModalIn 0.16s ease;\n}\n\n.dshr-modalHead {\n display: flex;\n min-height: 48px;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n padding: 0 16px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-modalTitle {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n font-size: 14px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modalClose {\n display: inline-flex;\n flex: none;\n width: 26px;\n height: 26px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n}\n\n.dshr-modalClose:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n.dshr-modalBody {\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.dshr-modalActions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n\n@keyframes dshrFadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes dshrModalIn {\n from {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n\n/* 按钮 */\n.dshr-settings .dshr-primaryButton,\n.dshr-shell .dshr-primaryButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-accent);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter 0.12s ease;\n}\n\n.dshr-settings .dshr-primaryButton:hover,\n.dshr-shell .dshr-primaryButton:hover {\n filter: brightness(1.08);\n}\n.dshr-settings .dshr-primaryButton:disabled,\n.dshr-shell .dshr-primaryButton:disabled {\n opacity: 0.55;\n cursor: default;\n}\n\n.dshr-settings .dshr-dangerButton,\n.dshr-shell .dshr-dangerButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-danger);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter 0.12s ease;\n}\n\n.dshr-settings .dshr-dangerButton:hover,\n.dshr-shell .dshr-dangerButton:hover {\n filter: brightness(1.1);\n}\n\n/* 登录步骤 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-loginStep {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n color: var(--rs-muted);\n font-size: 12px;\n line-height: 1.6;\n}\n\n.dshr-loginNum {\n display: inline-flex;\n flex: none;\n width: 18px;\n height: 18px;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n font-size: 11px;\n font-weight: 700;\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 9px 11px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-2);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 新 Key 弹窗 */\n.dshr-createdKeyBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-createdKey {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--rs-layer-2);\n word-break: break-all;\n user-select: all;\n}\n\n/* ============================================================\n dsh-router — 整体打磨\n ============================================================ */\n\n/* Masthead: 细腻渐变 + 底部微阴影 */\n.dshr-masthead {\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--rs-layer-1) 62%, transparent),\n transparent\n );\n}\n\n/* Brand 标题更突出 */\n.dshr-brandTitle {\n letter-spacing: 0.01em;\n}\n\n/* 卡片:阴影 + hover 微抬升 */\n.dshr-card {\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);\n transition:\n box-shadow 0.15s ease,\n transform 0.15s ease,\n border-color 0.15s ease;\n}\n\n.dshr-card-click:hover {\n transform: translateY(-1px);\n border-color: color-mix(in srgb, var(--rs-accent) 55%, var(--rs-line));\n box-shadow: 0 6px 18px rgb(0 0 0 / 0.1);\n}\n\n/* Tab:悬停背景 + 计数徽标配色 */\n.dshr-nav button {\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-nav button:hover {\n background: var(--rs-hover);\n border-radius: 6px 6px 0 0;\n}\n\n.dshr-navCount {\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n color: var(--rs-accent);\n font-weight: 600;\n}\n\n/* 徽标:更柔和 */\n.dshr-badge {\n box-shadow: inset 0 0 0 1px\n color-mix(in srgb, currentColor 12%, transparent);\n}\n\n/* 表格行悬停 + 表头层级 */\n.dshr-table tbody tr {\n transition: background 0.1s ease;\n}\n.dshr-table th {\n letter-spacing: 0.02em;\n}\n\n/* 输入框聚焦 */\n.dshr-input:focus {\n border-color: var(--rs-accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent);\n}\n\n/* 开关微过渡 */\n.dshr-toggle {\n transition:\n background 0.15s ease,\n box-shadow 0.15s ease;\n}\n.dshr-toggle:hover {\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent);\n}\n\n/* 内容区细滚动条 */\n.dshr-content::-webkit-scrollbar,\n.dshr-tableWrap::-webkit-scrollbar,\n.dshr-modalBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-content::-webkit-scrollbar-thumb,\n.dshr-tableWrap::-webkit-scrollbar-thumb,\n.dshr-modalBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-content::-webkit-scrollbar-track,\n.dshr-tableWrap::-webkit-scrollbar-track,\n.dshr-modalBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 端点与密钥(9router 风格布局)\n ============================================================ */\n\n/* 卡片头图标 */\n.dshr-cardIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 6px;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n}\n\n.dshr-cardAction {\n margin-left: auto;\n}\n\n/* Endpoint 列表 */\n.dshr-endpointList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 12px 14px;\n}\n\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-endpointLabel {\n flex: none;\n min-width: 88px;\n padding: 3px 8px;\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-2);\n font: 11px var(--rs-mono);\n text-align: center;\n}\n\n.dshr-endpointRow .dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--rs-muted);\n}\n\n.dshr-endpointNote {\n padding: 2px 2px 0;\n}\n\n/* 图标按钮 */\n.dshr-iconBtn {\n display: inline-flex;\n flex: none;\n width: 30px;\n height: 30px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-iconBtn:hover {\n color: var(--rs-accent);\n background: var(--rs-hover);\n}\n\n.dshr-iconBtn svg {\n display: block;\n}\n\n.dshr-iconBtn-sm {\n width: 26px;\n height: 26px;\n}\n\n/* 禁用态必须看得出来:纯图标按钮没有文字可读(不像「刷新中…」),\n 只靠 hover 反馈会让人以为还能点。 */\n.dshr-iconBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.dshr-iconBtn:disabled:hover {\n color: var(--rs-muted);\n background: transparent;\n}\n\n/* Require API key 行 */\n.dshr-requireRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-requireTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-requireDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 空状态 */\n.dshr-keyEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 40px 16px 44px;\n}\n\n.dshr-keyEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n margin-bottom: 6px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-keyEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-keyEmptyDesc {\n margin: 0 0 8px;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* key 列表(flex 行,非表格) */\n.dshr-keyList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-keyRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: opacity 0.12s ease;\n}\n\n.dshr-keyRow:last-child {\n border-bottom: none;\n}\n.dshr-keyRow-paused {\n opacity: 0.6;\n}\n\n.dshr-keyInfo {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-keyNameRow {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.dshr-keyName {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-keyValue {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-top: 2px;\n}\n\n.dshr-keyValue code {\n overflow: hidden;\n color: var(--rs-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-keyCreated {\n margin: 3px 0 0;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-keyPaused {\n color: var(--rs-warn);\n}\n\n.dshr-keyOps {\n display: flex;\n flex: none;\n align-items: center;\n gap: 4px;\n}\n\n/* 删除按钮 hover 才明显(9router:group-hover 显示) */\n.dshr-keyDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-keyRow:hover .dshr-keyDelete {\n opacity: 1;\n}\n.dshr-keyDelete:focus-visible {\n opacity: 1;\n}\n.dshr-keyDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 弹窗表单 */\n.dshr-modalForm {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-fieldLabel {\n font-size: 12px;\n font-weight: 650;\n color: var(--rs-text);\n margin-top: 4px;\n}\n\n.dshr-warnBox {\n padding: 12px 14px;\n border: 1px solid color-mix(in srgb, var(--rs-warn) 40%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--rs-warn) 8%, transparent);\n}\n\n.dshr-warnTitle {\n margin: 0 0 4px;\n color: var(--rs-warn);\n font-size: 13px;\n font-weight: 700;\n}\n\n.dshr-warnDesc {\n margin: 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n.dshr-createdRow {\n padding: 0;\n}\n\n.dshr-createdRow .dshr-input {\n color: var(--rs-text);\n}\n\n.dshr-fullButton {\n width: 100%;\n}\n\n/* ============================================================\n dsh-router — 供应商详情(9router 风格)\n ============================================================ */\n\n/* Header */\n.dshr-providerHead {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-backLink {\n display: inline-flex;\n align-items: center;\n align-self: flex-start;\n gap: 4px;\n border: none;\n padding: 2px 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n transition: color 0.12s ease;\n}\n\n.dshr-backLink:hover {\n color: var(--rs-accent);\n}\n\n.dshr-providerTitleRow {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dshr-providerIcon {\n display: inline-flex;\n flex: none;\n width: 44px;\n height: 44px;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n}\n\n.dshr-providerImg {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.dshr-providerName {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n letter-spacing: -0.01em;\n}\n\n.dshr-providerCount {\n margin: 2px 0 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n/* 链接池空状态 */\n.dshr-linkEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n padding: 36px 16px 40px;\n}\n\n.dshr-linkEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-linkEmptyText {\n text-align: center;\n}\n\n.dshr-linkEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-linkEmptyDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 链接列表(9router ConnectionRow 风格行) */\n.dshr-linkList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-linkRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: background 0.1s ease;\n}\n\n.dshr-linkRow:last-child {\n border-bottom: none;\n}\n.dshr-linkRow:hover {\n background: var(--rs-hover);\n}\n.dshr-linkRow[draggable=\"true\"] {\n cursor: grab;\n}\n.dshr-linkRow[draggable=\"true\"]:active {\n cursor: grabbing;\n}\n.dshr-linkRowDragging {\n opacity: 0.45;\n background: var(--rs-hover);\n}\n\n.dshr-linkGrip {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--rs-muted);\n flex: 0 0 auto;\n cursor: grab;\n padding: 4px 2px;\n user-select: none;\n}\n.dshr-linkGrip:active {\n cursor: grabbing;\n}\n.dshr-linkGrip svg {\n opacity: 0.65;\n}\n.dshr-linkRow:hover .dshr-linkGrip svg {\n opacity: 1;\n}\n\n.dshr-linkMain {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-linkTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-linkName {\n overflow: hidden;\n font-size: 13px;\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-linkSub {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 3px;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-linkUid {\n color: var(--rs-faint);\n}\n.dshr-linkCredits {\n color: var(--rs-muted);\n}\n.dshr-linkErr {\n color: var(--rs-warn);\n}\n.dshr-linkCooling {\n color: var(--rs-warn);\n}\n.dshr-linkDot {\n color: var(--rs-faint);\n opacity: 0.6;\n}\n\n.dshr-linkOps {\n display: flex;\n flex: none;\n align-items: center;\n}\n\n/* 删除链接 hover 显示 + 红色 */\n.dshr-linkDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-linkRow:hover .dshr-linkDelete {\n opacity: 1;\n}\n.dshr-linkDelete:focus-visible {\n opacity: 1;\n}\n.dshr-linkDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 改名:与删除一致,hover 行才显示;编辑中保持可见(否则保存按钮被藏住) */\n.dshr-linkEdit {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n.dshr-linkRow:hover .dshr-linkEdit {\n opacity: 1;\n}\n.dshr-linkEdit:focus-visible {\n opacity: 1;\n}\n.dshr-linkEdit:hover {\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n}\n.dshr-linkEdit.dshr-linkEdit-active {\n opacity: 1;\n}\n\n/* 模型 chips(9router ModelRow 风格) */\n.dshr-modelChips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n transition:\n border-color 0.12s ease,\n background 0.12s ease,\n color 0.12s ease;\n}\n\n.dshr-modelChip:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChip:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dshr-modelChipId {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n\n.dshr-modelChipCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n.dshr-modelChip-off {\n border-style: dashed;\n color: var(--rs-faint);\n opacity: 0.8;\n}\n\n.dshr-modelChip-off:hover {\n color: var(--rs-accent);\n border-color: var(--rs-accent);\n}\n\n.dshr-modelChipRestore {\n color: var(--rs-accent);\n font-size: 10px;\n}\n\n/* 已禁用区 */\n.dshr-disabledModels {\n width: 100%;\n}\n\n.dshr-disabledLabel {\n margin: 4px 0 0;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-disabledModels .dshr-modelChips {\n padding: 8px 0 0;\n}\n\n/* ============================================================\n dsh-router — 模型 chips 升级(9router 测试/复制 + 批量按钮)\n ============================================================ */\n\n/* 卡片头操作组 */\n.dshr-cardActions {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n/* 连接池策略选择 */\n.dshr-select {\n appearance: none;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-surface, transparent);\n color: inherit;\n font-size: 12px;\n padding: 3px 22px 3px 8px;\n cursor: pointer;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--rs-muted) 50%),\n linear-gradient(135deg, var(--rs-muted) 50%, transparent 50%);\n background-position:\n calc(100% - 12px) 55%,\n calc(100% - 7px) 55%;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n}\n.dshr-select:focus {\n outline: none;\n border-color: var(--rs-accent, #4c7dff);\n}\n\n/* 模型 chip 包装(状态边框) */\n.dshr-modelChipWrap {\n position: relative;\n display: inline-flex;\n align-items: center;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-1);\n transition:\n border-color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-modelChipWrap:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChipWrap.dshr-modelChip-ok {\n border-color: var(--rs-ok);\n}\n.dshr-modelChipWrap.dshr-modelChip-err {\n border-color: var(--rs-danger);\n}\n\n/* chip 本体(点击禁用) */\n.dshr-modelChipWrap .dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px 6px 6px;\n border: none;\n border-radius: 8px;\n color: var(--rs-text);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n}\n\n.dshr-modelChipStatus {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n}\n\n.dshr-modelChipWrap.dshr-modelChip-ok .dshr-modelChipStatus {\n color: var(--rs-ok);\n}\n.dshr-modelChipWrap.dshr-modelChip-err .dshr-modelChipStatus {\n color: var(--rs-danger);\n}\n\n/* chip 右侧操作(hover 显示) */\n.dshr-modelChipOps {\n display: inline-flex;\n align-items: center;\n gap: 0;\n padding-right: 4px;\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n\n.dshr-modelChipWrap:hover .dshr-modelChipOps,\n.dshr-modelChipOps:focus-within {\n opacity: 1;\n}\n\n/* 旧 .dshr-modelChip 独立样式(disabled 区仍用) */\n.dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n transition:\n border-color 0.12s ease,\n background 0.12s ease,\n color 0.12s ease;\n}\n\n.dshr-modelChip:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChip:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dshr-modelChipId {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n\n.dshr-modelChipCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n.dshr-modelChip-off {\n border-style: dashed;\n color: var(--rs-faint);\n opacity: 0.8;\n}\n\n.dshr-modelChip-off:hover {\n color: var(--rs-accent);\n border-color: var(--rs-accent);\n}\n\n.dshr-modelChipRestore {\n color: var(--rs-accent);\n font-size: 10px;\n}\n\n/* 计数徽标跟在标题/操作按钮右侧(模型卡片) */\n.dshr-cardMetaAfter {\n margin-left: 0;\n}\n\n/* 测试 loading 转圈 */\n.dshr-spin {\n display: inline-flex;\n animation: dshrSpin 0.8s linear infinite;\n}\n\n@keyframes dshrSpin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* 供应商标题行编辑按钮(靠右) */\n.dshr-providerEdit {\n margin-left: auto;\n}\n\n/* 模型搜索栏 */\n.dshr-modelSearch {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 14px 0;\n}\n\n.dshr-modelSearchIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n}\n\n.dshr-modelSearch .dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* 自定义模型删除按钮(hover 红色) */\n.dshr-modelDelete {\n color: var(--rs-muted);\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-modelDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 全局浮窗提示(toast) */\n.dshr-toast {\n position: fixed;\n z-index: 2000;\n left: 50%;\n bottom: 28px;\n transform: translateX(-50%);\n padding: 8px 16px;\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n border: 1px solid var(--rs-line-strong);\n box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n animation: dshrToastIn 0.18s ease;\n}\n\n@keyframes dshrToastIn {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\n/* ============ 组合(9router combos 布局交互) ============ */\n.dshr-comboIntro {\n overflow: visible;\n}\n\n.dshr-comboIntroDesc {\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-comboIntroBody {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px 14px;\n padding: 10px 14px;\n font-size: 12px;\n color: var(--rs-muted);\n border-top: 1px solid var(--rs-line);\n}\n\n.dshr-comboHeaderKey {\n font-weight: 600;\n color: var(--rs-text);\n background: color-mix(in srgb, var(--rs-success) 14%, transparent);\n border-radius: 4px;\n padding: 1px 6px;\n font-size: 11px;\n}\n\n.dshr-comboHeaderKey-2 {\n margin-left: 4px;\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n}\n\n.dshr-comboList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-comboCard {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-comboName {\n font-family: var(--rs-mono);\n font-size: 13px;\n font-weight: 600;\n color: var(--rs-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 200px;\n}\n\n.dshr-comboOps {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 10px;\n}\n\n.dshr-comboOpBtn-danger {\n color: var(--rs-danger);\n}\n.dshr-comboOpBtn-danger:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 12%, transparent);\n}\n\n.dshr-comboCardBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-top: 1px solid var(--rs-line);\n}\n\n.dshr-comboChips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-comboChip {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n background: color-mix(in srgb, var(--rs-text) 6%, transparent);\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n padding: 3px 8px 3px 5px;\n}\n\n.dshr-comboChipNum {\n width: 15px;\n height: 15px;\n border-radius: 50%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 600;\n color: var(--rs-primary);\n background: color-mix(in srgb, var(--rs-primary) 12%, transparent);\n}\n\n.dshr-comboChipSupplier {\n font-family: var(--rs-mono);\n font-size: 11px;\n color: var(--rs-text);\n}\n\n.dshr-comboEmptySteps {\n font-style: italic;\n font-size: 12px;\n}\n\n.dshr-comboMore {\n font-size: 10px;\n color: var(--rs-muted);\n}\n\n/* 表单 */\n.dshr-formLabel {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-text);\n margin: 0 0 6px;\n}\n\n.dshr-formHint {\n font-size: 10px;\n color: var(--rs-muted);\n margin: 4px 0 0;\n}\n\n.dshr-comboEmptyStepsBox {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 20px 0;\n border: 1px dashed color-mix(in srgb, var(--rs-text) 16%, transparent);\n border-radius: 9px;\n color: var(--rs-muted);\n}\n\n.dshr-comboStepList {\n display: flex;\n flex-direction: column;\n gap: 6px;\n max-height: 380px;\n overflow-y: auto;\n}\n\n.dshr-comboStepRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 4px;\n border: 1px solid transparent;\n border-radius: 7px;\n cursor: grab;\n}\n\n.dshr-comboStepRow:active {\n cursor: grabbing;\n}\n\n.dshr-comboStepRow-dragging {\n opacity: 0.45;\n border-color: var(--rs-accent);\n}\n\n.dshr-comboStepRow-over {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 8%, transparent);\n}\n\n.dshr-comboGrip {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n cursor: grab;\n}\n\n.dshr-comboStepNum {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 600;\n color: var(--rs-primary);\n background: color-mix(in srgb, var(--rs-primary) 12%, transparent);\n}\n\n.dshr-comboStepSelect {\n flex: 1;\n min-width: 0;\n}\n\n.dshr-comboStepRemove:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n.dshr-comboAddStep {\n width: 100%;\n margin-top: 8px;\n padding: 8px 0;\n border: 1px dashed color-mix(in srgb, var(--rs-text) 20%, transparent);\n border-radius: 9px;\n background: transparent;\n color: var(--rs-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition:\n border-color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-comboAddStep:hover {\n border-color: color-mix(in srgb, var(--rs-primary) 50%, transparent);\n background: color-mix(in srgb, var(--rs-primary) 5%, transparent);\n}\n\n/* 组合:已选模型行 + 添加模型挑选列表 */\n.dshr-comboSelectedModel {\n flex: 1;\n min-width: 0;\n font-family: var(--rs-mono);\n font-size: 12px;\n color: var(--rs-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-comboPickLabel {\n margin-top: 12px;\n}\n\n.dshr-comboModelSearch {\n margin-bottom: 6px;\n}\n\n.dshr-comboPickList {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n.dshr-comboPickItem {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n padding: 6px 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rs-text);\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n\n.dshr-comboPickItem:hover {\n background: var(--rs-hover);\n}\n\n.dshr-comboPickName {\n flex: 1;\n min-width: 0;\n font-family: var(--rs-mono);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-comboPickEmpty {\n font-size: 12px;\n font-style: italic;\n padding: 8px 2px;\n}\n\n/* 组合:添加模型按供应商分组 */\n.dshr-comboPickGroup {\n margin-bottom: 6px;\n}\n\n.dshr-comboPickGroupTitle {\n margin: 0 0 2px;\n padding: 0 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--rs-faint);\n}\n\n/* 组合表单:左右分栏(左=组合名/策略/已选,右=添加模型) */\n.dshr-comboFormSplit {\n position: relative;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n align-items: start;\n}\n\n.dshr-comboFormLeft {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n}\n\n.dshr-comboFormRight {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n max-height: 460px;\n}\n\n.dshr-comboFormRight .dshr-comboPickList {\n flex: 1;\n min-height: 0;\n}\n\n/* 组合编辑弹窗:加宽加高(body 更高,内部列表同步放大) */\n.dshr-modal-wide {\n width: min(760px, 100%);\n max-height: min(90vh, 820px);\n}\n\n.dshr-modal-wide .dshr-modalBody {\n min-height: 560px;\n}\n\n/* 左右分栏之间的分割线 */\n.dshr-comboFormDivider {\n position: absolute;\n top: 12px;\n bottom: 12px;\n left: 50%;\n width: 1px;\n background: var(--rs-line);\n}\n\n/* ============================================================\n dsh-router — 概览(用量看板,9router Overview 风格)\n ============================================================ */\n\n/* 周期切换 + 操作条 */\n.dshr-statsBar {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\n.dshr-periods {\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-2);\n}\n\n/* 周期按钮三态(按钮组:一组互斥的筛选按钮,不是导航 tab)。\n 特异性阶梯(类/伪类数 , 元素数):\n 文件顶部有 `.dshr-settings button { color: inherit; font: inherit }` = (1,1),\n 单类的 .dshr-period (1,0) 会被它打死(白字、600 字重全丢),所以下面每条\n 都必须带挂载容器(.dshr-settings / .dshr-shell)抬到 (2,0) 以上。\n **两个容器都要写**:只写 .dshr-shell 的话设置页里这套样式全掉\n (踩过:周期按钮退化成浏览器默认按钮)。\n 两种 hover 用 :not() 做成互斥,谁赢跟源码顺序无关(免得以后有人挪块就翻车)。 */\n.dshr-settings .dshr-period,\n.dshr-shell .dshr-period {\n min-width: 46px;\n height: 26px;\n padding: 0 10px;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease,\n filter 0.12s ease;\n}\n\n/* 未激活悬停:跟 .dshr-miniButton / .dshr-iconButton 一个套路(主文字色 + --rs-hover 底) */\n.dshr-settings .dshr-period:not(.dshr-period-on):hover,\n.dshr-shell .dshr-period:not(.dshr-period-on):hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n/* 激活:强调色块 + 白字。底色用 layer-1 时跟卡片同色,根本看不出选中了哪个 */\n.dshr-settings .dshr-period-on,\n.dshr-shell .dshr-period-on {\n color: #fff;\n background: var(--rs-accent);\n font-weight: 600;\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.12);\n}\n\n/* 激活态被悬停:跟 .dshr-primaryButton 一样提亮,表示「它还是能点的」 */\n.dshr-settings .dshr-period-on:hover,\n.dshr-shell .dshr-period-on:hover {\n color: #fff;\n background: var(--rs-accent);\n filter: brightness(1.08);\n}\n\n.dshr-statsBarRight {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.dshr-lifetime {\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 汇总卡(照 9router OverviewCards 的自适应五宫格) */\n.dshr-statGrid {\n display: grid;\n flex: none;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 10px;\n}\n\n.dshr-statCard {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 3px;\n padding: 12px 14px;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);\n}\n\n.dshr-statLabel {\n overflow: hidden;\n color: var(--rs-faint);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.03em;\n text-overflow: ellipsis;\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.dshr-statValue {\n overflow: hidden;\n font-size: 22px;\n font-weight: 700;\n letter-spacing: -0.01em;\n line-height: 1.2;\n text-overflow: ellipsis;\n}\n\n.dshr-statHint {\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-statCard-primary .dshr-statValue {\n color: var(--rs-accent);\n}\n.dshr-statCard-ok .dshr-statValue {\n color: var(--rs-success);\n}\n.dshr-statCard-warn .dshr-statValue {\n color: var(--rs-warn);\n}\n.dshr-statCard-danger .dshr-statValue {\n color: var(--rs-danger);\n}\n\n/* 签到卡:跟统计卡同宽同高,但里面是按钮不是数字。\n gap 沿用卡片的 3px(不覆写)—— 之前改成 6px 让这一行比别的高 6px,\n 两行卡片因此不齐(92 vs 98px)。\n 今日已点过 → 边框转成功色,一眼看出「今天点过了」 */\n.dshr-statCard-checkin {\n gap: 3px;\n}\n\n.dshr-statCard-done {\n border-color: color-mix(in srgb, var(--rs-success) 45%, transparent);\n}\n\n/* 按钮撑满卡片宽度:卡片本来就窄,居左的小按钮看着像没对齐 */\n.dshr-checkinButton {\n display: inline-flex;\n width: 100%;\n /* 26px ≈ .dshr-statValue 的行高(22px × 1.2),让按钮那一行跟数字的\n 那一行一样高,卡片整体高度才跟邻卡一致 */\n height: 26px;\n align-items: center;\n justify-content: center;\n gap: 5px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease,\n border-color 0.12s ease;\n}\n\n.dshr-checkinButton:hover:not(:disabled) {\n border-color: var(--rs-accent);\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 8%, transparent);\n}\n\n.dshr-checkinButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 已点过:按钮转成成功色柔和态(仍可再点) */\n.dshr-statCard-done .dshr-checkinButton {\n border-color: color-mix(in srgb, var(--rs-success) 40%, transparent);\n color: var(--rs-success);\n background: color-mix(in srgb, var(--rs-success) 8%, transparent);\n}\n\n/* 结果/失败都压在 hint 那一行里(卡片行数必须跟邻卡一致):\n 失败时才转红,其余沿用 hint 的弱色,不成功后多出一行 */\n.dshr-statHint-err {\n overflow: hidden;\n color: var(--rs-danger);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* 忙碌态:按钮转成弱色,跟「已点过」的成功色区分开 */\n.dshr-checkinButton-busy {\n color: var(--rs-faint);\n background: var(--rs-layer-2);\n border-color: var(--rs-line);\n}\n\n.dshr-estimateNote {\n margin: 0;\n color: var(--rs-faint);\n font-size: 11px;\n line-height: 1.6;\n}\n\n.dshr-estimateNote code {\n padding: 0 3px;\n border-radius: 3px;\n background: var(--rs-layer-2);\n font-family: var(--rs-mono);\n}\n\n/* 趋势折线图(纯 SVG + HTML 叠加层,不引图表库) */\n.dshr-trendWrap {\n position: relative;\n padding: 14px 14px 10px;\n}\n\n.dshr-trendBusy {\n position: absolute;\n inset: 0;\n background: color-mix(in srgb, var(--rs-layer-1) 55%, transparent);\n pointer-events: none;\n}\n\n/* 绘图区:高度跟 SVG 视口一致,叠加层用百分比定位才对得上 */\n.dshr-linePlot {\n position: relative;\n height: 150px;\n cursor: crosshair;\n}\n\n/* 键盘可达(←/→ 逐格):聚焦环跟面板其它控件一致 */\n.dshr-linePlot:focus-visible {\n outline: 2px solid var(--rs-accent);\n outline-offset: 2px;\n}\n\n/* 峰值:给读数一个量级参照;半透明底是怕压在折线上看不清 */\n.dshr-linePeak {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0 4px;\n border-radius: 3px;\n color: var(--rs-faint);\n background: color-mix(in srgb, var(--rs-layer-1) 72%, transparent);\n font-size: 9px;\n font-variant-numeric: tabular-nums;\n pointer-events: none;\n}\n\n.dshr-lineSvg {\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n inset: 0;\n overflow: visible;\n}\n\n.dshr-lineGrid {\n stroke: var(--rs-line);\n stroke-dasharray: 3 3;\n}\n\n.dshr-lineArea {\n fill: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n stroke: none;\n}\n\n.dshr-linePath {\n fill: none;\n stroke: var(--rs-accent);\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n/* 数值点:空心小圆(SVG 会被横向拉伸,所以画在 HTML 层) */\n.dshr-linePoint {\n position: absolute;\n width: 5px;\n height: 5px;\n border: 1.5px solid var(--rs-accent);\n border-radius: 50%;\n background: var(--rs-layer-1);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.dshr-lineGuide {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n background: var(--rs-line-strong);\n pointer-events: none;\n}\n\n.dshr-lineDot {\n position: absolute;\n width: 9px;\n height: 9px;\n border: 2px solid var(--rs-layer-1);\n border-radius: 50%;\n background: var(--rs-accent);\n box-shadow: 0 0 0 1px var(--rs-accent);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.dshr-lineLabels {\n position: relative;\n height: 14px;\n margin-top: 5px;\n}\n\n.dshr-lineLabel {\n position: absolute;\n top: 0;\n overflow: visible;\n color: var(--rs-faint);\n font-size: 9px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n transform: translateX(-50%);\n}\n\n/* 选中的时段:标签跟着高亮,不用抬头对读数才知道看的是哪一段 */\n.dshr-lineLabel-on {\n color: var(--rs-accent);\n font-weight: 700;\n}\n\n.dshr-trendReadout {\n display: flex;\n align-items: center;\n gap: 2px;\n min-height: 18px;\n padding-top: 6px;\n font-size: 11px;\n}\n\n.dshr-trendEmpty {\n display: flex;\n height: 150px;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n.dshr-trendEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n/* Top 榜(供应商 / 模型并排,窄屏堆叠) */\n.dshr-rankPair {\n display: grid;\n flex: none;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n align-items: start;\n}\n\n.dshr-rankList,\n.dshr-recentList {\n display: flex;\n flex-direction: column;\n padding: 6px 14px 12px;\n}\n\n.dshr-rankRow,\n.dshr-recentRow {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n padding: 7px 0;\n border-bottom: 1px solid var(--rs-line);\n font-size: 12px;\n}\n\n.dshr-rankRow:last-child,\n.dshr-recentRow:last-child {\n border-bottom: none;\n}\n\n/* 表头:不参与 hover,且不画分隔线(视觉上属于卡片头) */\n.dshr-rankHead {\n border-bottom: 1px solid var(--rs-line);\n color: var(--rs-faint);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.03em;\n text-transform: uppercase;\n}\n\n.dshr-rankRow:not(.dshr-rankHead):hover {\n background: var(--rs-hover);\n}\n\n.dshr-rankName {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-rankNum {\n flex: none;\n min-width: 52px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-rankFail {\n color: var(--rs-danger);\n}\n\n.dshr-tokenIn {\n color: var(--rs-accent);\n}\n.dshr-tokenOut {\n color: var(--rs-success);\n}\n\n/* 最近请求列宽(比 Top 榜宽,要放得下模型名) */\n.dshr-recentTime {\n display: flex;\n flex: none;\n align-items: center;\n gap: 5px;\n width: 78px;\n color: var(--rs-muted);\n font-variant-numeric: tabular-nums;\n}\n\n.dshr-recentModel {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-recentSupplier {\n flex: none;\n width: 92px;\n overflow: hidden;\n color: var(--rs-muted);\n text-align: center;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* 最近请求的 In/Out 与耗时列:**固定宽度**(不是 min-width)——\n rankNum 的 min-width 会让表头(纯文本)与数据行(更长)算出的列宽不同,\n 两行的后一列就对不齐。固定宽度让表头与每一行严格同宽。 */\n.dshr-recentTokens {\n flex: none;\n width: 80px;\n overflow: hidden;\n text-align: right;\n text-overflow: ellipsis;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-recentDuration {\n flex: none;\n width: 40px;\n overflow: hidden;\n text-align: right;\n text-overflow: ellipsis;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-recentRow-fail .dshr-recentModel {\n color: var(--rs-danger);\n}\n\n/* 状态点 */\n.dshr-dot {\n display: inline-block;\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n\n.dshr-dot-ok {\n background: var(--rs-success);\n}\n.dshr-dot-fail {\n background: var(--rs-danger);\n}\n\n/* ============================================================\n dsh-router — 设置导航图标(换掉宿主的默认齿轮)\n ============================================================ */\n\n/* DSH 0.1.x 的 settings.section 注册只认 id / order / label,图标是宿主按\n 「内置 id 白名单」选的,插件传不了 —— 外部 section 一律拿到通用齿轮。\n settings-nav-icon.ts 给本插件那一行打上标记,这里用 currentColor mask\n 渲染路由字形:跟随宿主导航的 hover/active 配色,也保持 16px 图标节奏。\n 升级路径:等契约长出 icon 字段,删掉 settings-nav-icon.ts 和这一整块。 */\n[data-dsh-router-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-router-settings-nav]::before {\n content: \"\";\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'><circle%20cx='5'%20cy='5'%20r='2.4'/><circle%20cx='19'%20cy='5'%20r='2.4'/><circle%20cx='12'%20cy='19'%20r='2.4'/><path%20d='M7.4%205h9.2'/><path%20d='M5%207.4v6.2c0%202.6%203%203.4%204.6%203.8'/><path%20d='M19%207.4v6.2c0%202.6-3%203.4-4.6%203.8'/></svg>\")\n center / contain no-repeat;\n mask: url(\"data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'><circle%20cx='5'%20cy='5'%20r='2.4'/><circle%20cx='19'%20cy='5'%20r='2.4'/><circle%20cx='12'%20cy='19'%20r='2.4'/><path%20d='M7.4%205h9.2'/><path%20d='M5%207.4v6.2c0%202.6%203%203.4%204.6%203.8'/><path%20d='M19%207.4v6.2c0%202.6-3%203.4-4.6%203.8'/></svg>\")\n center / contain no-repeat;\n}\n\n/* ============================================================\n last-hit —— 输入框底部 dock 里的「最近命中」徽章\n (LastHitDock.tsx 注册到官方 conversation.composer.dock,\n 即 .uV2eYG_dock 操作行里、原生上下文环旁边;scope: session)\n\n 与同排控件对齐:28px 高胶囊、hover 用 interactive-bg-hover、\n 图标 14px 用 label-tertiary;弹卡对齐原生 panel 规格\n (--dsw-specific-menu / --dsw-elevation-prominent / 12px 圆角 / 264px)。\n ============================================================ */\n\n.dshr-lastHit {\n position: relative;\n display: inline-flex;\n flex: none;\n align-items: center;\n}\n\n.dshr-lastHit-btn {\n position: relative;\n display: inline-flex;\n height: 28px;\n flex: none;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-secondary);\n background: 0 0;\n border: none;\n border-radius: 999px;\n padding: 0 8px;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n max-width: 220px;\n}\n\n.dshr-lastHit-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* 图标:与隔壁上下文环的描边同色(它用 label-tertiary 画进度弧),\n 尺寸 14px 也对齐该环的 svg —— 两个控件并排时看着是一套。 */\n.dshr-lastHit-btn svg {\n width: 14px;\n height: 14px;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-lastHit-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-lastHit-fail {\n color: var(--dsw-alias-label-error, #e5534b);\n}\n\n.dshr-lastHit-fail svg {\n color: var(--dsw-alias-label-error, #e5534b);\n}\n\n.dshr-lastHit-card {\n position: absolute;\n right: 0;\n bottom: calc(100% + 8px);\n z-index: 100;\n box-sizing: border-box;\n width: 264px;\n border: 0;\n border-radius: 12px;\n padding: 12px;\n color: var(--dsw-alias-label-secondary);\n /* --dsw-specific-menu 是**半透明**色,原生 panel 靠 backdrop-filter 做毛玻璃;\n 缺了它只剩半透明、看着像没背景。这两条必须成对出现。 */\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-primary, #fff));\n backdrop-filter: var(--dsw-menu-backdrop-filter, blur(40px) saturate(150%));\n -webkit-backdrop-filter: var(--dsw-menu-backdrop-filter, blur(40px) saturate(150%));\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent, 0 6px 24px rgba(0, 0, 0, 0.18));\n font-size: 12px;\n line-height: 20px;\n cursor: default;\n}\n\n/* 头部:照宿主聊天气泡的 .bRhRbq_title 规格\n (label-primary / space-between / gap16 / margin-bottom8 / font-weight500)\n 左标签带 14px 图标,右值 tabular-nums,下接一条 0.5px 分隔线。 */\n.dshr-lastHit-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.dshr-lastHit-titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-lastHit-titleLabel svg {\n width: 14px;\n height: 14px;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-lastHit-titleValue {\n overflow: hidden;\n font-variant-numeric: tabular-nums;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-lastHit-titleRule {\n border-top: .5px solid var(--dsw-alias-border-l2);\n margin-bottom: 10px;\n}\n\n.dshr-lastHit-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 2px 0;\n}\n\n.dshr-lastHit-key {\n flex: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dshr-lastHit-val {\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ============================================================\n dsh-router — 官方插件页「路由组件」一节\n ------------------------------------------------------------\n 尺寸与配色照抄宿主 plugin-manager 的 detailSection/rows/row\n (14px 500 小标题、40px 圆角图标行、12px 行距),让这一节看起来\n 就是原生列表的一部分。\n\n 注意:--rs-* 变量只定义在 .dshr-settings / .dshr-shell 上,而这一节\n 渲染在**插件页**里(两者的 DOM 祖先不同),所以这里直接用宿主的\n --dsw-alias-* 原始变量,不走中间层。\n ============================================================ */\n\n.dshr-comp {\n display: flex;\n flex-direction: column;\n gap: 28px;\n min-width: 0;\n}\n\n.dshr-compError {\n margin: 0;\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compGroup {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n}\n\n.dshr-compHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n}\n\n.dshr-compTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compCount {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compHint {\n margin: -4px 0 0;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compRows {\n display: flex;\n flex-direction: column;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshr-compRow {\n display: flex;\n align-items: center;\n gap: 16px;\n min-width: 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n padding: 12px 2px;\n}\n\n.dshr-compRow:last-child {\n border-bottom: 0;\n}\n\n.dshr-compRowIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: 0.5px solid var(--dsw-alias-border-l3);\n border-radius: 10px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n}\n\n.dshr-compRowIcon img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.dshr-compRowMain {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.dshr-compRowName {\n color: var(--dsw-alias-label-primary);\n font-size: 13.5px;\n font-weight: 500;\n line-height: 20px;\n overflow-wrap: anywhere;\n}\n\n.dshr-compRowState {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n line-height: 16px;\n overflow-wrap: anywhere;\n}\n\n/* ============================================================\n dsh-router — 插件自检面板(设置 → 路由 → 扩展 → 插件自检)\n ------------------------------------------------------------\n 这一页渲染在**宿主的插件页**里,DOM 祖先既不是 .dshr-settings 也不是\n .dshr-shell,所以文件顶部那套 `--rs-*` 主题变量在这里**不存在**(未定义的\n var() 会让整条声明失效:边框/文字色退成继承值)。\n\n 所以这里**不用**设置页那套组件样式,直接按宿主页面的视觉语言写,用宿主的\n `--dsw-alias-*` token:几何对齐它(14px 小标题、12px 次要文字、0.5px 描边、\n 12px 圆角),配色也从同一套 token 取 —— 于是这块面板看起来就是原生的一部分。\n 主按钮不用在这里画:交给宿主的 Button 原语(见 env.d.ts),配色由原语自己的\n token 家族决定,不必猜「主色底上配什么字色」。\n ============================================================ */\n\n.dshr-comp {\n gap: 16px;\n}\n\n.dshr-compIntro {\n margin: 0;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n line-height: 20px;\n}\n\n/* 三栏收进同一块面板,读起来是「一组控件」而不是三个飘着的输入框。\n 窄了换行(详情区宽度随窗口变,写死三列会在窄窗口挤成一团)。 */\n.dshr-compForm {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 14px 16px;\n padding: 14px 16px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compField {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-compFieldHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.dshr-compFieldLabel {\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n font-weight: 500;\n line-height: 18px;\n}\n\n/* 标题右侧的用法要点。语义放这里比塞进顶部那段长文字更容易被读到 ——\n 「模型只列可用的」「连接留空 = 池内任选」都是用这一栏时才关心的。 */\n.dshr-compFieldNote {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n text-align: right;\n}\n\n/* 36px / 12px 圆角,与宿主 Button 原语的 md 尺寸对齐。 */\n.dshr-compSelect {\n width: 100%;\n height: 36px;\n padding: 0 32px 0 11px;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background-color: var(--dsw-alias-bg-layer-2);\n background-image:\n linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-tertiary) 50%),\n linear-gradient(135deg, var(--dsw-alias-label-tertiary) 50%, transparent 50%);\n background-position:\n calc(100% - 17px) 16px,\n calc(100% - 12px) 16px;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n text-overflow: ellipsis;\n cursor: pointer;\n}\n\n.dshr-compSelect:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3);\n}\n\n.dshr-compSelect:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);\n}\n\n.dshr-compSelect:disabled {\n border-color: var(--dsw-alias-border-l1);\n background-color: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-tertiary);\n cursor: not-allowed;\n}\n\n/* 动作行:把按钮与它旁边的提示排在一条基线上。两个动作行(访问测试 / 契约体检)\n 共用 `dshr-compBtnRow` —— 那条抬特异性的字色规则就挂在它上面(见下)。 */\n.dshr-compActions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dshr-compHint {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* 结果区:成功/失败/请求失败 三种语气。底下垫一层极淡的状态色,比只有一行\n 彩色文字更容易扫到 —— 自检的结果是这一页唯一重要的信息。 */\n.dshr-compNotice {\n display: flex;\n gap: 10px;\n padding: 12px 14px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compNotice[data-tone=\"ok\"] {\n border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 34%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);\n}\n\n.dshr-compNotice[data-tone=\"fail\"],\n.dshr-compNotice[data-tone=\"network\"] {\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n}\n\n.dshr-compNoticeIcon {\n flex: none;\n width: 16px;\n height: 16px;\n margin-top: 2px;\n color: var(--dsw-alias-state-success-secondary);\n}\n\n.dshr-compNotice[data-tone=\"fail\"] .dshr-compNoticeIcon,\n.dshr-compNotice[data-tone=\"network\"] .dshr-compNoticeIcon {\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dshr-compNoticeBody {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.dshr-compNoticeTitle {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compNoticeText {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n line-height: 19px;\n /* 上游可能回一整段 HTML/JSON,限定宽度换行,别把布局撑破 */\n overflow-wrap: anywhere;\n}\n\n/* 回显测的是哪三样。一条「通过」如果不带对象,就是没法归因的一串绿字。 */\n.dshr-compNoticeMeta {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n overflow-wrap: anywhere;\n}\n\n/* 宿主 Button 原语的 primary 想把字色设成 --dsw-alias-label-primary-foreground,\n 但上面那条兜底 `.dshr-settings button { color: inherit; font: inherit }`\n 特异性 (1,1) **高于**原语的 `.primary` (1,0) —— 赢的是它。于是按钮变成\n 「#0f1115 的底 + 继承来的 #0f1115 的字」,黑底黑字、按钮看着是空的。\n\n 按本文件既有的抬特异性办法修(见文件里 .dshr-tab-on / .dshr-period 那几处注释:\n 凡是受这条兜底影响的规则,都必须带挂载容器抬到 (2,0) 以上)。这里\n `.dshr-comp .dshr-compBtnRow button` 是 (2,1),不依赖源码顺序,以后有人挪块也不会翻车。\n\n 作用域挂在**动作行的共用类 `dshr-compBtnRow`** 上,不是某个具体容器:上一版这条\n 只写了 `.dshr-compActions`,随后新增的「契约体检」按钮在另一个容器里,于是又变回\n 黑底黑字。现在两个动作行共用这个类,再加按钮只需沿用它。\n\n 修的是**把原语的意图还回去**,不是自己另挑一个颜色:字色仍由原语那个 token 决定。 */\n.dshr-comp .dshr-compBtnRow button {\n color: var(--dsw-alias-label-primary-foreground);\n font-size: 14px;\n line-height: 22px;\n}\n\n/* --- 契约体检区(插件自检面板的第二块) ---\n 状态靠 data-state 上色,和「结果卡」那套语气一致:过了是绿、失败是红、\n 没实现是灰、不执行是中性(它是**故意**不跑的,不是问题)。 */\n\n.dshr-compSection {\n display: flex;\n flex-direction: column;\n gap: 12px;\n margin-top: 4px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l1);\n}\n\n.dshr-compHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.dshr-compSectionTitle {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compSectionNote {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compProbeRow {\n display: flex;\n align-items: flex-end;\n gap: 12px;\n}\n\n.dshr-compProbeRow .dshr-compField {\n flex: 1;\n}\n\n.dshr-compProbeBtn {\n flex: none;\n}\n\n.dshr-compReport {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compReportHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.dshr-compReportName {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compReportSum {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compMembers {\n display: flex;\n flex-direction: column;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshr-compMember {\n display: grid;\n grid-template-columns: 16px minmax(0, auto) minmax(0, 1fr);\n align-items: baseline;\n gap: 10px;\n padding: 5px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l1);\n}\n\n.dshr-compMember:last-child {\n border-bottom: 0;\n}\n\n.dshr-compMemberState {\n flex: none;\n text-align: center;\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compMember[data-state=\"ok\"] .dshr-compMemberState {\n color: var(--dsw-alias-state-success-secondary);\n}\n\n.dshr-compMember[data-state=\"fail\"] .dshr-compMemberState {\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dshr-compMember[data-state=\"absent\"] .dshr-compMemberState,\n.dshr-compMember[data-state=\"skipped\"] .dshr-compMemberState {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-compMemberName {\n color: var(--dsw-alias-label-primary);\n font-size: 12.5px;\n line-height: 19px;\n white-space: nowrap;\n}\n\n.dshr-compMemberKey {\n margin-left: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n}\n\n.dshr-compMemberDetail {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n overflow-wrap: anywhere;\n}\n\n\n/*$vite$:1*/")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
1
+ (function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("/* ============================================================\n dsh-router — sidebar entry\n ============================================================ */\n\n.dshr-entry {\n box-sizing: border-box;\n display: flex;\n width: 100%;\n min-height: 36px;\n align-items: center;\n gap: 10px;\n border: none;\n border-radius: 8px;\n padding: 0 10px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-entry:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshr-entry[data-active] {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-active);\n font-weight: 600;\n}\n\n.dshr-entryIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n\n.dshr-entryIcon svg {\n display: block;\n width: 18px;\n height: 18px;\n}\n\n.dshr-entryLabel {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entry {\n width: 36px;\n min-height: 36px;\n justify-content: center;\n margin: 0 auto 12px;\n padding: 0;\n border-radius: 50%;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entryLabel {\n display: none;\n}\n\n/* ============================================================\n dsh-router — 主题变量(设置页 .dshr-settings / 老中心栏 .dshr-shell 共用)\n ============================================================ */\n\n.dshr-settings,\n.dshr-shell {\n --rs-bg: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n --rs-layer-1: var(--dsw-alias-bg-layer-1);\n --rs-layer-2: var(--dsw-alias-bg-layer-2);\n --rs-input: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-2));\n --rs-text: var(--dsw-alias-label-primary);\n --rs-muted: var(--dsw-alias-label-secondary);\n --rs-faint: var(--dsw-alias-label-tertiary);\n --rs-line: var(--dsw-alias-border-l1);\n --rs-line-strong: var(--dsw-alias-border-l2);\n --rs-accent: var(--dsw-alias-state-business-primary);\n --rs-hover: var(--dsw-alias-interactive-bg-hover);\n --rs-danger: var(--dsw-alias-state-error-primary);\n --rs-success: var(--dsw-alias-state-success-primary);\n --rs-ok: var(--rs-success);\n --rs-warn: #9a6a18;\n --rs-mono: var(\n --ds-font-family-code,\n \"SFMono-Regular\",\n Consolas,\n monospace\n );\n --rs-sans: var(\n --dsw-font-family,\n -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif\n );\n box-sizing: border-box;\n min-width: 0;\n color: var(--rs-text);\n font: 13px/1.55 var(--rs-sans);\n -webkit-tap-highlight-color: transparent;\n}\n\n/* ============================================================\n dsh-router — 设置页容器(settings.section 座位)\n ============================================================ */\n\n/* 容器几何(浏览器实测,改之前先量别凭感觉):\n - 内容宽 564px(面板 612 − 左右 padding 24)\n - 外壳 .VOzbGW_options 是 `overflow-y:auto` + `flex:1 1 0%`,高度确定\n (视口 813 时可视高 703~746)\n - 根节点外面套了一层 display:contents(宿主 slot renderer 给的),**没有\n 盒子** —— 但实测 `height:100%` 仍能穿过它解析到外壳高度\n (验过:root 从 1332px 溢出态变成 722px,正好填满容器)\n\n 布局规则:**tab 条固定,只有下面的内容区滚**。\n 所以根节点必须限高(不限高内容会把外壳撑开,变成整页滚),内容区再\n min-height:0 + overflow-y:auto 自己滚。\n 关键字是 min-height:0 —— flex 子项默认 min-height:auto,不给 0 的话\n 子项缩不小,overflow 永远不触发。 */\n.dshr-settings {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after {\n box-sizing: border-box;\n}\n\n/* 主导航 = tab 条。照 DSH 原生的内容区 tab(插件页那套 pbvGtq_tab):\n 容器一条 1px 底部分隔线,激活项用 ::after 画 2px 下划线压在线上。\n 刻意**不用**填充色块——那会跟下面的周期按钮组撞成同一种控件,\n 层级也反了(导航是页面级,周期是筛选级)。 */\n.dshr-tabs {\n display: flex;\n flex: none; /* 固定:内容区滚,tab 条不动 */\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--rs-line);\n /* 右端跟卡片对齐(实测数字,别凭感觉调):\n root = 564;内容区因 padding-right 8px → 内容可视宽 556;\n 卡片再贴着可视区排 → 548。\n tab 条是 .dshr-settings 的直接子项,起点是 root(564),\n 要落到 548 就得让掉 8(padding) + 8(滚动条) = 16px。\n 写成 margin 而不用 padding,是为了不把分隔线画到 padding 区域外。 */\n margin-right: 16px;\n}\n\n.dshr-tab {\n position: relative;\n min-width: 0;\n height: 36px;\n padding: 0 12px;\n border: none;\n border-radius: 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n transition: color 0.12s ease;\n}\n\n.dshr-tab:hover {\n color: var(--rs-text);\n}\n\n/* 激活:主文字色 + 2px 下划线(bottom:-1px 压在容器的分隔线上)。\n 双类是因为上面有 `.dshr-settings button { color: inherit; font: inherit }` (1,1),\n 单类的 .dshr-tab-on 只有 (1,0),颜色和字号会被吃掉。 */\n.dshr-settings .dshr-tab-on {\n color: var(--rs-text);\n font-size: 13px;\n font-weight: 500;\n}\n\n.dshr-settings .dshr-tab-on::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: \"\";\n background: var(--rs-text);\n}\n\n.dshr-tabBack {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-tabBack:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n/* 内容区:吃掉剩余高度并**自己滚**,这样 tab 条永远留在顶部。\n flex:1 + min-height:0 是必须的(见 .dshr-settings 的注释)。 */\n.dshr-settingsBody {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow-y: auto;\n /* 恒定预留滚动条槽位。\n 实测不设它时 clientWidth 在 564(内容短、无滚动条)和 556(内容长、有滚动条)\n 之间跳,卡片宽也跟着 556/548 跳 —— 切 tab 时内容会横向抖一下。\n 不支持 scrollbar-gutter 的浏览器只是退化成「照样占宽」,不会坏。 */\n scrollbar-gutter: stable;\n /* 右内边距:内容别贴到滚动条上。滚动条宽 8px 且占布局宽度\n (offsetWidth 564 vs clientWidth 556),不撑开的话内容右边缘正好压在它上面。 */\n padding-right: 8px;\n}\n\n/* 细滚动条:跟 .dshr-content / .dshr-modalBody 一个套路 */\n.dshr-settingsBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-settingsBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-settingsBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 老的中心栏劫持骨架(settings 不可用时的回退,非主路径)\n ============================================================ */\n\n[data-pane=\"conversation\"] {\n position: relative;\n}\n.dshDesktopConversationSurface {\n position: relative;\n}\n\n[data-dsh-router-view] {\n position: absolute;\n z-index: 60;\n inset: 0;\n display: none;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n}\n\n.dshr-panelView {\n isolation: isolate;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [data-dsh-router-view] {\n display: block;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [data-pane=\"conversation\"]\n > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n [class*=\"centerCol\"]\n > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(\n [data-dsh-ssh-active]\n ):not([data-dsh-mnemon-active])\n .dshDesktopConversationSurface\n > :not([data-dsh-router-view]) {\n display: none !important;\n}\n\n.dshr-shell {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--rs-bg);\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after,\n.dshr-shell *,\n.dshr-shell *::before,\n.dshr-shell *::after {\n box-sizing: border-box;\n}\n\n/* 按钮/表单重置 + 聚焦环:两个挂载容器都要覆盖。\n 只写 .dshr-shell 的话设置页整套控件样式全掉(踩过:周期按钮变裸文字)。 */\n.dshr-settings button,\n.dshr-settings input,\n.dshr-settings select,\n.dshr-settings textarea,\n.dshr-shell button,\n.dshr-shell input,\n.dshr-shell select,\n.dshr-shell textarea {\n color: inherit;\n font: inherit;\n}\n\n.dshr-settings button:focus-visible,\n.dshr-settings input:focus-visible,\n.dshr-settings select:focus-visible,\n.dshr-settings textarea:focus-visible,\n.dshr-shell button:focus-visible,\n.dshr-shell input:focus-visible,\n.dshr-shell select:focus-visible,\n.dshr-shell textarea:focus-visible {\n outline: 2px solid var(--rs-accent);\n outline-offset: 2px;\n}\n\n/* Masthead */\n.dshr-masthead {\n display: flex;\n min-height: 56px;\n flex: 0 0 auto;\n align-items: center;\n gap: 16px;\n padding: 0 18px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-brand {\n display: grid;\n min-width: 0;\n flex: 0 1 auto;\n align-content: center;\n gap: 1px 9px;\n}\n\n.dshr-brandTitle {\n overflow: hidden;\n font-size: 15px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-brandSub {\n overflow: hidden;\n color: var(--rs-faint);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-statusCluster {\n display: inline-flex;\n min-width: 0;\n flex: 0 0 auto;\n align-items: center;\n gap: 7px;\n margin-left: auto;\n color: var(--rs-muted);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.dshr-statusEndpoint {\n overflow: hidden;\n max-width: 220px;\n color: var(--rs-faint);\n font: 11px var(--rs-mono);\n text-overflow: ellipsis;\n}\n\n.dshr-headerActions {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n}\n\n.dshr-refreshedAt {\n color: var(--rs-faint);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-iconButton {\n display: inline-flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n cursor: pointer;\n}\n\n.dshr-iconButton:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n.dshr-iconButton:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.dshr-backButton {\n display: inline-flex;\n height: 28px;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-text);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-backButton:hover {\n background: var(--rs-hover);\n}\n.dshr-backButton svg {\n display: block;\n}\n\n/* Alert banner */\n.dshr-alert {\n display: flex;\n flex: 0 0 auto;\n align-items: baseline;\n gap: 10px;\n margin: 12px 18px 0;\n padding: 10px 12px;\n border: 1px solid var(--rs-line-strong);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-alert strong {\n color: var(--rs-danger);\n font-size: 12px;\n}\n.dshr-alert span {\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n/* Nav tabs */\n.dshr-nav {\n display: flex;\n flex: 0 0 auto;\n gap: 2px;\n padding: 0 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-nav button {\n position: relative;\n min-height: 42px;\n padding: 0 12px;\n border: none;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n cursor: pointer;\n}\n\n.dshr-nav button:hover {\n color: var(--rs-text);\n}\n\n.dshr-nav button[aria-current=\"page\"] {\n color: var(--rs-text);\n font-weight: 600;\n}\n\n.dshr-nav button[aria-current=\"page\"]::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: \"\";\n background: var(--rs-accent);\n}\n\n.dshr-navCount {\n display: inline-block;\n margin-left: 5px;\n padding: 0 6px;\n border-radius: 9px;\n color: var(--rs-faint);\n background: var(--rs-layer-2);\n font-size: 11px;\n line-height: 16px;\n}\n\n/* Content */\n.dshr-content {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow: auto;\n}\n\n.dshr-empty {\n display: flex;\n min-height: 120px;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n font-size: 13px;\n}\n\n/* 两行空状态(标题 + 说明)。`.dshr-empty` 本身是横排的单行 flex,直接套两行\n div 会左右排开,所以另给一个纵向堆叠的变体。 */\n.dshr-emptyStack {\n flex-direction: column;\n gap: 6px;\n text-align: center;\n}\n\n/* Accounts table */\n.dshr-tableWrap {\n overflow: auto;\n padding: 12px 18px;\n}\n\n.dshr-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.dshr-table th {\n position: sticky;\n top: 0;\n padding: 8px 10px;\n border-bottom: 1px solid var(--rs-line-strong);\n color: var(--rs-faint);\n background: var(--rs-bg);\n font-size: 11px;\n font-weight: 600;\n text-align: left;\n white-space: nowrap;\n}\n\n.dshr-table td {\n padding: 7px 10px;\n border-bottom: 1px solid var(--rs-line);\n vertical-align: middle;\n}\n\n.dshr-table tbody tr:hover {\n background: var(--rs-hover);\n}\n\n.dshr-mono {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n.dshr-num {\n text-align: right;\n}\n.dshr-muted {\n color: var(--rs-faint);\n}\n.dshr-reason {\n color: var(--rs-muted);\n font-size: 11px;\n}\n\n.dshr-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 9px;\n font-size: 11px;\n line-height: 16px;\n}\n\n.dshr-badge-ok {\n color: var(--rs-success);\n background: color-mix(in srgb, var(--rs-success) 12%, transparent);\n}\n.dshr-badge-warn {\n color: var(--rs-warn);\n background: color-mix(in srgb, var(--rs-warn) 14%, transparent);\n}\n.dshr-badge-danger {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 12%, transparent);\n}\n\n/* Models grid */\n.dshr-modelGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: 10px;\n padding: 14px 18px;\n}\n\n/* 设置页里横向 padding 归零:内容区已经贴着容器边,再加 18px 会把卡片挤成\n 1 列、图表也跟着变窄。上下 padding 保留。 */\n.dshr-settings .dshr-modelGrid,\n.dshr-settings .dshr-tableWrap {\n padding-right: 0;\n padding-left: 0;\n}\n\n.dshr-modelCard {\n display: flex;\n min-width: 0;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-modelCard:hover {\n border-color: var(--rs-line-strong);\n}\n\n.dshr-modelId {\n overflow: hidden;\n font-family: var(--rs-mono);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modelCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n/* ============================================================\n dsh-router — 9router 风格 tab 内容(供应商 / 组合 / 端点与密钥)\n ============================================================ */\n\n/* tab 内容:设置页里**不自己滚** —— 滚动交给 .dshr-settingsBody(tab 条下方\n 那一块)。这里再给 overflow 就会出现嵌套滚动条(内容区一个、tabBody 一个)。\n 老的 .dshr-shell 中心栏挂载方式没有 settingsBody 那层,滚动由它自己承担。 */\n.dshr-tabBody {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 14px;\n padding: 0;\n}\n\n.dshr-shell .dshr-tabBody {\n flex: 1 1 auto;\n min-height: 0;\n padding: 14px 18px;\n overflow-y: auto;\n}\n\n.dshr-card {\n flex: none; /* 不参与 flex 伸缩:高度=内容,避免被压缩裁切 */\n min-width: 0;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n overflow: hidden;\n}\n\n.dshr-cardHead {\n display: flex;\n min-height: 44px;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-cardTitle {\n font-size: 13px;\n font-weight: 650;\n}\n\n.dshr-cardMeta {\n margin-left: auto;\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 组合链 */\n.dshr-comboChain {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-comboStep {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dshr-comboArrow {\n color: var(--rs-faint);\n margin-right: 8px;\n}\n\n.dshr-comboSupplier {\n font-family: var(--rs-mono);\n font-size: 12px;\n}\n\n/* 端点与密钥 */\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px;\n}\n\n.dshr-endpointCode {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-2);\n color: var(--rs-text);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-miniButton {\n display: inline-flex;\n flex: none;\n height: 28px;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-miniButton:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n.dshr-code {\n margin: 0;\n padding: 12px 14px;\n overflow-x: auto;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font: 12px/1.6 var(--rs-mono);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n/* ============================================================\n dsh-router — 供应商详情 / 端点与密钥 / 通用控件\n ============================================================ */\n\n/* 详情页头 */\n.dshr-detailHead {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 36px;\n}\n\n.dshr-detailTitle {\n font-size: 14px;\n font-weight: 650;\n}\n\n/* 可点击卡片 */\n.dshr-card-click {\n cursor: pointer;\n transition: border-color 0.12s ease;\n}\n\n.dshr-card-click:hover {\n border-color: var(--rs-accent);\n}\n\n.dshr-cardHint {\n padding: 0 14px 12px;\n}\n\n/* 供应商小卡片(grid 布局,替代整行大卡片) */\n.dshr-supplierGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));\n gap: 12px;\n}\n\n/* 供应商分组(内置 / 插件分开) */\n.dshr-supplierGroup {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-supplierGroup + .dshr-supplierGroup {\n margin-top: 22px;\n}\n\n.dshr-supplierGroupTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-muted);\n letter-spacing: 0.04em;\n padding-left: 2px;\n}\n\n.dshr-supplierCard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n padding: 16px 18px;\n border: 1px solid var(--rs-line);\n border-radius: 12px;\n background: var(--rs-layer-1);\n cursor: pointer;\n transition: border-color 0.12s ease;\n}\n\n.dshr-supplierCard:hover {\n border-color: var(--rs-accent);\n}\n\n.dshr-supplierName {\n font-size: 14px;\n font-weight: 650;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-supplierRow {\n display: flex;\n align-items: center;\n gap: 9px;\n min-width: 0;\n}\n\n.dshr-supplierIcon {\n width: 24px;\n height: 24px;\n flex: none;\n border-radius: 5px;\n object-fit: contain;\n color: var(--rs-faint);\n}\n\n.dshr-supplierMeta {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-chevron {\n color: var(--rs-faint);\n font-size: 18px;\n line-height: 1;\n margin-left: auto;\n}\n\n/* 登录框 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n background: var(--rs-layer-2);\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 输入框 */\n.dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-text);\n font-size: 12px;\n}\n\n.dshr-input:focus {\n outline: none;\n border-color: var(--rs-accent);\n}\n\n/* 成功提示 */\n.dshr-alert-ok {\n border-color: color-mix(in srgb, var(--rs-ok) 45%, transparent);\n background: color-mix(in srgb, var(--rs-ok) 10%, transparent);\n}\n\n.dshr-createdKey {\n display: block;\n margin: 6px 0;\n padding: 8px 10px;\n border-radius: 6px;\n background: var(--rs-layer-1);\n word-break: break-all;\n}\n\n/* 模型卡片行 */\n.dshr-modelRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dshr-modelCard-off {\n opacity: 0.55;\n}\n\n/* 开关 */\n/* 扩展页:未就绪问题描述(红字) */\n.dshr-ext-error {\n color: var(--rs-danger);\n font-size: 11px;\n margin-top: 4px;\n}\n\n/* 扩展供应商式小卡片:未就绪红字(描述只在详情页,卡片不放) */\n.dshr-extCardError {\n color: var(--rs-danger);\n font-size: 11px;\n}\n\n/* 扩展卡片内容区:文字左对齐,左侧留白与标题行的图标位对齐 */\n.dshr-extBody {\n padding: 10px 14px 12px 48px;\n text-align: left;\n}\n\n/* 操作按钮组 */\n.dshr-ops {\n display: flex;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dshr-danger {\n color: var(--rs-danger);\n}\n.dshr-danger:hover {\n color: var(--rs-danger);\n border-color: var(--rs-danger);\n}\n\n/* ============================================================\n dsh-router — 弹窗 + 按钮 + 整体打磨\n ============================================================ */\n\n/* 弹窗 */\n.dshr-modalOverlay {\n position: fixed;\n z-index: 1000;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: color-mix(in srgb, #000 42%, transparent);\n backdrop-filter: blur(2px);\n animation: dshrFadeIn 0.12s ease;\n}\n\n.dshr-modal {\n width: min(560px, 100%);\n max-height: min(80vh, 640px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--rs-line-strong);\n border-radius: 12px;\n background: var(--rs-layer-1);\n box-shadow:\n 0 20px 50px rgb(0 0 0 / 0.35),\n 0 0 0 1px rgb(0 0 0 / 0.05);\n animation: dshrModalIn 0.16s ease;\n}\n\n.dshr-modalHead {\n display: flex;\n min-height: 48px;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n padding: 0 16px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-modalTitle {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n font-size: 14px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modalClose {\n display: inline-flex;\n flex: none;\n width: 26px;\n height: 26px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n}\n\n.dshr-modalClose:hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n.dshr-modalBody {\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.dshr-modalActions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n\n@keyframes dshrFadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes dshrModalIn {\n from {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n\n/* 按钮 */\n.dshr-settings .dshr-primaryButton,\n.dshr-shell .dshr-primaryButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-accent);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter 0.12s ease;\n}\n\n.dshr-settings .dshr-primaryButton:hover,\n.dshr-shell .dshr-primaryButton:hover {\n filter: brightness(1.08);\n}\n.dshr-settings .dshr-primaryButton:disabled,\n.dshr-shell .dshr-primaryButton:disabled {\n opacity: 0.55;\n cursor: default;\n}\n\n.dshr-settings .dshr-dangerButton,\n.dshr-shell .dshr-dangerButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-danger);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter 0.12s ease;\n}\n\n.dshr-settings .dshr-dangerButton:hover,\n.dshr-shell .dshr-dangerButton:hover {\n filter: brightness(1.1);\n}\n\n/* 登录步骤 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-loginStep {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n color: var(--rs-muted);\n font-size: 12px;\n line-height: 1.6;\n}\n\n.dshr-loginNum {\n display: inline-flex;\n flex: none;\n width: 18px;\n height: 18px;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n font-size: 11px;\n font-weight: 700;\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 9px 11px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-2);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 新 Key 弹窗 */\n.dshr-createdKeyBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-createdKey {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--rs-layer-2);\n word-break: break-all;\n user-select: all;\n}\n\n/* ============================================================\n dsh-router — 整体打磨\n ============================================================ */\n\n/* Masthead: 细腻渐变 + 底部微阴影 */\n.dshr-masthead {\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--rs-layer-1) 62%, transparent),\n transparent\n );\n}\n\n/* Brand 标题更突出 */\n.dshr-brandTitle {\n letter-spacing: 0.01em;\n}\n\n/* 卡片:阴影 + hover 微抬升 */\n.dshr-card {\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);\n transition:\n box-shadow 0.15s ease,\n transform 0.15s ease,\n border-color 0.15s ease;\n}\n\n.dshr-card-click:hover {\n transform: translateY(-1px);\n border-color: color-mix(in srgb, var(--rs-accent) 55%, var(--rs-line));\n box-shadow: 0 6px 18px rgb(0 0 0 / 0.1);\n}\n\n/* Tab:悬停背景 + 计数徽标配色 */\n.dshr-nav button {\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-nav button:hover {\n background: var(--rs-hover);\n border-radius: 6px 6px 0 0;\n}\n\n.dshr-navCount {\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n color: var(--rs-accent);\n font-weight: 600;\n}\n\n/* 徽标:更柔和 */\n.dshr-badge {\n box-shadow: inset 0 0 0 1px\n color-mix(in srgb, currentColor 12%, transparent);\n}\n\n/* 表格行悬停 + 表头层级 */\n.dshr-table tbody tr {\n transition: background 0.1s ease;\n}\n.dshr-table th {\n letter-spacing: 0.02em;\n}\n\n/* 输入框聚焦 */\n.dshr-input:focus {\n border-color: var(--rs-accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent);\n}\n\n/* 内容区细滚动条 */\n.dshr-content::-webkit-scrollbar,\n.dshr-tableWrap::-webkit-scrollbar,\n.dshr-modalBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-content::-webkit-scrollbar-thumb,\n.dshr-tableWrap::-webkit-scrollbar-thumb,\n.dshr-modalBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-content::-webkit-scrollbar-track,\n.dshr-tableWrap::-webkit-scrollbar-track,\n.dshr-modalBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 端点与密钥(9router 风格布局)\n ============================================================ */\n\n/* 卡片头图标 */\n.dshr-cardIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 6px;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n}\n\n.dshr-cardAction {\n margin-left: auto;\n}\n\n/* Endpoint 列表 */\n.dshr-endpointList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 12px 14px;\n}\n\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-endpointLabel {\n flex: none;\n min-width: 88px;\n padding: 3px 8px;\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-2);\n font: 11px var(--rs-mono);\n text-align: center;\n}\n\n.dshr-endpointRow .dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--rs-muted);\n}\n\n.dshr-endpointNote {\n padding: 2px 2px 0;\n}\n\n/* 图标按钮 */\n.dshr-iconBtn {\n display: inline-flex;\n flex: none;\n width: 30px;\n height: 30px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-iconBtn:hover {\n color: var(--rs-accent);\n background: var(--rs-hover);\n}\n\n.dshr-iconBtn svg {\n display: block;\n}\n\n.dshr-iconBtn-sm {\n width: 26px;\n height: 26px;\n}\n\n/* 禁用态必须看得出来:纯图标按钮没有文字可读(不像「刷新中…」),\n 只靠 hover 反馈会让人以为还能点。 */\n.dshr-iconBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.dshr-iconBtn:disabled:hover {\n color: var(--rs-muted);\n background: transparent;\n}\n\n/* Require API key 行 */\n.dshr-requireRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-requireTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-requireDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 空状态 */\n.dshr-keyEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 40px 16px 44px;\n}\n\n.dshr-keyEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n margin-bottom: 6px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-keyEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-keyEmptyDesc {\n margin: 0 0 8px;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* key 列表(flex 行,非表格) */\n.dshr-keyList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-keyRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: opacity 0.12s ease;\n}\n\n.dshr-keyRow:last-child {\n border-bottom: none;\n}\n.dshr-keyRow-paused {\n opacity: 0.6;\n}\n\n.dshr-keyInfo {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-keyNameRow {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.dshr-keyName {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-keyValue {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-top: 2px;\n}\n\n.dshr-keyValue code {\n overflow: hidden;\n color: var(--rs-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-keyCreated {\n margin: 3px 0 0;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-keyPaused {\n color: var(--rs-warn);\n}\n\n.dshr-keyOps {\n display: flex;\n flex: none;\n align-items: center;\n gap: 4px;\n}\n\n/* 删除按钮 hover 才明显(9router:group-hover 显示) */\n.dshr-keyDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-keyRow:hover .dshr-keyDelete {\n opacity: 1;\n}\n.dshr-keyDelete:focus-visible {\n opacity: 1;\n}\n.dshr-keyDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 弹窗表单 */\n.dshr-modalForm {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-fieldLabel {\n font-size: 12px;\n font-weight: 650;\n color: var(--rs-text);\n margin-top: 4px;\n}\n\n.dshr-warnBox {\n padding: 12px 14px;\n border: 1px solid color-mix(in srgb, var(--rs-warn) 40%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--rs-warn) 8%, transparent);\n}\n\n.dshr-warnTitle {\n margin: 0 0 4px;\n color: var(--rs-warn);\n font-size: 13px;\n font-weight: 700;\n}\n\n.dshr-warnDesc {\n margin: 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n.dshr-createdRow {\n padding: 0;\n}\n\n.dshr-createdRow .dshr-input {\n color: var(--rs-text);\n}\n\n.dshr-fullButton {\n width: 100%;\n}\n\n/* ============================================================\n dsh-router — 供应商详情(9router 风格)\n ============================================================ */\n\n/* Header */\n.dshr-providerHead {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-backLink {\n display: inline-flex;\n align-items: center;\n align-self: flex-start;\n gap: 4px;\n border: none;\n padding: 2px 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n transition: color 0.12s ease;\n}\n\n.dshr-backLink:hover {\n color: var(--rs-accent);\n}\n\n.dshr-providerTitleRow {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dshr-providerIcon {\n display: inline-flex;\n flex: none;\n width: 44px;\n height: 44px;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n}\n\n.dshr-providerImg {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.dshr-providerName {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n letter-spacing: -0.01em;\n}\n\n.dshr-providerCount {\n margin: 2px 0 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n/* 链接池空状态 */\n.dshr-linkEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n padding: 36px 16px 40px;\n}\n\n.dshr-linkEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-linkEmptyText {\n text-align: center;\n}\n\n.dshr-linkEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-linkEmptyDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 链接列表(9router ConnectionRow 风格行) */\n.dshr-linkList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-linkRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: background 0.1s ease;\n}\n\n.dshr-linkRow:last-child {\n border-bottom: none;\n}\n.dshr-linkRow:hover {\n background: var(--rs-hover);\n}\n.dshr-linkRow[draggable=\"true\"] {\n cursor: grab;\n}\n.dshr-linkRow[draggable=\"true\"]:active {\n cursor: grabbing;\n}\n.dshr-linkRowDragging {\n opacity: 0.45;\n background: var(--rs-hover);\n}\n\n.dshr-linkGrip {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--rs-muted);\n flex: 0 0 auto;\n cursor: grab;\n padding: 4px 2px;\n user-select: none;\n}\n.dshr-linkGrip:active {\n cursor: grabbing;\n}\n.dshr-linkGrip svg {\n opacity: 0.65;\n}\n.dshr-linkRow:hover .dshr-linkGrip svg {\n opacity: 1;\n}\n\n.dshr-linkMain {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-linkTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-linkName {\n overflow: hidden;\n font-size: 13px;\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-linkSub {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 3px;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-linkUid {\n color: var(--rs-faint);\n}\n.dshr-linkCredits {\n color: var(--rs-muted);\n}\n.dshr-linkErr {\n color: var(--rs-warn);\n}\n.dshr-linkCooling {\n color: var(--rs-warn);\n}\n.dshr-linkDot {\n color: var(--rs-faint);\n opacity: 0.6;\n}\n\n.dshr-linkOps {\n display: flex;\n flex: none;\n align-items: center;\n}\n\n/* 删除链接 hover 显示 + 红色 */\n.dshr-linkDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-linkRow:hover .dshr-linkDelete {\n opacity: 1;\n}\n.dshr-linkDelete:focus-visible {\n opacity: 1;\n}\n.dshr-linkDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 改名:与删除一致,hover 行才显示;编辑中保持可见(否则保存按钮被藏住) */\n.dshr-linkEdit {\n color: var(--rs-muted);\n opacity: 0;\n transition:\n opacity 0.12s ease,\n color 0.12s ease,\n background 0.12s ease;\n}\n.dshr-linkRow:hover .dshr-linkEdit {\n opacity: 1;\n}\n.dshr-linkEdit:focus-visible {\n opacity: 1;\n}\n.dshr-linkEdit:hover {\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n}\n.dshr-linkEdit.dshr-linkEdit-active {\n opacity: 1;\n}\n\n/* 模型 chips(9router ModelRow 风格) */\n.dshr-modelChips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n transition:\n border-color 0.12s ease,\n background 0.12s ease,\n color 0.12s ease;\n}\n\n.dshr-modelChip:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChip:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dshr-modelChipId {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n\n.dshr-modelChipCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n.dshr-modelChip-off {\n border-style: dashed;\n color: var(--rs-faint);\n opacity: 0.8;\n}\n\n.dshr-modelChip-off:hover {\n color: var(--rs-accent);\n border-color: var(--rs-accent);\n}\n\n.dshr-modelChipRestore {\n color: var(--rs-accent);\n font-size: 10px;\n}\n\n/* 已禁用区 */\n.dshr-disabledModels {\n width: 100%;\n}\n\n.dshr-disabledLabel {\n margin: 4px 0 0;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-disabledModels .dshr-modelChips {\n padding: 8px 0 0;\n}\n\n/* ============================================================\n dsh-router — 模型 chips 升级(9router 测试/复制 + 批量按钮)\n ============================================================ */\n\n/* 卡片头操作组 */\n.dshr-cardActions {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n/* 连接池策略选择 */\n.dshr-select {\n appearance: none;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-surface, transparent);\n color: inherit;\n font-size: 12px;\n padding: 3px 22px 3px 8px;\n cursor: pointer;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--rs-muted) 50%),\n linear-gradient(135deg, var(--rs-muted) 50%, transparent 50%);\n background-position:\n calc(100% - 12px) 55%,\n calc(100% - 7px) 55%;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n}\n.dshr-select:focus {\n outline: none;\n border-color: var(--rs-accent, #4c7dff);\n}\n\n/* 模型 chip 包装(状态边框) */\n.dshr-modelChipWrap {\n position: relative;\n display: inline-flex;\n align-items: center;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-1);\n transition:\n border-color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-modelChipWrap:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChipWrap.dshr-modelChip-ok {\n border-color: var(--rs-ok);\n}\n.dshr-modelChipWrap.dshr-modelChip-err {\n border-color: var(--rs-danger);\n}\n\n/* chip 本体(点击禁用) */\n.dshr-modelChipWrap .dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px 6px 6px;\n border: none;\n border-radius: 8px;\n color: var(--rs-text);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n}\n\n.dshr-modelChipStatus {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n}\n\n.dshr-modelChipWrap.dshr-modelChip-ok .dshr-modelChipStatus {\n color: var(--rs-ok);\n}\n.dshr-modelChipWrap.dshr-modelChip-err .dshr-modelChipStatus {\n color: var(--rs-danger);\n}\n\n/* chip 右侧操作(hover 显示) */\n.dshr-modelChipOps {\n display: inline-flex;\n align-items: center;\n gap: 0;\n padding-right: 4px;\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n\n.dshr-modelChipWrap:hover .dshr-modelChipOps,\n.dshr-modelChipOps:focus-within {\n opacity: 1;\n}\n\n/* 旧 .dshr-modelChip 独立样式(disabled 区仍用) */\n.dshr-modelChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n transition:\n border-color 0.12s ease,\n background 0.12s ease,\n color 0.12s ease;\n}\n\n.dshr-modelChip:hover {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 6%, transparent);\n}\n\n.dshr-modelChip:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dshr-modelChipId {\n font-family: var(--rs-mono);\n font-size: 11px;\n}\n\n.dshr-modelChipCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n.dshr-modelChip-off {\n border-style: dashed;\n color: var(--rs-faint);\n opacity: 0.8;\n}\n\n.dshr-modelChip-off:hover {\n color: var(--rs-accent);\n border-color: var(--rs-accent);\n}\n\n.dshr-modelChipRestore {\n color: var(--rs-accent);\n font-size: 10px;\n}\n\n/* 计数徽标跟在标题/操作按钮右侧(模型卡片) */\n.dshr-cardMetaAfter {\n margin-left: 0;\n}\n\n/* 测试 loading 转圈 */\n.dshr-spin {\n display: inline-flex;\n animation: dshrSpin 0.8s linear infinite;\n}\n\n@keyframes dshrSpin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* 供应商标题行编辑按钮(靠右) */\n.dshr-providerEdit {\n margin-left: auto;\n}\n\n/* 模型搜索栏 */\n.dshr-modelSearch {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 14px 0;\n}\n\n.dshr-modelSearchIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n}\n\n.dshr-modelSearch .dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* 自定义模型删除按钮(hover 红色) */\n.dshr-modelDelete {\n color: var(--rs-muted);\n transition:\n color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-modelDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 全局浮窗提示(toast) */\n.dshr-toast {\n position: fixed;\n z-index: 2000;\n left: 50%;\n bottom: 28px;\n transform: translateX(-50%);\n padding: 8px 16px;\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n border: 1px solid var(--rs-line-strong);\n box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n animation: dshrToastIn 0.18s ease;\n}\n\n@keyframes dshrToastIn {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\n/* ============ 组合(9router combos 布局交互) ============ */\n.dshr-comboIntro {\n overflow: visible;\n}\n\n.dshr-comboIntroDesc {\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-comboIntroBody {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px 14px;\n padding: 10px 14px;\n font-size: 12px;\n color: var(--rs-muted);\n border-top: 1px solid var(--rs-line);\n}\n\n.dshr-comboHeaderKey {\n font-weight: 600;\n color: var(--rs-text);\n background: color-mix(in srgb, var(--rs-success) 14%, transparent);\n border-radius: 4px;\n padding: 1px 6px;\n font-size: 11px;\n}\n\n.dshr-comboHeaderKey-2 {\n margin-left: 4px;\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n}\n\n.dshr-comboList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-comboCard {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-comboName {\n font-family: var(--rs-mono);\n font-size: 13px;\n font-weight: 600;\n color: var(--rs-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 200px;\n}\n\n.dshr-comboOps {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 10px;\n}\n\n.dshr-comboOpBtn-danger {\n color: var(--rs-danger);\n}\n.dshr-comboOpBtn-danger:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 12%, transparent);\n}\n\n.dshr-comboCardBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-top: 1px solid var(--rs-line);\n}\n\n.dshr-comboChips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-comboChip {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n background: color-mix(in srgb, var(--rs-text) 6%, transparent);\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n padding: 3px 8px 3px 5px;\n}\n\n.dshr-comboChipNum {\n width: 15px;\n height: 15px;\n border-radius: 50%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 600;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n}\n\n.dshr-comboChipSupplier {\n font-family: var(--rs-mono);\n font-size: 11px;\n color: var(--rs-text);\n}\n\n.dshr-comboEmptySteps {\n font-style: italic;\n font-size: 12px;\n}\n\n.dshr-comboMore {\n font-size: 10px;\n color: var(--rs-muted);\n}\n\n/* 表单 */\n.dshr-formLabel {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-text);\n margin: 0 0 6px;\n}\n\n.dshr-formHint {\n font-size: 10px;\n color: var(--rs-muted);\n margin: 4px 0 0;\n}\n\n.dshr-comboEmptyStepsBox {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 20px 0;\n border: 1px dashed color-mix(in srgb, var(--rs-text) 16%, transparent);\n border-radius: 9px;\n color: var(--rs-muted);\n}\n\n.dshr-comboStepList {\n display: flex;\n flex-direction: column;\n gap: 6px;\n /* 原 max-height: 380px —— 写死的值,与容器无关。改由 flex 链给。 */\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.dshr-comboStepRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 4px;\n border: 1px solid transparent;\n border-radius: 7px;\n cursor: grab;\n}\n\n.dshr-comboStepRow:active {\n cursor: grabbing;\n}\n\n.dshr-comboStepRow-dragging {\n opacity: 0.45;\n border-color: var(--rs-accent);\n}\n\n.dshr-comboStepRow-over {\n border-color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 8%, transparent);\n}\n\n.dshr-comboGrip {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n cursor: grab;\n}\n\n.dshr-comboStepNum {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 600;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n}\n\n.dshr-comboStepSelect {\n flex: 1;\n min-width: 0;\n}\n\n.dshr-comboStepRemove:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n.dshr-comboAddStep {\n width: 100%;\n margin-top: 8px;\n padding: 8px 0;\n border: 1px dashed color-mix(in srgb, var(--rs-text) 20%, transparent);\n border-radius: 9px;\n background: transparent;\n color: var(--rs-accent);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition:\n border-color 0.12s ease,\n background 0.12s ease;\n}\n\n.dshr-comboAddStep:hover {\n border-color: color-mix(in srgb, var(--rs-accent) 50%, transparent);\n background: color-mix(in srgb, var(--rs-accent) 5%, transparent);\n}\n\n/* 组合:已选模型行 + 添加模型挑选列表 */\n.dshr-comboSelectedModel {\n flex: 1;\n min-width: 0;\n font-family: var(--rs-mono);\n font-size: 12px;\n color: var(--rs-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-comboPickLabel {\n margin-top: 12px;\n}\n\n.dshr-comboModelSearch {\n margin-bottom: 6px;\n}\n\n.dshr-comboPickList {\n display: flex;\n flex-direction: column;\n gap: 2px;\n /* 原 max-height: 300px —— 写死的值,与容器无关。改由 flex 链给。 */\n overflow-y: auto;\n}\n\n.dshr-comboPickItem {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n padding: 6px 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rs-text);\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s ease;\n}\n\n.dshr-comboPickItem:hover {\n background: var(--rs-hover);\n}\n\n.dshr-comboPickName {\n flex: 1;\n min-width: 0;\n font-family: var(--rs-mono);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-comboPickEmpty {\n font-size: 12px;\n font-style: italic;\n padding: 8px 2px;\n}\n\n/* 组合:添加模型按供应商分组 */\n.dshr-comboPickGroup {\n margin-bottom: 6px;\n}\n\n.dshr-comboPickGroupTitle {\n margin: 0 0 2px;\n padding: 0 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--rs-faint);\n}\n\n/* 组合表单:左右分栏(左=组合名/策略/已选,右=添加模型) */\n.dshr-comboFormSplit {\n position: relative;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n /* stretch 而非 start:两列都吃满行高,右列那个滚动列表才有高可用。\n 用 start 的话行高由最高的一列决定,短的那列留白 —— 同一个病。 */\n align-items: stretch;\n /* 行高必须是 minmax(0,1fr) 而不是隐式 auto:auto 行高由内容决定,\n 视口一小内容压不下去,滚动就又跑到外层 body 去了。 */\n grid-template-rows: minmax(0, 1fr);\n flex: 1;\n min-height: 0;\n}\n\n.dshr-comboFormLeft {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n}\n\n/* 「模型」那一坨(标题 + 列表)吃掉剩余高度,列表才有空间滚动 */\n.dshr-comboFormLeft > div:last-child {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n.dshr-comboFormRight {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n min-height: 0;\n}\n\n.dshr-comboFormRight .dshr-comboPickList {\n flex: 1;\n min-height: 0;\n}\n\n/* 组合编辑弹窗:加宽加高(body 更高,内部列表同步放大) */\n.dshr-modal-wide {\n /* 高度上限必须**扣掉遮罩的 24px×2 padding**:原来的 90vh 在视口\n 低于 480px 时,90vh + 48px 必然超过视口,弹窗顶出遮罩。\n (基础 .dshr-modal 用 80vh 所以一直没这问题,只有 -wide 变体有。) */\n --dshr-modalMaxH: calc(100vh - 48px);\n width: min(760px, 100%);\n max-height: min(var(--dshr-modalMaxH), 820px);\n}\n\n/* ⚠️ 这条 min-height **只预留**了高度,不往内传递:body 是块容器,里面的内容\n 各自按自己的 max-height 摆(pick list 300 / 左列表 380 / 右列 460),\n 560 - 内容高 就是底部那条空白 —— 选 2 个模型时最明显,选满时反而看不见,\n 所以它一直躲过了「按内容相关问题」的眼。\n 改成 flex 列,并让 .dshr-modalForm 吃掉剩余高度,预留才真正被内容用掉。\n\n ⚠️ 地板也不能写死:视口一小,560px 会把弹窗顶出遮罩,逼得**外层 body**\n 出来滚动(多一道滚动条,还裁掉一截)。上限跟着 --dshr-modalMaxH 走\n (60 = 头部 48 + 边框 2 + 余量),空间不够时该滚的是内层\n .dshr-comboPickList,不是整个 body。 */\n.dshr-modal-wide .dshr-modalBody {\n min-height: min(560px, calc(var(--dshr-modalMaxH) - 60px));\n display: flex;\n flex-direction: column;\n}\n\n.dshr-modal-wide .dshr-modalForm {\n flex: 1;\n min-height: 0;\n}\n\n/* 左右分栏之间的分割线 */\n.dshr-comboFormDivider {\n position: absolute;\n top: 12px;\n bottom: 12px;\n left: 50%;\n width: 1px;\n background: var(--rs-line);\n}\n\n/* ============================================================\n dsh-router — 概览(用量看板,9router Overview 风格)\n ============================================================ */\n\n/* 周期切换 + 操作条 */\n.dshr-statsBar {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\n.dshr-periods {\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-2);\n}\n\n/* 周期按钮三态(按钮组:一组互斥的筛选按钮,不是导航 tab)。\n 特异性阶梯(类/伪类数 , 元素数):\n 文件顶部有 `.dshr-settings button { color: inherit; font: inherit }` = (1,1),\n 单类的 .dshr-period (1,0) 会被它打死(白字、600 字重全丢),所以下面每条\n 都必须带挂载容器(.dshr-settings / .dshr-shell)抬到 (2,0) 以上。\n **两个容器都要写**:只写 .dshr-shell 的话设置页里这套样式全掉\n (踩过:周期按钮退化成浏览器默认按钮)。\n 两种 hover 用 :not() 做成互斥,谁赢跟源码顺序无关(免得以后有人挪块就翻车)。 */\n.dshr-settings .dshr-period,\n.dshr-shell .dshr-period {\n min-width: 46px;\n height: 26px;\n padding: 0 10px;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease,\n filter 0.12s ease;\n}\n\n/* 未激活悬停:跟 .dshr-miniButton / .dshr-iconButton 一个套路(主文字色 + --rs-hover 底) */\n.dshr-settings .dshr-period:not(.dshr-period-on):hover,\n.dshr-shell .dshr-period:not(.dshr-period-on):hover {\n color: var(--rs-text);\n background: var(--rs-hover);\n}\n\n/* 激活:强调色块 + 白字。底色用 layer-1 时跟卡片同色,根本看不出选中了哪个 */\n.dshr-settings .dshr-period-on,\n.dshr-shell .dshr-period-on {\n color: #fff;\n background: var(--rs-accent);\n font-weight: 600;\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.12);\n}\n\n/* 激活态被悬停:跟 .dshr-primaryButton 一样提亮,表示「它还是能点的」 */\n.dshr-settings .dshr-period-on:hover,\n.dshr-shell .dshr-period-on:hover {\n color: #fff;\n background: var(--rs-accent);\n filter: brightness(1.08);\n}\n\n.dshr-statsBarRight {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.dshr-lifetime {\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 汇总卡(照 9router OverviewCards 的自适应五宫格) */\n.dshr-statGrid {\n display: grid;\n flex: none;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 10px;\n}\n\n.dshr-statCard {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 3px;\n padding: 12px 14px;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);\n}\n\n.dshr-statLabel {\n overflow: hidden;\n color: var(--rs-faint);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.03em;\n text-overflow: ellipsis;\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.dshr-statValue {\n overflow: hidden;\n font-size: 22px;\n font-weight: 700;\n letter-spacing: -0.01em;\n line-height: 1.2;\n text-overflow: ellipsis;\n}\n\n.dshr-statHint {\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-statCard-primary .dshr-statValue {\n color: var(--rs-accent);\n}\n.dshr-statCard-ok .dshr-statValue {\n color: var(--rs-success);\n}\n.dshr-statCard-warn .dshr-statValue {\n color: var(--rs-warn);\n}\n.dshr-statCard-danger .dshr-statValue {\n color: var(--rs-danger);\n}\n\n/* 签到卡:跟统计卡同宽同高,但里面是按钮不是数字。\n gap 沿用卡片的 3px(不覆写)—— 之前改成 6px 让这一行比别的高 6px,\n 两行卡片因此不齐(92 vs 98px)。\n 今日已点过 → 边框转成功色,一眼看出「今天点过了」 */\n.dshr-statCard-checkin {\n gap: 3px;\n}\n\n.dshr-statCard-done {\n border-color: color-mix(in srgb, var(--rs-success) 45%, transparent);\n}\n\n/* 按钮撑满卡片宽度:卡片本来就窄,居左的小按钮看着像没对齐 */\n.dshr-checkinButton {\n display: inline-flex;\n width: 100%;\n /* 26px ≈ .dshr-statValue 的行高(22px × 1.2),让按钮那一行跟数字的\n 那一行一样高,卡片整体高度才跟邻卡一致 */\n height: 26px;\n align-items: center;\n justify-content: center;\n gap: 5px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition:\n color 0.12s ease,\n background 0.12s ease,\n border-color 0.12s ease;\n}\n\n.dshr-checkinButton:hover:not(:disabled) {\n border-color: var(--rs-accent);\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 8%, transparent);\n}\n\n.dshr-checkinButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 已点过:按钮转成成功色柔和态(仍可再点) */\n.dshr-statCard-done .dshr-checkinButton {\n border-color: color-mix(in srgb, var(--rs-success) 40%, transparent);\n color: var(--rs-success);\n background: color-mix(in srgb, var(--rs-success) 8%, transparent);\n}\n\n/* 结果/失败都压在 hint 那一行里(卡片行数必须跟邻卡一致):\n 失败时才转红,其余沿用 hint 的弱色,不成功后多出一行 */\n.dshr-statHint-err {\n overflow: hidden;\n color: var(--rs-danger);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* 忙碌态:按钮转成弱色,跟「已点过」的成功色区分开 */\n.dshr-checkinButton-busy {\n color: var(--rs-faint);\n background: var(--rs-layer-2);\n border-color: var(--rs-line);\n}\n\n.dshr-estimateNote {\n margin: 0;\n color: var(--rs-faint);\n font-size: 11px;\n line-height: 1.6;\n}\n\n.dshr-estimateNote code {\n padding: 0 3px;\n border-radius: 3px;\n background: var(--rs-layer-2);\n font-family: var(--rs-mono);\n}\n\n/* 趋势折线图(纯 SVG + HTML 叠加层,不引图表库) */\n.dshr-trendWrap {\n position: relative;\n padding: 14px 14px 10px;\n}\n\n.dshr-trendBusy {\n position: absolute;\n inset: 0;\n background: color-mix(in srgb, var(--rs-layer-1) 55%, transparent);\n pointer-events: none;\n}\n\n/* 绘图区:高度跟 SVG 视口一致,叠加层用百分比定位才对得上 */\n.dshr-linePlot {\n position: relative;\n height: 150px;\n cursor: crosshair;\n}\n\n/* 键盘可达(←/→ 逐格):聚焦环跟面板其它控件一致 */\n.dshr-linePlot:focus-visible {\n outline: 2px solid var(--rs-accent);\n outline-offset: 2px;\n}\n\n/* 峰值:给读数一个量级参照;半透明底是怕压在折线上看不清 */\n.dshr-linePeak {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0 4px;\n border-radius: 3px;\n color: var(--rs-faint);\n background: color-mix(in srgb, var(--rs-layer-1) 72%, transparent);\n font-size: 9px;\n font-variant-numeric: tabular-nums;\n pointer-events: none;\n}\n\n.dshr-lineSvg {\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n inset: 0;\n overflow: visible;\n}\n\n.dshr-lineGrid {\n stroke: var(--rs-line);\n stroke-dasharray: 3 3;\n}\n\n.dshr-lineArea {\n fill: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n stroke: none;\n}\n\n.dshr-linePath {\n fill: none;\n stroke: var(--rs-accent);\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n/* 数值点:空心小圆(SVG 会被横向拉伸,所以画在 HTML 层) */\n.dshr-linePoint {\n position: absolute;\n width: 5px;\n height: 5px;\n border: 1.5px solid var(--rs-accent);\n border-radius: 50%;\n background: var(--rs-layer-1);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.dshr-lineGuide {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n background: var(--rs-line-strong);\n pointer-events: none;\n}\n\n.dshr-lineDot {\n position: absolute;\n width: 9px;\n height: 9px;\n border: 2px solid var(--rs-layer-1);\n border-radius: 50%;\n background: var(--rs-accent);\n box-shadow: 0 0 0 1px var(--rs-accent);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.dshr-lineLabels {\n position: relative;\n height: 14px;\n margin-top: 5px;\n}\n\n.dshr-lineLabel {\n position: absolute;\n top: 0;\n overflow: visible;\n color: var(--rs-faint);\n font-size: 9px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n transform: translateX(-50%);\n}\n\n/* 选中的时段:标签跟着高亮,不用抬头对读数才知道看的是哪一段 */\n.dshr-lineLabel-on {\n color: var(--rs-accent);\n font-weight: 700;\n}\n\n.dshr-trendReadout {\n display: flex;\n align-items: center;\n gap: 2px;\n min-height: 18px;\n padding-top: 6px;\n font-size: 11px;\n}\n\n.dshr-trendEmpty {\n display: flex;\n height: 150px;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n.dshr-trendEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n/* Top 榜(供应商 / 模型并排,窄屏堆叠) */\n.dshr-rankPair {\n display: grid;\n flex: none;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n align-items: start;\n}\n\n.dshr-rankList,\n.dshr-recentList {\n display: flex;\n flex-direction: column;\n padding: 6px 14px 12px;\n}\n\n.dshr-rankRow,\n.dshr-recentRow {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n padding: 7px 0;\n border-bottom: 1px solid var(--rs-line);\n font-size: 12px;\n}\n\n.dshr-rankRow:last-child,\n.dshr-recentRow:last-child {\n border-bottom: none;\n}\n\n/* 表头:不参与 hover,且不画分隔线(视觉上属于卡片头) */\n.dshr-rankHead {\n border-bottom: 1px solid var(--rs-line);\n color: var(--rs-faint);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.03em;\n text-transform: uppercase;\n}\n\n.dshr-rankRow:not(.dshr-rankHead):hover {\n background: var(--rs-hover);\n}\n\n.dshr-rankName {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-rankNum {\n flex: none;\n min-width: 52px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-rankFail {\n color: var(--rs-danger);\n}\n\n.dshr-tokenIn {\n color: var(--rs-accent);\n}\n.dshr-tokenOut {\n color: var(--rs-success);\n}\n\n/* 最近请求列宽(比 Top 榜宽,要放得下模型名) */\n.dshr-recentTime {\n display: flex;\n flex: none;\n align-items: center;\n gap: 5px;\n width: 78px;\n color: var(--rs-muted);\n font-variant-numeric: tabular-nums;\n}\n\n.dshr-recentModel {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-recentSupplier {\n flex: none;\n width: 92px;\n overflow: hidden;\n color: var(--rs-muted);\n text-align: center;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* 最近请求的 In/Out 与耗时列:**固定宽度**(不是 min-width)——\n rankNum 的 min-width 会让表头(纯文本)与数据行(更长)算出的列宽不同,\n 两行的后一列就对不齐。固定宽度让表头与每一行严格同宽。 */\n.dshr-recentTokens {\n flex: none;\n width: 80px;\n overflow: hidden;\n text-align: right;\n text-overflow: ellipsis;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-recentDuration {\n flex: none;\n width: 40px;\n overflow: hidden;\n text-align: right;\n text-overflow: ellipsis;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.dshr-recentRow-fail .dshr-recentModel {\n color: var(--rs-danger);\n}\n\n/* 状态点 */\n.dshr-dot {\n display: inline-block;\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n\n.dshr-dot-ok {\n background: var(--rs-success);\n}\n.dshr-dot-fail {\n background: var(--rs-danger);\n}\n\n/* ============================================================\n dsh-router — 设置导航图标(换掉宿主的默认齿轮)\n ============================================================ */\n\n/* DSH 0.1.x 的 settings.section 注册只认 id / order / label,图标是宿主按\n 「内置 id 白名单」选的,插件传不了 —— 外部 section 一律拿到通用齿轮。\n settings-nav-icon.ts 给本插件那一行打上标记,这里用 currentColor mask\n 渲染路由字形:跟随宿主导航的 hover/active 配色,也保持 16px 图标节奏。\n 升级路径:等契约长出 icon 字段,删掉 settings-nav-icon.ts 和这一整块。 */\n[data-dsh-router-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-router-settings-nav]::before {\n content: \"\";\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'><circle%20cx='5'%20cy='5'%20r='2.4'/><circle%20cx='19'%20cy='5'%20r='2.4'/><circle%20cx='12'%20cy='19'%20r='2.4'/><path%20d='M7.4%205h9.2'/><path%20d='M5%207.4v6.2c0%202.6%203%203.4%204.6%203.8'/><path%20d='M19%207.4v6.2c0%202.6-3%203.4-4.6%203.8'/></svg>\")\n center / contain no-repeat;\n mask: url(\"data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'><circle%20cx='5'%20cy='5'%20r='2.4'/><circle%20cx='19'%20cy='5'%20r='2.4'/><circle%20cx='12'%20cy='19'%20r='2.4'/><path%20d='M7.4%205h9.2'/><path%20d='M5%207.4v6.2c0%202.6%203%203.4%204.6%203.8'/><path%20d='M19%207.4v6.2c0%202.6-3%203.4-4.6%203.8'/></svg>\")\n center / contain no-repeat;\n}\n\n/* ============================================================\n last-hit —— 输入框底部 dock 里的「最近命中」徽章\n (LastHitDock.tsx 注册到官方 conversation.composer.dock,\n 即 .uV2eYG_dock 操作行里、原生上下文环旁边;scope: session)\n\n 与同排控件对齐:28px 高胶囊、hover 用 interactive-bg-hover、\n 图标 14px 用 label-tertiary;弹卡对齐原生 panel 规格\n (--dsw-specific-menu / --dsw-elevation-prominent / 12px 圆角 / 264px)。\n ============================================================ */\n\n.dshr-lastHit {\n position: relative;\n display: inline-flex;\n flex: none;\n align-items: center;\n}\n\n.dshr-lastHit-btn {\n position: relative;\n display: inline-flex;\n height: 28px;\n flex: none;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-secondary);\n background: 0 0;\n border: none;\n border-radius: 999px;\n padding: 0 8px;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n max-width: 220px;\n}\n\n.dshr-lastHit-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* 图标:与隔壁上下文环的描边同色(它用 label-tertiary 画进度弧),\n 尺寸 14px 也对齐该环的 svg —— 两个控件并排时看着是一套。 */\n.dshr-lastHit-btn svg {\n width: 14px;\n height: 14px;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-lastHit-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-lastHit-fail {\n color: var(--dsw-alias-label-error, #e5534b);\n}\n\n.dshr-lastHit-fail svg {\n color: var(--dsw-alias-label-error, #e5534b);\n}\n\n.dshr-lastHit-card {\n position: absolute;\n right: 0;\n bottom: calc(100% + 8px);\n z-index: 100;\n box-sizing: border-box;\n width: 264px;\n border: 0;\n border-radius: 12px;\n padding: 12px;\n color: var(--dsw-alias-label-secondary);\n /* --dsw-specific-menu 是**半透明**色,原生 panel 靠 backdrop-filter 做毛玻璃;\n 缺了它只剩半透明、看着像没背景。这两条必须成对出现。 */\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-primary, #fff));\n backdrop-filter: var(--dsw-menu-backdrop-filter, blur(40px) saturate(150%));\n -webkit-backdrop-filter: var(--dsw-menu-backdrop-filter, blur(40px) saturate(150%));\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent, 0 6px 24px rgba(0, 0, 0, 0.18));\n font-size: 12px;\n line-height: 20px;\n cursor: default;\n}\n\n/* 头部:照宿主聊天气泡的 .bRhRbq_title 规格\n (label-primary / space-between / gap16 / margin-bottom8 / font-weight500)\n 左标签带 14px 图标,右值 tabular-nums,下接一条 0.5px 分隔线。 */\n.dshr-lastHit-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.dshr-lastHit-titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-lastHit-titleLabel svg {\n width: 14px;\n height: 14px;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-lastHit-titleValue {\n overflow: hidden;\n font-variant-numeric: tabular-nums;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-lastHit-titleRule {\n border-top: .5px solid var(--dsw-alias-border-l2);\n margin-bottom: 10px;\n}\n\n.dshr-lastHit-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 2px 0;\n}\n\n.dshr-lastHit-key {\n flex: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dshr-lastHit-val {\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ============================================================\n dsh-router — 官方插件页「路由组件」一节\n ------------------------------------------------------------\n 尺寸与配色照抄宿主 plugin-manager 的 detailSection/rows/row\n (14px 500 小标题、40px 圆角图标行、12px 行距),让这一节看起来\n 就是原生列表的一部分。\n\n 注意:--rs-* 变量只定义在 .dshr-settings / .dshr-shell 上,而这一节\n 渲染在**插件页**里(两者的 DOM 祖先不同),所以这里直接用宿主的\n --dsw-alias-* 原始变量,不走中间层。\n ============================================================ */\n\n.dshr-comp {\n display: flex;\n flex-direction: column;\n gap: 28px;\n min-width: 0;\n}\n\n.dshr-compError {\n margin: 0;\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compGroup {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n}\n\n.dshr-compHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n}\n\n.dshr-compTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compCount {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compHint {\n margin: -4px 0 0;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compRows {\n display: flex;\n flex-direction: column;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshr-compRow {\n display: flex;\n align-items: center;\n gap: 16px;\n min-width: 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n /* ⚠️ 左右 14px(原先 2px):行贴着卡片边缘,文字几乎顶到边框,读起来\"挤\"。\n 2026-09-29 用户提出。与 `-compHead` 的内缩对齐。 */\n padding: 12px 14px;\n}\n\n.dshr-compRow:last-child {\n border-bottom: 0;\n}\n\n.dshr-compRowIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: 0.5px solid var(--dsw-alias-border-l3);\n border-radius: 10px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n}\n\n.dshr-compRowIcon img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.dshr-compRowMain {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.dshr-compRowName {\n color: var(--dsw-alias-label-primary);\n font-size: 13.5px;\n font-weight: 500;\n line-height: 20px;\n overflow-wrap: anywhere;\n}\n\n/* 扩展子开关的浮动提示:`dshr-toast` 的**窄范围覆盖**。\n ⚠️ 不去改 `.dshr-toast` 本身 —— 它被 `SupplierDetail` 也在用(那边全是\n 「连接池顺序已保存」这种 9 字短句,所以从没暴露过宽度问题)。\n 本处的错误文案更长(「只能删除自定义准则(内置条目请用开关关掉)」19 字),\n 而 `.dshr-toast` 是 `white-space: nowrap` **且没有 `max-width`** ⇒ 会横向溢出屏幕。\n ⇒ 只加**换行 + 上限 + 居中**,定位用绝对定位挂在提示自身,**不影响既有调用方**。 */\n.dshr-toastWrap {\n position: fixed;\n z-index: 2000;\n left: 50%;\n bottom: 28px;\n transform: translateX(-50%);\n max-width: min(420px, calc(100vw - 32px));\n padding: 8px 16px;\n border-radius: 8px;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n border: 1px solid var(--rs-line-strong);\n box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);\n font-size: 12px;\n font-weight: 500;\n white-space: pre-wrap; /* 允许换行,不再 nowrap 溢出 */\n text-align: center;\n animation: dshrToastIn 0.18s ease;\n}\n\n/* 标题 + 「修改」按钮的**同一行**(`ExtDetail` 的子开关区)。\n ⚠️ 单独一个类而不改 `.dshr-compRowName`:那个类 `RouterComponentsSection` /\n `ExtTestPanel` 也在用,给它加 `display:flex` 会把**那些页面的标题**也变成\n flex 盒子(它们标题后面没有按钮,平白多一层)。\n 没有这层时按钮会掉到**下一行**:`.dshr-compRowMain` 是 column,而\n `.dshr-compRowName` 是行内元素、标题一长就折行(2026-09-29 用户报)。 */\n/* 子开关区:`.dshr-compGroup` 的 `gap: 12px` 覆盖为 0(2026-09-30 用户要求)。\n ⚠️ **不能直接改 `.dshr-compGroup`**:它还被 `RouterComponentsSection` 用着,\n 那里的 12px 隔的是「组标题 / hint / 列表」三块,删了会挤成一坨。\n ⇒ 本处单独覆盖,且**只覆盖 gap**(其余属性仍复用同一个类)。\n 本页不需要这个间距:页头自带 `border-bottom` 分割线,再加 12px 留白\n 会让分割线\"悬\"在一段空白上方(用户说的「分割线有高度」)。 */\n.dshr-compGroupTight {\n gap: 0;\n}\n\n/* 行内的「修改」按钮:**hover 该行才显形**(2026-09-29)。\n 14 行各挂一个常驻图标 = 一排按钮压过内容本身。\n ⚠️ **必须同时有 `:focus-within`**:纯 `:hover` 会让**键盘用户永远够不到**\n (Tab 到它时它是 `opacity:0`,看得见焦点环但看不见按钮)。\n ⚠️ 用 `opacity` 而不是 `visibility`/`display` —— 后两者会让它**不可聚焦**\n (`visibility:hidden` 的元素不进 Tab 序),`opacity:0` 仍在。 */\n.dshr-compRowAct {\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n\n.dshr-compRow:hover .dshr-compRowAct,\n.dshr-compRow:focus-within .dshr-compRowAct {\n opacity: 1;\n}\n\n/* 触屏没有 hover ⇒ 按钮永远点不到。窄屏(触屏为主)直接常显。 */\n@media (hover: none) {\n .dshr-compRowAct {\n opacity: 1;\n }\n}\n\n.dshr-compRowTitleLine {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-compRowState {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n line-height: 16px;\n overflow-wrap: anywhere;\n /* 保留换行:子开关的**原文**是这个类的另一个用法(2026-09-29 合并进来),\n 它是多行段落。状态文案都是单行,所以这条对它们没有副作用。\n ⇒ 原来的 `.dshr-compRowBody` 整块删掉:14 条规则的正文不需要左边框 +\n 缩进那种「引用块」的额外装饰(用户要求),层级只靠颜色就够。 */\n white-space: pre-wrap;\n}\n\n/* ============================================================\n dsh-router — 插件自检面板(设置 → 路由 → 扩展 → 插件自检)\n ------------------------------------------------------------\n 这一页渲染在**宿主的插件页**里,DOM 祖先既不是 .dshr-settings 也不是\n .dshr-shell,所以文件顶部那套 `--rs-*` 主题变量在这里**不存在**(未定义的\n var() 会让整条声明失效:边框/文字色退成继承值)。\n\n 所以这里**不用**设置页那套组件样式,直接按宿主页面的视觉语言写,用宿主的\n `--dsw-alias-*` token:几何对齐它(14px 小标题、12px 次要文字、0.5px 描边、\n 12px 圆角),配色也从同一套 token 取 —— 于是这块面板看起来就是原生的一部分。\n 主按钮不用在这里画:交给宿主的 Button 原语(见 env.d.ts),配色由原语自己的\n token 家族决定,不必猜「主色底上配什么字色」。\n ============================================================ */\n\n.dshr-comp {\n gap: 16px;\n}\n\n.dshr-compIntro {\n margin: 0;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n line-height: 20px;\n}\n\n/* 三栏收进同一块面板,读起来是「一组控件」而不是三个飘着的输入框。\n 窄了换行(详情区宽度随窗口变,写死三列会在窄窗口挤成一团)。 */\n.dshr-compForm {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 14px 16px;\n padding: 14px 16px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compField {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.dshr-compFieldHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.dshr-compFieldLabel {\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n font-weight: 500;\n line-height: 18px;\n}\n\n/* 标题右侧的用法要点。语义放这里比塞进顶部那段长文字更容易被读到 ——\n 「模型只列可用的」「连接留空 = 池内任选」都是用这一栏时才关心的。 */\n.dshr-compFieldNote {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n text-align: right;\n}\n\n/* 36px / 12px 圆角,与宿主 Button 原语的 md 尺寸对齐。 */\n.dshr-compSelect {\n width: 100%;\n height: 36px;\n padding: 0 32px 0 11px;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background-color: var(--dsw-alias-bg-layer-2);\n background-image:\n linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-tertiary) 50%),\n linear-gradient(135deg, var(--dsw-alias-label-tertiary) 50%, transparent 50%);\n background-position:\n calc(100% - 17px) 16px,\n calc(100% - 12px) 16px;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n text-overflow: ellipsis;\n cursor: pointer;\n}\n\n.dshr-compSelect:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3);\n}\n\n.dshr-compSelect:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);\n}\n\n.dshr-compSelect:disabled {\n border-color: var(--dsw-alias-border-l1);\n background-color: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-tertiary);\n cursor: not-allowed;\n}\n\n/* 动作行:把按钮与它旁边的提示排在一条基线上。两个动作行(访问测试 / 契约体检)\n 共用 `dshr-compBtnRow` —— 那条抬特异性的字色规则就挂在它上面(见下)。 */\n.dshr-compActions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dshr-compHint {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* 结果区:成功/失败/请求失败 三种语气。底下垫一层极淡的状态色,比只有一行\n 彩色文字更容易扫到 —— 自检的结果是这一页唯一重要的信息。 */\n.dshr-compNotice {\n display: flex;\n gap: 10px;\n padding: 12px 14px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compNotice[data-tone=\"ok\"] {\n border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 34%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);\n}\n\n.dshr-compNotice[data-tone=\"fail\"],\n.dshr-compNotice[data-tone=\"network\"] {\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n}\n\n.dshr-compNoticeIcon {\n flex: none;\n width: 16px;\n height: 16px;\n margin-top: 2px;\n color: var(--dsw-alias-state-success-secondary);\n}\n\n.dshr-compNotice[data-tone=\"fail\"] .dshr-compNoticeIcon,\n.dshr-compNotice[data-tone=\"network\"] .dshr-compNoticeIcon {\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dshr-compNoticeBody {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.dshr-compNoticeTitle {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compNoticeText {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n line-height: 19px;\n /* 上游可能回一整段 HTML/JSON,限定宽度换行,别把布局撑破 */\n overflow-wrap: anywhere;\n}\n\n/* 回显测的是哪三样。一条「通过」如果不带对象,就是没法归因的一串绿字。 */\n.dshr-compNoticeMeta {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n overflow-wrap: anywhere;\n}\n\n/* 宿主 Button 原语的 primary 想把字色设成 --dsw-alias-label-primary-foreground,\n 但上面那条兜底 `.dshr-settings button { color: inherit; font: inherit }`\n 特异性 (1,1) **高于**原语的 `.primary` (1,0) —— 赢的是它。于是按钮变成\n 「#0f1115 的底 + 继承来的 #0f1115 的字」,黑底黑字、按钮看着是空的。\n\n 按本文件既有的抬特异性办法修(见文件里 .dshr-tab-on / .dshr-period 那几处注释:\n 凡是受这条兜底影响的规则,都必须带挂载容器抬到 (2,0) 以上)。这里\n `.dshr-comp .dshr-compBtnRow button` 是 (2,1),不依赖源码顺序,以后有人挪块也不会翻车。\n\n 作用域挂在**动作行的共用类 `dshr-compBtnRow`** 上,不是某个具体容器:上一版这条\n 只写了 `.dshr-compActions`,随后新增的「契约体检」按钮在另一个容器里,于是又变回\n 黑底黑字。现在两个动作行共用这个类,再加按钮只需沿用它。\n\n 修的是**把原语的意图还回去**,不是自己另挑一个颜色:字色仍由原语那个 token 决定。 */\n.dshr-comp .dshr-compBtnRow button {\n color: var(--dsw-alias-label-primary-foreground);\n font-size: 14px;\n line-height: 22px;\n}\n\n/* --- 契约体检区(插件自检面板的第二块) ---\n 状态靠 data-state 上色,和「结果卡」那套语气一致:过了是绿、失败是红、\n 没实现是灰、不执行是中性(它是**故意**不跑的,不是问题)。 */\n\n.dshr-compSection {\n display: flex;\n flex-direction: column;\n gap: 12px;\n margin-top: 4px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l1);\n}\n\n.dshr-compHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.dshr-compSectionTitle {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compSectionNote {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compProbeRow {\n display: flex;\n align-items: flex-end;\n gap: 12px;\n}\n\n.dshr-compProbeRow .dshr-compField {\n flex: 1;\n}\n\n.dshr-compProbeBtn {\n flex: none;\n}\n\n.dshr-compReport {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.dshr-compReportHead {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.dshr-compReportName {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.dshr-compReportSum {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compMembers {\n display: flex;\n flex-direction: column;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshr-compMember {\n display: grid;\n grid-template-columns: 16px minmax(0, auto) minmax(0, 1fr);\n align-items: baseline;\n gap: 10px;\n padding: 5px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l1);\n}\n\n.dshr-compMember:last-child {\n border-bottom: 0;\n}\n\n.dshr-compMemberState {\n flex: none;\n text-align: center;\n font-size: 12px;\n line-height: 18px;\n}\n\n.dshr-compMember[data-state=\"ok\"] .dshr-compMemberState {\n color: var(--dsw-alias-state-success-secondary);\n}\n\n.dshr-compMember[data-state=\"fail\"] .dshr-compMemberState {\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dshr-compMember[data-state=\"absent\"] .dshr-compMemberState,\n.dshr-compMember[data-state=\"skipped\"] .dshr-compMemberState {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dshr-compMemberName {\n color: var(--dsw-alias-label-primary);\n font-size: 12.5px;\n line-height: 19px;\n white-space: nowrap;\n}\n\n.dshr-compMemberKey {\n margin-left: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n}\n\n.dshr-compMemberDetail {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n overflow-wrap: anywhere;\n}\n\n\n/*$vite$:1*/")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
2
2
  window.__ModuleLoader__.load({
3
3
  id: "dsh-external/dsh-router",
4
4
  factory: (require) => {
@@ -1618,14 +1618,13 @@ window.__ModuleLoader__.load({
1618
1618
  className: "dshr-requireDesc",
1619
1619
  children: "不带有效 key 的请求将被拒绝"
1620
1620
  })]
1621
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1622
- type: "button",
1623
- className: `dshr-toggle ${requireApiKey ? "dshr-toggle-on" : ""}`,
1624
- role: "switch",
1625
- "aria-checked": requireApiKey,
1626
- onClick: () => void toggleRequire(!requireApiKey),
1621
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
1622
+ checked: requireApiKey,
1623
+ label: "Require API key 开关",
1627
1624
  title: requireApiKey ? "关闭鉴权" : "开启鉴权",
1628
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshr-toggleKnob" })
1625
+ onChange: (next) => {
1626
+ toggleRequire(next);
1627
+ }
1629
1628
  })]
1630
1629
  }),
1631
1630
  keys === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1725,14 +1724,13 @@ window.__ModuleLoader__.load({
1725
1724
  ]
1726
1725
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1727
1726
  className: "dshr-keyOps",
1728
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1729
- type: "button",
1730
- className: `dshr-toggle ${key.isActive ? "dshr-toggle-on" : ""}`,
1731
- role: "switch",
1732
- "aria-checked": key.isActive,
1727
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
1728
+ checked: key.isActive,
1729
+ label: `${key.name} 开关`,
1733
1730
  title: key.isActive ? "暂停 key" : "恢复 key",
1734
- onClick: () => onKeySwitch(key, !key.isActive),
1735
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshr-toggleKnob" })
1731
+ onChange: (next) => {
1732
+ onKeySwitch(key, next);
1733
+ }
1736
1734
  })
1737
1735
  })]
1738
1736
  }, key.id))
@@ -3306,6 +3304,53 @@ window.__ModuleLoader__.load({
3306
3304
  }
3307
3305
  //#endregion
3308
3306
  //#region src/client/ExtDetail.tsx
3307
+ /**
3308
+ * 扩展详情页 —— 从设置 → 路由 → 扩展 的卡片点进来。
3309
+ *
3310
+ * **总开关不在这一层。** 启停的唯一入口是官方插件页(设置 → 插件 →
3311
+ * dsh-router-core 详情页的「路由组件」一节),那里直接调核心的
3312
+ * `PATCH /router/api/ext`,自检与持久化都由核心裁决。同一件事只留一个开关,
3313
+ * 否则两处状态各说各话。
3314
+ *
3315
+ * **但扩展自带的子开关在这一层**(`item.controls`,如准则的每一条):它管的是
3316
+ * 扩展自己的**行为细节**,没有第二处地方可放 —— 插件页那一行只有宿主管的
3317
+ * 总开关。走**同一个** PATCH 端点、同一个 `data` 落盘,不存在"两处状态各说
3318
+ * 各话"的问题(那正是总开关要单点的原因)。
3319
+ *
3320
+ * 列表页只列**已启用**的扩展(关闭的连卡片都不出现),所以这里也不必再显示
3321
+ * 「已启用/未启用」——那行字只能是常量。布局同供应商详情:返回链接 + 图标 +
3322
+ * 名称/副行,下面跟扩展说明卡与未就绪横幅。有 `api()` 支持的自定义扩展可在未来
3323
+ * 按需加独立面板,此处不内置任何具体扩展的专属视图。
3324
+ */
3325
+ /**
3326
+ * 服务端的 `error` 是**给调试看的**(`'controlId must be a non-empty string'`、
3327
+ * `'reorder failed'`…),直接摆到页面上等于把 API 内部话漏给用户。
3328
+ *
3329
+ * 做法:**先认自己那几个已知中文错误**(核心对"为什么拒"给的是中文),其余一律
3330
+ * 回落到调用方给的兜底文案。宁可少说一句,也不要给一句看不懂的。
3331
+ */
3332
+ function friendly(serverError, fallback) {
3333
+ if (serverError === void 0 || serverError === "") return fallback;
3334
+ if (/[\u4e00-\u9fa5]/.test(serverError)) return serverError;
3335
+ return fallback;
3336
+ }
3337
+ /** 行内图标按钮里的图标(照 `CombosTab` 的同款路径与线宽,1.8px 描边)。 */
3338
+ function RowIcon$1({ d, size = 15 }) {
3339
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3340
+ viewBox: "0 0 24 24",
3341
+ width: size,
3342
+ height: size,
3343
+ fill: "none",
3344
+ stroke: "currentColor",
3345
+ strokeWidth: "1.8",
3346
+ strokeLinecap: "round",
3347
+ strokeLinejoin: "round",
3348
+ "aria-hidden": "true",
3349
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
3350
+ });
3351
+ }
3352
+ /** 铅笔图标 —— 与 `CombosTab.tsx` / `SupplierDetail.tsx` 的 `I.edit` **逐字相同**。 */
3353
+ var I_EDIT = "M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3zM13.5 6.5l3 3";
3309
3354
  function Icon$1({ d, size = 18 }) {
3310
3355
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3311
3356
  viewBox: "0 0 24 24",
@@ -3324,6 +3369,570 @@ window.__ModuleLoader__.load({
3324
3369
  back: "M19 12H5M12 19l-7-7 7-7",
3325
3370
  bolt: "M13 2L4 14h6l-1 8 9-12h-6z"
3326
3371
  };
3372
+ /** 「+ 添加准则」弹窗 —— 标题 + 正文,必填。 */
3373
+ function AddControlModal({ onClose, onSave, busy }) {
3374
+ const [title, setTitle] = (0, react.useState)("");
3375
+ const [body, setBody] = (0, react.useState)("");
3376
+ const valid = title.trim() !== "" && body.trim() !== "";
3377
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Modal, {
3378
+ title: "添加准则",
3379
+ onClose,
3380
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3381
+ className: "dshr-modalForm",
3382
+ children: [
3383
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3384
+ className: "dshr-requireTitle",
3385
+ htmlFor: "dshr-add-title",
3386
+ children: "标题"
3387
+ }),
3388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3389
+ id: "dshr-add-title",
3390
+ className: "dshr-input",
3391
+ value: title,
3392
+ onChange: (e) => {
3393
+ setTitle(e.target.value);
3394
+ },
3395
+ autoFocus: true
3396
+ }),
3397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3398
+ className: "dshr-requireTitle",
3399
+ htmlFor: "dshr-add-body",
3400
+ children: "内容"
3401
+ }),
3402
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3403
+ id: "dshr-add-body",
3404
+ className: "dshr-input",
3405
+ style: {
3406
+ minHeight: 140,
3407
+ resize: "vertical",
3408
+ fontFamily: "inherit",
3409
+ lineHeight: 1.6
3410
+ },
3411
+ value: body,
3412
+ onChange: (e) => {
3413
+ setBody(e.target.value);
3414
+ }
3415
+ }),
3416
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3417
+ className: "dshr-modalActions",
3418
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3419
+ type: "button",
3420
+ className: "dshr-miniButton",
3421
+ onClick: onClose,
3422
+ disabled: busy,
3423
+ children: "取消"
3424
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3425
+ type: "button",
3426
+ className: "dshr-primaryButton",
3427
+ disabled: busy || !valid,
3428
+ title: valid ? "添加" : "标题与内容都不能为空",
3429
+ onClick: () => {
3430
+ onSave(title.trim(), body.trim());
3431
+ },
3432
+ children: busy ? "添加中…" : "添加"
3433
+ })]
3434
+ })
3435
+ ]
3436
+ })
3437
+ });
3438
+ }
3439
+ /**
3440
+ * 「修改」弹窗 —— 改一个子开关的标题与正文(2026-09-29)。
3441
+ *
3442
+ * ⚠️ **改完必须真的进 prompt**,否则这就是"只改了显示"的假编辑
3443
+ * (服务端 `setControlText` + 渲染层的 `effectiveText` 是这件事的另一半)。
3444
+ *
3445
+ * ⚠️ **空标题 / 空正文不允许提交**(按钮禁用 + 服务端也拒):空标题让这一行没
3446
+ * 名字,空正文让这条规则凭空消失 —— 两者都像"被关了"而不是"被改坏了"。
3447
+ */
3448
+ function EditControlModal({ control, onClose, onSave, onReset, onRemove, busy }) {
3449
+ const [title, setTitle] = (0, react.useState)(control.title);
3450
+ const [body, setBody] = (0, react.useState)(control.body ?? "");
3451
+ const trimmedTitle = title.trim();
3452
+ const trimmedBody = body.trim();
3453
+ const valid = trimmedTitle !== "" && trimmedBody !== "";
3454
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Modal, {
3455
+ title: `修改:${control.title}`,
3456
+ onClose,
3457
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3458
+ className: "dshr-modalForm",
3459
+ children: [
3460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3461
+ className: "dshr-requireTitle",
3462
+ htmlFor: `dshr-ctl-title-${control.id}`,
3463
+ children: "标题"
3464
+ }),
3465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3466
+ id: `dshr-ctl-title-${control.id}`,
3467
+ className: "dshr-input",
3468
+ value: title,
3469
+ onChange: (e) => {
3470
+ setTitle(e.target.value);
3471
+ },
3472
+ autoFocus: true
3473
+ }),
3474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3475
+ className: "dshr-requireTitle",
3476
+ htmlFor: `dshr-ctl-body-${control.id}`,
3477
+ children: "内容"
3478
+ }),
3479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3480
+ id: `dshr-ctl-body-${control.id}`,
3481
+ className: "dshr-input",
3482
+ style: {
3483
+ minHeight: 160,
3484
+ resize: "vertical",
3485
+ fontFamily: "inherit",
3486
+ lineHeight: 1.6
3487
+ },
3488
+ value: body,
3489
+ onChange: (e) => {
3490
+ setBody(e.target.value);
3491
+ }
3492
+ }),
3493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3494
+ className: "dshr-compHint",
3495
+ children: "改完立刻生效于 system prompt。此处只存**改过的部分**;未改的分类仍随内置版本更新。"
3496
+ }),
3497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3498
+ className: "dshr-modalActions",
3499
+ children: [
3500
+ control.custom === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3501
+ type: "button",
3502
+ className: "dshr-dangerButton",
3503
+ disabled: busy,
3504
+ onClick: onRemove,
3505
+ children: "删除这条"
3506
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3507
+ type: "button",
3508
+ className: "dshr-miniButton",
3509
+ disabled: busy || control.overridden !== true,
3510
+ title: control.overridden === true ? "还原成内置内容" : "这条没被改过,无需还原",
3511
+ onClick: onReset,
3512
+ children: "还原成内置"
3513
+ }),
3514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
3515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3516
+ type: "button",
3517
+ className: "dshr-miniButton",
3518
+ onClick: onClose,
3519
+ disabled: busy,
3520
+ children: "取消"
3521
+ }),
3522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3523
+ type: "button",
3524
+ className: "dshr-primaryButton",
3525
+ disabled: busy || !valid,
3526
+ title: valid ? "保存" : "标题与内容都不能为空",
3527
+ onClick: () => {
3528
+ const patch = {};
3529
+ if (trimmedTitle !== control.title) patch.title = trimmedTitle;
3530
+ if (trimmedBody !== control.body) patch.body = trimmedBody;
3531
+ onSave(patch);
3532
+ },
3533
+ children: busy ? "保存中…" : "保存"
3534
+ })
3535
+ ]
3536
+ })
3537
+ ]
3538
+ })
3539
+ });
3540
+ }
3541
+ /**
3542
+ * 扩展自带的子开关(如准则的每一条)。
3543
+ *
3544
+ * ⚠️ **失败时回读真值,不留乐观假象**(与 `RouterComponentsSection.toggle` 同款
3545
+ * 纪律):乐观更新看着顺,但核心拒了(扩展未就绪 / 未知 controlId / 网络失败)
3546
+ * 时,开关会停在一个「看起来成功了」的状态,而 prompt 里其实没变。
3547
+ *
3548
+ * ⚠️ **总开关关时显示为禁用而不是隐藏**:隐藏会让用户以为这些分类不存在。
3549
+ * 它们的值也没被清掉(总开关只叠加失效),所以重新打开总开关就恢复原样。
3550
+ */
3551
+ function ExtControls({ item }) {
3552
+ const [rows, setRows] = (0, react.useState)(() => [...item.controls ?? []]);
3553
+ const controls = rows;
3554
+ const [state, setState] = (0, react.useState)(() => Object.fromEntries(controls.map((c) => [c.id, c.on])));
3555
+ const [busy, setBusy] = (0, react.useState)("");
3556
+ const [error, setError] = (0, react.useState)("");
3557
+ (0, react.useEffect)(() => {
3558
+ setRows([...item.controls ?? []]);
3559
+ }, [
3560
+ item.id,
3561
+ item.enabled,
3562
+ (item.controls ?? []).length
3563
+ ]);
3564
+ (0, react.useEffect)(() => {
3565
+ setState(Object.fromEntries(controls.map((c) => [c.id, c.on])));
3566
+ }, [
3567
+ item.id,
3568
+ item.enabled,
3569
+ controls.length
3570
+ ]);
3571
+ const toggle = async (id, next) => {
3572
+ setBusy(id);
3573
+ setError("");
3574
+ setState((s) => ({
3575
+ ...s,
3576
+ [id]: next
3577
+ }));
3578
+ try {
3579
+ const data = await (await fetch(`${ROUTER_API_BASE}/ext`, {
3580
+ method: "PATCH",
3581
+ headers: { "Content-Type": "application/json" },
3582
+ body: JSON.stringify({
3583
+ id: item.id,
3584
+ controlId: id,
3585
+ on: next
3586
+ }),
3587
+ cache: "no-store"
3588
+ })).json();
3589
+ if (data.ok === true) {
3590
+ const self = data.enhancers?.find((e) => e.id === item.id);
3591
+ if (self?.controls) setState(Object.fromEntries(self.controls.map((c) => [c.id, c.on])));
3592
+ return;
3593
+ }
3594
+ setError(data.error ?? "切换失败");
3595
+ } catch {
3596
+ setError("切换失败(网络)");
3597
+ } finally {
3598
+ try {
3599
+ const self = (await (await fetch(`${ROUTER_API_BASE}/ext`, { cache: "no-store" })).json()).enhancers?.find((e) => e.id === item.id);
3600
+ if (self?.controls) setState(Object.fromEntries(self.controls.map((c) => [c.id, c.on])));
3601
+ } catch {}
3602
+ setBusy("");
3603
+ }
3604
+ };
3605
+ const disabled = busy !== "";
3606
+ const withBody = controls.filter((c) => typeof c.body === "string" && c.body !== "");
3607
+ const [editing, setEditing] = (0, react.useState)(null);
3608
+ const [removing, setRemoving] = (0, react.useState)(null);
3609
+ const [adding, setAdding] = (0, react.useState)(false);
3610
+ const [dragIndex, setDragIndex] = (0, react.useState)(null);
3611
+ (0, react.useEffect)(() => {
3612
+ if (error === "") return void 0;
3613
+ const t = window.setTimeout(() => {
3614
+ setError("");
3615
+ }, 2500);
3616
+ return () => {
3617
+ window.clearTimeout(t);
3618
+ };
3619
+ }, [error]);
3620
+ const [titles, setTitles] = (0, react.useState)(() => Object.fromEntries((item.controls ?? []).map((c) => [c.id, c.title])));
3621
+ /**
3622
+ * 发一个 `op` 动作并**回读真值**。
3623
+ *
3624
+ * ⚠️ 成功与失败都回读:乐观更新看着顺,但服务端拒了(删内置条目 / 还原没改过的)
3625
+ * 时,面板会停在「以为改了却没生效」的状态。
3626
+ */
3627
+ const act = async (payload, onDone) => {
3628
+ setBusy("op");
3629
+ setError("");
3630
+ try {
3631
+ const data = await (await fetch(`${ROUTER_API_BASE}/ext`, {
3632
+ method: "PATCH",
3633
+ headers: { "Content-Type": "application/json" },
3634
+ body: JSON.stringify({
3635
+ id: item.id,
3636
+ ...payload
3637
+ }),
3638
+ cache: "no-store"
3639
+ })).json();
3640
+ if (data.ok !== true) {
3641
+ setError(friendly(data.error, "操作失败"));
3642
+ return;
3643
+ }
3644
+ const self = data.enhancers?.find((e) => e.id === item.id);
3645
+ if (self?.controls) {
3646
+ setState(Object.fromEntries(self.controls.map((c) => [c.id, c.on])));
3647
+ setTitles(Object.fromEntries(self.controls.map((c) => [c.id, c.title])));
3648
+ setRows(self.controls);
3649
+ }
3650
+ onDone?.();
3651
+ } catch {
3652
+ setError("操作失败(网络)");
3653
+ } finally {
3654
+ setBusy("");
3655
+ }
3656
+ };
3657
+ /** 拖动后落盘顺序;失败回读真值。 */
3658
+ const persist = async (ids) => {
3659
+ setError("");
3660
+ try {
3661
+ const data = await (await fetch(`${ROUTER_API_BASE}/ext`, {
3662
+ method: "PATCH",
3663
+ headers: { "Content-Type": "application/json" },
3664
+ body: JSON.stringify({
3665
+ id: item.id,
3666
+ op: "reorder",
3667
+ ids
3668
+ }),
3669
+ cache: "no-store"
3670
+ })).json();
3671
+ if (data.ok !== true) setError(friendly(data.error, "排序保存失败"));
3672
+ const self = data.enhancers?.find((e) => e.id === item.id);
3673
+ if (self?.controls) setRows(self.controls);
3674
+ } catch {
3675
+ setError("排序保存失败(网络)");
3676
+ try {
3677
+ const self = (await (await fetch(`${ROUTER_API_BASE}/ext`, { cache: "no-store" })).json()).enhancers?.find((e) => e.id === item.id);
3678
+ if (self?.controls) setRows(self.controls);
3679
+ } catch {}
3680
+ }
3681
+ };
3682
+ const save = async (controlId, patch) => {
3683
+ setBusy(controlId);
3684
+ setError("");
3685
+ try {
3686
+ const data = await (await fetch(`${ROUTER_API_BASE}/ext`, {
3687
+ method: "PATCH",
3688
+ headers: { "Content-Type": "application/json" },
3689
+ body: JSON.stringify({
3690
+ id: item.id,
3691
+ controlId,
3692
+ ...patch
3693
+ }),
3694
+ cache: "no-store"
3695
+ })).json();
3696
+ if (data.ok !== true) {
3697
+ setError(friendly(data.error, "保存失败"));
3698
+ return;
3699
+ }
3700
+ const self = data.enhancers?.find((e) => e.id === item.id);
3701
+ if (self?.controls) setState(Object.fromEntries(self.controls.map((c) => [c.id, c.on])));
3702
+ if (self?.controls) setTitles(Object.fromEntries(self.controls.map((c) => [c.id, c.title])));
3703
+ setEditing(null);
3704
+ } catch {
3705
+ setError("保存失败(网络)");
3706
+ } finally {
3707
+ setBusy("");
3708
+ }
3709
+ };
3710
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3711
+ className: "dshr-card",
3712
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3713
+ className: "dshr-compGroup dshr-compGroupTight",
3714
+ children: [
3715
+ item.description !== void 0 && item.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3716
+ className: "dshr-compRow",
3717
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3718
+ className: "dshr-compTitle",
3719
+ children: item.description
3720
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3721
+ type: "button",
3722
+ className: "dshr-primaryButton",
3723
+ onClick: () => {
3724
+ setAdding(true);
3725
+ },
3726
+ style: { marginLeft: "auto" },
3727
+ children: "+ 添加准则"
3728
+ })]
3729
+ }),
3730
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3731
+ className: "dshr-compRows",
3732
+ children: controls.map((c, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
3733
+ className: `dshr-compRow${dragIndex === index ? " dshr-linkRowDragging" : ""}`,
3734
+ draggable: true,
3735
+ onDragStart: (e) => {
3736
+ setDragIndex(index);
3737
+ e.dataTransfer.effectAllowed = "move";
3738
+ },
3739
+ onDragOver: (e) => {
3740
+ e.preventDefault();
3741
+ e.dataTransfer.dropEffect = "move";
3742
+ },
3743
+ onDrop: (e) => {
3744
+ e.preventDefault();
3745
+ if (dragIndex === null || dragIndex === index) {
3746
+ setDragIndex(null);
3747
+ return;
3748
+ }
3749
+ const next = [...controls];
3750
+ const [moved] = next.splice(dragIndex, 1);
3751
+ if (moved !== void 0) next.splice(index, 0, moved);
3752
+ setDragIndex(null);
3753
+ persist(next.map((x) => x.id));
3754
+ },
3755
+ onDragEnd: () => {
3756
+ setDragIndex(null);
3757
+ },
3758
+ children: [
3759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3760
+ className: "dshr-linkGrip",
3761
+ title: "拖动排序",
3762
+ "aria-hidden": "true",
3763
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
3764
+ viewBox: "0 0 24 24",
3765
+ width: "14",
3766
+ height: "14",
3767
+ fill: "currentColor",
3768
+ children: [
3769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3770
+ cx: "9",
3771
+ cy: "6",
3772
+ r: "1.5"
3773
+ }),
3774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3775
+ cx: "15",
3776
+ cy: "6",
3777
+ r: "1.5"
3778
+ }),
3779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3780
+ cx: "9",
3781
+ cy: "12",
3782
+ r: "1.5"
3783
+ }),
3784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3785
+ cx: "15",
3786
+ cy: "12",
3787
+ r: "1.5"
3788
+ }),
3789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3790
+ cx: "9",
3791
+ cy: "18",
3792
+ r: "1.5"
3793
+ }),
3794
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3795
+ cx: "15",
3796
+ cy: "18",
3797
+ r: "1.5"
3798
+ })
3799
+ ]
3800
+ })
3801
+ }),
3802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3803
+ className: "dshr-compRowMain",
3804
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3805
+ className: "dshr-compRowTitleLine",
3806
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3807
+ className: "dshr-compRowName",
3808
+ children: titles[c.id] ?? c.title
3809
+ }), c.editable === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3810
+ type: "button",
3811
+ className: "dshr-iconBtn dshr-iconBtn-sm dshr-compRowAct",
3812
+ "aria-label": `修改「${titles[c.id] ?? c.title}」`,
3813
+ title: `修改「${titles[c.id] ?? c.title}」的标题与内容`,
3814
+ onClick: () => {
3815
+ setEditing(c.id);
3816
+ },
3817
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowIcon$1, { d: I_EDIT })
3818
+ })]
3819
+ }), typeof c.body === "string" && c.body !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3820
+ className: "dshr-compRowState",
3821
+ children: c.body
3822
+ })]
3823
+ }),
3824
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
3825
+ checked: state[c.id] === true,
3826
+ disabled: disabled || item.enabled !== true,
3827
+ label: `${c.title} 开关`,
3828
+ title: item.enabled !== true ? "扩展总开关已关闭,子开关暂不生效" : state[c.id] === true ? `关闭 ${c.title}` : `开启 ${c.title}`,
3829
+ onChange: (next) => {
3830
+ toggle(c.id, next);
3831
+ }
3832
+ })
3833
+ ]
3834
+ }, c.id))
3835
+ }),
3836
+ withBody.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3837
+ className: "dshr-compHint",
3838
+ children: "本扩展没有提供原文。"
3839
+ }),
3840
+ adding && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddControlModal, {
3841
+ busy: busy === "add",
3842
+ onClose: () => {
3843
+ setAdding(false);
3844
+ },
3845
+ onSave: (t, b) => {
3846
+ act({
3847
+ op: "add",
3848
+ title: t,
3849
+ body: b
3850
+ }, () => {
3851
+ setAdding(false);
3852
+ });
3853
+ }
3854
+ }),
3855
+ removing !== null && (() => {
3856
+ const target = controls.find((c) => c.id === removing);
3857
+ if (target === void 0) return null;
3858
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Modal, {
3859
+ title: "删除自定义准则",
3860
+ onClose: () => {
3861
+ setRemoving(null);
3862
+ },
3863
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3864
+ className: "dshr-modalForm",
3865
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3866
+ className: "dshr-muted",
3867
+ children: [
3868
+ "确定删除「",
3869
+ titles[target.id] ?? target.title,
3870
+ "」吗?此操作不可撤销。"
3871
+ ]
3872
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3873
+ className: "dshr-modalActions",
3874
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3875
+ type: "button",
3876
+ className: "dshr-miniButton",
3877
+ onClick: () => {
3878
+ setRemoving(null);
3879
+ },
3880
+ children: "取消"
3881
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3882
+ type: "button",
3883
+ className: "dshr-dangerButton",
3884
+ disabled: busy !== "",
3885
+ onClick: () => {
3886
+ act({
3887
+ op: "remove",
3888
+ controlId: target.id
3889
+ }, () => {
3890
+ setRemoving(null);
3891
+ });
3892
+ },
3893
+ children: "删除"
3894
+ })]
3895
+ })]
3896
+ })
3897
+ });
3898
+ })(),
3899
+ editing !== null && (() => {
3900
+ const target = controls.find((c) => c.id === editing);
3901
+ if (target === void 0) return null;
3902
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditControlModal, {
3903
+ control: {
3904
+ ...target,
3905
+ title: titles[target.id] ?? target.title
3906
+ },
3907
+ busy: busy === editing,
3908
+ onClose: () => {
3909
+ setEditing(null);
3910
+ },
3911
+ onSave: (patch) => {
3912
+ save(editing, patch);
3913
+ },
3914
+ onReset: () => {
3915
+ act({
3916
+ op: "reset",
3917
+ controlId: editing
3918
+ }, () => {
3919
+ setEditing(null);
3920
+ });
3921
+ },
3922
+ onRemove: () => {
3923
+ setEditing(null);
3924
+ setRemoving(editing);
3925
+ }
3926
+ });
3927
+ })()
3928
+ ]
3929
+ }), error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3930
+ className: "dshr-toastWrap",
3931
+ role: "status",
3932
+ children: error
3933
+ })]
3934
+ });
3935
+ }
3327
3936
  function ExtDetail({ item, onBack }) {
3328
3937
  const Custom = extPanel(item.id);
3329
3938
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3371,14 +3980,7 @@ window.__ModuleLoader__.load({
3371
3980
  })]
3372
3981
  })]
3373
3982
  })]
3374
- }), Custom !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Custom, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.description !== void 0 && item.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3375
- className: "dshr-card",
3376
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3377
- className: "dshr-muted",
3378
- style: { padding: "12px 14px" },
3379
- children: item.description
3380
- })
3381
- }), item.ready === false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3983
+ }), Custom !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Custom, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.controls !== void 0 && item.controls.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExtControls, { item }), item.ready === false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3382
3984
  className: "dshr-alert",
3383
3985
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "未就绪" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.detail !== void 0 && item.detail !== "" ? item.detail : "当前不可用" })]
3384
3986
  })] })]
@@ -3913,7 +4515,7 @@ window.__ModuleLoader__.load({
3913
4515
  };
3914
4516
  }
3915
4517
  //#endregion
3916
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/typeof.js
4518
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/typeof.js
3917
4519
  function _typeof(o) {
3918
4520
  "@babel/helpers - typeof";
3919
4521
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -3923,7 +4525,7 @@ window.__ModuleLoader__.load({
3923
4525
  }, _typeof(o);
3924
4526
  }
3925
4527
  //#endregion
3926
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/toPrimitive.js
4528
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/toPrimitive.js
3927
4529
  function toPrimitive(t, r) {
3928
4530
  if ("object" != _typeof(t) || !t) return t;
3929
4531
  var e = t[Symbol.toPrimitive];
@@ -3935,13 +4537,13 @@ window.__ModuleLoader__.load({
3935
4537
  return ("string" === r ? String : Number)(t);
3936
4538
  }
3937
4539
  //#endregion
3938
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/toPropertyKey.js
4540
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/toPropertyKey.js
3939
4541
  function toPropertyKey(t) {
3940
4542
  var i = toPrimitive(t, "string");
3941
4543
  return "symbol" == _typeof(i) ? i : i + "";
3942
4544
  }
3943
4545
  //#endregion
3944
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/defineProperty.js
4546
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/defineProperty.js
3945
4547
  function _defineProperty(e, r, t) {
3946
4548
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
3947
4549
  value: t,
@@ -5200,34 +5802,17 @@ window.__ModuleLoader__.load({
5200
5802
  * (kind: list, scope: session —— 即宿主 composer 卡片底部操作行
5201
5803
  * `.uV2eYG_dock` 里、原生上下文环旁边的位置)。
5202
5804
  * scope 为 session 时宿主给组件注入 `sessionId`,据此按**当前会话**取命中。
5203
- *
5204
- * **仅宿主 >= 0.1.7 才挂**:composer.dock 这个座位在 0.1.5 也在,但渲染在
5205
- * InputBar 之后的独立块(位置不对),只有 0.1.7 才放进底部操作行。座位名相同
5206
- * 没法靠契约区分,故先问核心 `/router/api/health` 的 `lastHitDock` 支持位。
5207
- * @returns 清理函数;不支持或宿主无 slots 时为空操作
5805
+ * @returns 清理函数;宿主没给 slots 时为空操作
5208
5806
  */
5209
- function mountLastHitDock(ctx, supported) {
5807
+ function mountLastHitDock(ctx) {
5210
5808
  const slots = ctx.slots;
5211
- if (slots === void 0 || !supported) return () => {};
5809
+ if (slots === void 0) return () => {};
5212
5810
  return slots.inject("conversation.composer.dock", () => slots.register({
5213
5811
  name: "conversation.composer.dock",
5214
5812
  id: "dsh-router-last-hit",
5215
5813
  order: 10
5216
5814
  }, LastHitDock));
5217
5815
  }
5218
- /**
5219
- * 问核心是否支持「最近命中」徽章(宿主 >= 0.1.7)。
5220
- * 网络/解析失败一律当**不支持**——宁可不显示,也不在 0.1.5 上摆错位置。
5221
- */
5222
- async function fetchLastHitDockSupport() {
5223
- try {
5224
- const res = await fetch(`${ROUTER_API_BASE}/health`);
5225
- if (!res.ok) return false;
5226
- return (await res.json()).lastHitDock === true;
5227
- } catch {
5228
- return false;
5229
- }
5230
- }
5231
5816
  /** 挂载「设置 → 路由」页;拿不到 slots 时返回 undefined 表示跳过。 */
5232
5817
  function mountSettingsSection(ctx) {
5233
5818
  const slots = ctx.slots;
@@ -5270,9 +5855,7 @@ window.__ModuleLoader__.load({
5270
5855
  ctx.effect(() => dispose, "dsh-router: ext test panel registry");
5271
5856
  }
5272
5857
  function apply(ctx) {
5273
- fetchLastHitDockSupport().then((supported) => {
5274
- ctx.effect(() => mountLastHitDock(ctx, supported), "dsh-router: last-hit dock");
5275
- });
5858
+ ctx.effect(() => mountLastHitDock(ctx), "dsh-router: last-hit dock");
5276
5859
  const disposeSettings = mountSettingsSection(ctx);
5277
5860
  if (disposeSettings === void 0) {
5278
5861
  console.warn("[dsh-router] 宿主未提供 slots,回退到侧边栏面板");