dsh-router-core 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/docs/ext.md +224 -0
- package/docs/pool-sticky-block.md +257 -0
- package/lib/client-registry.js +267 -95
- package/lib/client.js +267 -95
- package/lib/index.js +416 -13
- package/lib/types/ext/contract.d.ts +95 -0
- package/lib/types/ext/store.d.ts +16 -0
- package/lib/types/llm/adapter.d.ts +9 -0
- package/lib/types/router/account-pool.d.ts +58 -2
- package/lib/types/router/prefix-affinity.d.ts +6 -0
- package/lib/types/router/usage-store.d.ts +8 -0
- package/lib/types/shared.d.ts +18 -0
- package/package.json +5 -5
- package/src/client/ExtTab.tsx +143 -0
- package/src/client/RouterView.tsx +10 -2
- package/src/client/SupplierDetail.tsx +1 -1
- package/src/client/router.css +19 -0
- package/src/ext/contract.ts +115 -0
- package/src/ext/store.test.ts +144 -0
- package/src/ext/store.ts +125 -0
- package/src/index.ts +71 -0
- package/src/llm/adapter.ts +22 -5
- package/src/router/account-pool.test.ts +244 -1
- package/src/router/account-pool.ts +180 -4
- package/src/router/index.ts +25 -2
- package/src/router/prefix-affinity.ts +80 -0
- package/src/router/usage-store.ts +8 -0
- package/src/shared.ts +20 -0
package/lib/client.js
CHANGED
|
@@ -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(--ds-font-family-code, 'SFMono-Regular', Consolas, monospace);\n --rs-sans: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);\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 { box-sizing: border-box; }\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 .12s ease;\n}\n\n.dshr-tab:hover { color: var(--rs-text); }\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 { color: var(--rs-text); background: var(--rs-hover); }\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'] { position: relative; }\n.dshDesktopConversationSurface { position: relative; }\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 { isolation: isolate; }\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-dsh-router-view] {\n display: block;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-pane='conversation'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [class*='centerCol'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) .dshDesktopConversationSurface > :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 { box-sizing: border-box; }\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 { color: inherit; font: inherit; }\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 { color: var(--rs-text); background: var(--rs-hover); }\n.dshr-iconButton:disabled { opacity: 0.5; cursor: default; }\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 { background: var(--rs-hover); }\n.dshr-backButton svg { display: block; }\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 { color: var(--rs-danger); font-size: 12px; }\n.dshr-alert span { color: var(--rs-muted); font-size: 12px; }\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 { color: var(--rs-text); }\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/* 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 { background: var(--rs-hover); }\n\n.dshr-mono { font-family: var(--rs-mono); font-size: 11px; }\n.dshr-num { text-align: right; }\n.dshr-muted { color: var(--rs-faint); }\n.dshr-reason { color: var(--rs-muted); font-size: 11px; }\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 { color: var(--rs-success); background: color-mix(in srgb, var(--rs-success) 12%, transparent); }\n.dshr-badge-warn { color: var(--rs-warn); background: color-mix(in srgb, var(--rs-warn) 14%, transparent); }\n.dshr-badge-danger { color: var(--rs-danger); background: color-mix(in srgb, var(--rs-danger) 12%, transparent); }\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 { border-color: var(--rs-line-strong); }\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 { color: var(--rs-text); background: var(--rs-hover); }\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 .12s ease;\n}\n\n.dshr-card-click:hover { border-color: var(--rs-accent); }\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: .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 .12s ease;\n}\n\n.dshr-supplierCard:hover { border-color: var(--rs-accent); }\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: .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 .15s ease;\n}\n\n.dshr-toggle-on { background: var(--rs-ok); }\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 .15s ease;\n}\n\n.dshr-toggle-on .dshr-toggleKnob { transform: translateX(14px); }\n\n/* 操作按钮组 */\n.dshr-ops {\n display: flex;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dshr-danger { color: var(--rs-danger); }\n.dshr-danger:hover { color: var(--rs-danger); border-color: var(--rs-danger); }\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 .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: 0 20px 50px rgb(0 0 0 / .35), 0 0 0 1px rgb(0 0 0 / .05);\n animation: dshrModalIn .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 { color: var(--rs-text); background: var(--rs-hover); }\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 { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dshrModalIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }\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 .12s ease;\n}\n\n.dshr-settings .dshr-primaryButton:hover,\n.dshr-shell .dshr-primaryButton:hover { filter: brightness(1.08); }\n.dshr-settings .dshr-primaryButton:disabled,\n.dshr-shell .dshr-primaryButton:disabled { opacity: .55; cursor: default; }\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 .12s ease;\n}\n\n.dshr-settings .dshr-dangerButton:hover,\n.dshr-shell .dshr-dangerButton:hover { filter: brightness(1.1); }\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(to bottom, color-mix(in srgb, var(--rs-layer-1) 62%, transparent), transparent);\n}\n\n/* Brand 标题更突出 */\n.dshr-brandTitle {\n letter-spacing: .01em;\n}\n\n/* 卡片:阴影 + hover 微抬升 */\n.dshr-card {\n box-shadow: 0 1px 2px rgb(0 0 0 / .04);\n transition: box-shadow .15s ease, transform .15s ease, border-color .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 / .10);\n}\n\n/* Tab:悬停背景 + 计数徽标配色 */\n.dshr-nav button {\n transition: color .12s ease, background .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 color-mix(in srgb, currentColor 12%, transparent);\n}\n\n/* 表格行悬停 + 表头层级 */\n.dshr-table tbody tr { transition: background .1s ease; }\n.dshr-table th { letter-spacing: .02em; }\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 { transition: background .15s ease, box-shadow .15s ease; }\n.dshr-toggle:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent); }\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: color .12s ease, background .12s ease;\n}\n\n.dshr-iconBtn:hover { color: var(--rs-accent); background: var(--rs-hover); }\n\n.dshr-iconBtn svg { display: block; }\n\n.dshr-iconBtn-sm { width: 26px; height: 26px; }\n\n/* 禁用态必须看得出来:纯图标按钮没有文字可读(不像「刷新中…」),\n 只靠 hover 反馈会让人以为还能点。 */\n.dshr-iconBtn:disabled { opacity: .4; cursor: default; }\n\n.dshr-iconBtn:disabled:hover { color: var(--rs-muted); background: transparent; }\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 .12s ease;\n}\n\n.dshr-keyRow:last-child { border-bottom: none; }\n.dshr-keyRow-paused { opacity: .6; }\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 { color: var(--rs-warn); }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-keyRow:hover .dshr-keyDelete { opacity: 1; }\n.dshr-keyDelete:focus-visible { opacity: 1; }\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 { padding: 0; }\n\n.dshr-createdRow .dshr-input { color: var(--rs-text); }\n\n.dshr-fullButton { width: 100%; }\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 .12s ease;\n}\n\n.dshr-backLink:hover { color: var(--rs-accent); }\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: -.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 { text-align: center; }\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 .1s ease;\n}\n\n.dshr-linkRow:last-child { border-bottom: none; }\n.dshr-linkRow:hover { background: var(--rs-hover); }\n.dshr-linkRow[draggable='true'] { cursor: grab; }\n.dshr-linkRow[draggable='true']:active { cursor: grabbing; }\n.dshr-linkRowDragging { opacity: .45; background: var(--rs-hover); }\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 { cursor: grabbing; }\n.dshr-linkGrip svg { opacity: .65; }\n.dshr-linkRow:hover .dshr-linkGrip svg { opacity: 1; }\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 { color: var(--rs-faint); }\n.dshr-linkCredits { color: var(--rs-muted); }\n.dshr-linkErr { color: var(--rs-warn); }\n.dshr-linkCooling { color: var(--rs-warn); }\n.dshr-linkDot { color: var(--rs-faint); opacity: .6; }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-linkRow:hover .dshr-linkDelete { opacity: 1; }\n.dshr-linkDelete:focus-visible { opacity: 1; }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n.dshr-linkRow:hover .dshr-linkEdit { opacity: 1; }\n.dshr-linkEdit:focus-visible { opacity: 1; }\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 { opacity: 1; }\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: border-color .12s ease, background .12s ease, color .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 { opacity: .6; cursor: default; }\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: .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-poolStrategy {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\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: linear-gradient(45deg, transparent 50%, var(--rs-muted) 50%), linear-gradient(135deg, var(--rs-muted) 50%, transparent 50%);\n background-position: calc(100% - 12px) 55%, calc(100% - 7px) 55%;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n}\n.dshr-select:focus { outline: none; 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: border-color .12s ease, background .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 { border-color: var(--rs-ok); }\n.dshr-modelChipWrap.dshr-modelChip-err { border-color: var(--rs-danger); }\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 { color: var(--rs-ok); }\n.dshr-modelChipWrap.dshr-modelChip-err .dshr-modelChipStatus { color: var(--rs-danger); }\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 .12s ease;\n}\n\n.dshr-modelChipWrap:hover .dshr-modelChipOps,\n.dshr-modelChipOps:focus-within { opacity: 1; }\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: border-color .12s ease, background .12s ease, color .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 { opacity: .6; cursor: default; }\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: .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 .8s linear infinite;\n}\n\n@keyframes dshrSpin {\n to { transform: rotate(360deg); }\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: color .12s ease, background .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 / .18);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n animation: dshrToastIn .18s ease;\n}\n\n@keyframes dshrToastIn {\n from { opacity: 0; transform: translateX(-50%) translateY(8px); }\n to { opacity: 1; transform: translateX(-50%) translateY(0); }\n}\n\n/* ============ 组合(9router combos 布局交互) ============ */\n.dshr-comboIntro { overflow: visible; }\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 { display: flex; flex-direction: column; gap: 10px; }\n\n.dshr-comboCard { display: flex; flex-direction: column; }\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 { color: var(--rs-danger); }\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 { font-style: italic; font-size: 12px; }\n\n.dshr-comboMore { font-size: 10px; color: var(--rs-muted); }\n\n.dshr-comboStrategySelect {\n width: 84px;\n height: 26px;\n padding: 0 6px;\n font-size: 12px;\n}\n\n.dshr-comboStrategySelect:disabled {\n opacity: .55;\n cursor: not-allowed;\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: 260px;\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 { cursor: grabbing; }\n\n.dshr-comboStepRow-dragging {\n opacity: .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 { flex: 1; min-width: 0; }\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: border-color .12s ease, background .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 { margin-top: 12px; }\n\n.dshr-comboModelSearch { margin-bottom: 6px; }\n\n.dshr-comboPickList {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 180px;\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 .12s ease;\n}\n\n.dshr-comboPickItem:hover { background: var(--rs-hover); }\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 { margin-bottom: 6px; }\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: 340px;\n}\n\n.dshr-comboFormRight .dshr-comboPickList {\n flex: 1;\n min-height: 0;\n}\n\n/* 组合编辑弹窗:加宽 */\n.dshr-modal-wide {\n width: min(760px, 100%);\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: color .12s ease, background .12s ease, filter .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 / .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 / .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: .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: -.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 { color: var(--rs-accent); }\n.dshr-statCard-ok .dshr-statValue { color: var(--rs-success); }\n.dshr-statCard-warn .dshr-statValue { color: var(--rs-warn); }\n.dshr-statCard-danger .dshr-statValue { color: var(--rs-danger); }\n\n/* 签到卡:跟统计卡同宽同高,但里面是按钮不是数字。\n gap 沿用卡片的 3px(不覆写)—— 之前改成 6px 让这一行比别的高 6px,\n 两行卡片因此不齐(92 vs 98px)。\n 今日已点过 → 边框转成功色,一眼看出「今天点过了」 */\n.dshr-statCard-checkin { gap: 3px; }\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: color .12s ease, background .12s ease, border-color .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 { opacity: .6; cursor: default; }\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 { border-bottom: none; }\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: .03em;\n text-transform: uppercase;\n}\n\n.dshr-rankRow:not(.dshr-rankHead):hover { background: var(--rs-hover); }\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 { color: var(--rs-danger); }\n\n.dshr-tokenIn { color: var(--rs-accent); }\n.dshr-tokenOut { color: var(--rs-success); }\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-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-recentRow-fail .dshr-recentModel { color: var(--rs-danger); }\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 { background: var(--rs-success); }\n.dshr-dot-fail { background: var(--rs-danger); }\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>\") 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>\") center / contain no-repeat;\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(--ds-font-family-code, 'SFMono-Regular', Consolas, monospace);\n --rs-sans: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);\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 { box-sizing: border-box; }\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 .12s ease;\n}\n\n.dshr-tab:hover { color: var(--rs-text); }\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 { color: var(--rs-text); background: var(--rs-hover); }\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'] { position: relative; }\n.dshDesktopConversationSurface { position: relative; }\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 { isolation: isolate; }\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-dsh-router-view] {\n display: block;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-pane='conversation'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [class*='centerCol'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) .dshDesktopConversationSurface > :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 { box-sizing: border-box; }\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 { color: inherit; font: inherit; }\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 { color: var(--rs-text); background: var(--rs-hover); }\n.dshr-iconButton:disabled { opacity: 0.5; cursor: default; }\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 { background: var(--rs-hover); }\n.dshr-backButton svg { display: block; }\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 { color: var(--rs-danger); font-size: 12px; }\n.dshr-alert span { color: var(--rs-muted); font-size: 12px; }\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 { color: var(--rs-text); }\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/* 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 { background: var(--rs-hover); }\n\n.dshr-mono { font-family: var(--rs-mono); font-size: 11px; }\n.dshr-num { text-align: right; }\n.dshr-muted { color: var(--rs-faint); }\n.dshr-reason { color: var(--rs-muted); font-size: 11px; }\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 { color: var(--rs-success); background: color-mix(in srgb, var(--rs-success) 12%, transparent); }\n.dshr-badge-warn { color: var(--rs-warn); background: color-mix(in srgb, var(--rs-warn) 14%, transparent); }\n.dshr-badge-danger { color: var(--rs-danger); background: color-mix(in srgb, var(--rs-danger) 12%, transparent); }\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 { border-color: var(--rs-line-strong); }\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 { color: var(--rs-text); background: var(--rs-hover); }\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 .12s ease;\n}\n\n.dshr-card-click:hover { border-color: var(--rs-accent); }\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: .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 .12s ease;\n}\n\n.dshr-supplierCard:hover { border-color: var(--rs-accent); }\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: .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 .15s ease;\n}\n\n.dshr-toggle-on { background: var(--rs-ok); }\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 .15s ease;\n}\n\n.dshr-toggle-on .dshr-toggleKnob { transform: translateX(14px); }\n\n/* 开关禁用态:不可用(未就绪)且未开启时,灰显 + 不可点 */\n.dshr-toggle:disabled {\n cursor: default;\n opacity: .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-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 { color: var(--rs-danger); }\n.dshr-danger:hover { color: var(--rs-danger); border-color: var(--rs-danger); }\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 .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: 0 20px 50px rgb(0 0 0 / .35), 0 0 0 1px rgb(0 0 0 / .05);\n animation: dshrModalIn .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 { color: var(--rs-text); background: var(--rs-hover); }\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 { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dshrModalIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }\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 .12s ease;\n}\n\n.dshr-settings .dshr-primaryButton:hover,\n.dshr-shell .dshr-primaryButton:hover { filter: brightness(1.08); }\n.dshr-settings .dshr-primaryButton:disabled,\n.dshr-shell .dshr-primaryButton:disabled { opacity: .55; cursor: default; }\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 .12s ease;\n}\n\n.dshr-settings .dshr-dangerButton:hover,\n.dshr-shell .dshr-dangerButton:hover { filter: brightness(1.1); }\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(to bottom, color-mix(in srgb, var(--rs-layer-1) 62%, transparent), transparent);\n}\n\n/* Brand 标题更突出 */\n.dshr-brandTitle {\n letter-spacing: .01em;\n}\n\n/* 卡片:阴影 + hover 微抬升 */\n.dshr-card {\n box-shadow: 0 1px 2px rgb(0 0 0 / .04);\n transition: box-shadow .15s ease, transform .15s ease, border-color .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 / .10);\n}\n\n/* Tab:悬停背景 + 计数徽标配色 */\n.dshr-nav button {\n transition: color .12s ease, background .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 color-mix(in srgb, currentColor 12%, transparent);\n}\n\n/* 表格行悬停 + 表头层级 */\n.dshr-table tbody tr { transition: background .1s ease; }\n.dshr-table th { letter-spacing: .02em; }\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 { transition: background .15s ease, box-shadow .15s ease; }\n.dshr-toggle:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent); }\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: color .12s ease, background .12s ease;\n}\n\n.dshr-iconBtn:hover { color: var(--rs-accent); background: var(--rs-hover); }\n\n.dshr-iconBtn svg { display: block; }\n\n.dshr-iconBtn-sm { width: 26px; height: 26px; }\n\n/* 禁用态必须看得出来:纯图标按钮没有文字可读(不像「刷新中…」),\n 只靠 hover 反馈会让人以为还能点。 */\n.dshr-iconBtn:disabled { opacity: .4; cursor: default; }\n\n.dshr-iconBtn:disabled:hover { color: var(--rs-muted); background: transparent; }\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 .12s ease;\n}\n\n.dshr-keyRow:last-child { border-bottom: none; }\n.dshr-keyRow-paused { opacity: .6; }\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 { color: var(--rs-warn); }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-keyRow:hover .dshr-keyDelete { opacity: 1; }\n.dshr-keyDelete:focus-visible { opacity: 1; }\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 { padding: 0; }\n\n.dshr-createdRow .dshr-input { color: var(--rs-text); }\n\n.dshr-fullButton { width: 100%; }\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 .12s ease;\n}\n\n.dshr-backLink:hover { color: var(--rs-accent); }\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: -.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 { text-align: center; }\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 .1s ease;\n}\n\n.dshr-linkRow:last-child { border-bottom: none; }\n.dshr-linkRow:hover { background: var(--rs-hover); }\n.dshr-linkRow[draggable='true'] { cursor: grab; }\n.dshr-linkRow[draggable='true']:active { cursor: grabbing; }\n.dshr-linkRowDragging { opacity: .45; background: var(--rs-hover); }\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 { cursor: grabbing; }\n.dshr-linkGrip svg { opacity: .65; }\n.dshr-linkRow:hover .dshr-linkGrip svg { opacity: 1; }\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 { color: var(--rs-faint); }\n.dshr-linkCredits { color: var(--rs-muted); }\n.dshr-linkErr { color: var(--rs-warn); }\n.dshr-linkCooling { color: var(--rs-warn); }\n.dshr-linkDot { color: var(--rs-faint); opacity: .6; }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-linkRow:hover .dshr-linkDelete { opacity: 1; }\n.dshr-linkDelete:focus-visible { opacity: 1; }\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: opacity .12s ease, color .12s ease, background .12s ease;\n}\n.dshr-linkRow:hover .dshr-linkEdit { opacity: 1; }\n.dshr-linkEdit:focus-visible { opacity: 1; }\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 { opacity: 1; }\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: border-color .12s ease, background .12s ease, color .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 { opacity: .6; cursor: default; }\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: .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-poolStrategy {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\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: linear-gradient(45deg, transparent 50%, var(--rs-muted) 50%), linear-gradient(135deg, var(--rs-muted) 50%, transparent 50%);\n background-position: calc(100% - 12px) 55%, calc(100% - 7px) 55%;\n background-size: 5px 5px;\n background-repeat: no-repeat;\n}\n.dshr-select:focus { outline: none; 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: border-color .12s ease, background .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 { border-color: var(--rs-ok); }\n.dshr-modelChipWrap.dshr-modelChip-err { border-color: var(--rs-danger); }\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 { color: var(--rs-ok); }\n.dshr-modelChipWrap.dshr-modelChip-err .dshr-modelChipStatus { color: var(--rs-danger); }\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 .12s ease;\n}\n\n.dshr-modelChipWrap:hover .dshr-modelChipOps,\n.dshr-modelChipOps:focus-within { opacity: 1; }\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: border-color .12s ease, background .12s ease, color .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 { opacity: .6; cursor: default; }\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: .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 .8s linear infinite;\n}\n\n@keyframes dshrSpin {\n to { transform: rotate(360deg); }\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: color .12s ease, background .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 / .18);\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n animation: dshrToastIn .18s ease;\n}\n\n@keyframes dshrToastIn {\n from { opacity: 0; transform: translateX(-50%) translateY(8px); }\n to { opacity: 1; transform: translateX(-50%) translateY(0); }\n}\n\n/* ============ 组合(9router combos 布局交互) ============ */\n.dshr-comboIntro { overflow: visible; }\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 { display: flex; flex-direction: column; gap: 10px; }\n\n.dshr-comboCard { display: flex; flex-direction: column; }\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 { color: var(--rs-danger); }\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 { font-style: italic; font-size: 12px; }\n\n.dshr-comboMore { font-size: 10px; color: var(--rs-muted); }\n\n.dshr-comboStrategySelect {\n width: 84px;\n height: 26px;\n padding: 0 6px;\n font-size: 12px;\n}\n\n.dshr-comboStrategySelect:disabled {\n opacity: .55;\n cursor: not-allowed;\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: 260px;\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 { cursor: grabbing; }\n\n.dshr-comboStepRow-dragging {\n opacity: .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 { flex: 1; min-width: 0; }\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: border-color .12s ease, background .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 { margin-top: 12px; }\n\n.dshr-comboModelSearch { margin-bottom: 6px; }\n\n.dshr-comboPickList {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 180px;\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 .12s ease;\n}\n\n.dshr-comboPickItem:hover { background: var(--rs-hover); }\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 { margin-bottom: 6px; }\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: 340px;\n}\n\n.dshr-comboFormRight .dshr-comboPickList {\n flex: 1;\n min-height: 0;\n}\n\n/* 组合编辑弹窗:加宽 */\n.dshr-modal-wide {\n width: min(760px, 100%);\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: color .12s ease, background .12s ease, filter .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 / .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 / .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: .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: -.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 { color: var(--rs-accent); }\n.dshr-statCard-ok .dshr-statValue { color: var(--rs-success); }\n.dshr-statCard-warn .dshr-statValue { color: var(--rs-warn); }\n.dshr-statCard-danger .dshr-statValue { color: var(--rs-danger); }\n\n/* 签到卡:跟统计卡同宽同高,但里面是按钮不是数字。\n gap 沿用卡片的 3px(不覆写)—— 之前改成 6px 让这一行比别的高 6px,\n 两行卡片因此不齐(92 vs 98px)。\n 今日已点过 → 边框转成功色,一眼看出「今天点过了」 */\n.dshr-statCard-checkin { gap: 3px; }\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: color .12s ease, background .12s ease, border-color .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 { opacity: .6; cursor: default; }\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 { border-bottom: none; }\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: .03em;\n text-transform: uppercase;\n}\n\n.dshr-rankRow:not(.dshr-rankHead):hover { background: var(--rs-hover); }\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 { color: var(--rs-danger); }\n\n.dshr-tokenIn { color: var(--rs-accent); }\n.dshr-tokenOut { color: var(--rs-success); }\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-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-recentRow-fail .dshr-recentModel { color: var(--rs-danger); }\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 { background: var(--rs-success); }\n.dshr-dot-fail { background: var(--rs-danger); }\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>\") 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>\") center / contain no-repeat;\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-router-core",
|
|
4
4
|
factory: (require) => {
|
|
@@ -76,7 +76,7 @@ window.__ModuleLoader__.load({
|
|
|
76
76
|
* + 可用模型卡片(flex-wrap chips,禁用模型单独列出可恢复)。
|
|
77
77
|
* 加链接流程由供应商能力驱动(登录链接 / API key / 轮询登录)。
|
|
78
78
|
*/
|
|
79
|
-
function Icon$
|
|
79
|
+
function Icon$5({ d, size = 18 }) {
|
|
80
80
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
81
81
|
viewBox: "0 0 24 24",
|
|
82
82
|
width: size,
|
|
@@ -90,7 +90,7 @@ window.__ModuleLoader__.load({
|
|
|
90
90
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
-
var I$
|
|
93
|
+
var I$5 = {
|
|
94
94
|
back: "M19 12H5M12 19l-7-7 7-7",
|
|
95
95
|
link: "M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7",
|
|
96
96
|
add: "M12 5v14M5 12h14",
|
|
@@ -621,8 +621,8 @@ window.__ModuleLoader__.load({
|
|
|
621
621
|
type: "button",
|
|
622
622
|
className: "dshr-backLink",
|
|
623
623
|
onClick: onBack,
|
|
624
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
625
|
-
d: I$
|
|
624
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
625
|
+
d: I$5.back,
|
|
626
626
|
size: 16
|
|
627
627
|
}), "返回"]
|
|
628
628
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -637,8 +637,8 @@ window.__ModuleLoader__.load({
|
|
|
637
637
|
onError: (e) => {
|
|
638
638
|
e.currentTarget.style.display = "none";
|
|
639
639
|
}
|
|
640
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
641
|
-
d: I$
|
|
640
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
641
|
+
d: I$5.link,
|
|
642
642
|
size: 22
|
|
643
643
|
})
|
|
644
644
|
}),
|
|
@@ -666,8 +666,8 @@ window.__ModuleLoader__.load({
|
|
|
666
666
|
setAliasError("");
|
|
667
667
|
setShowAlias(true);
|
|
668
668
|
},
|
|
669
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
670
|
-
d: I$
|
|
669
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
670
|
+
d: I$5.edit,
|
|
671
671
|
size: 15
|
|
672
672
|
})
|
|
673
673
|
})
|
|
@@ -697,8 +697,8 @@ window.__ModuleLoader__.load({
|
|
|
697
697
|
"aria-label": "刷新积分并探测健康",
|
|
698
698
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
699
699
|
className: refreshing ? "dshr-spin" : void 0,
|
|
700
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
701
|
-
d: I$
|
|
700
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
701
|
+
d: I$5.refresh,
|
|
702
702
|
size: 15
|
|
703
703
|
})
|
|
704
704
|
})
|
|
@@ -712,8 +712,8 @@ window.__ModuleLoader__.load({
|
|
|
712
712
|
"aria-label": "签到所有链接",
|
|
713
713
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
714
714
|
className: checkingIn ? "dshr-spin" : void 0,
|
|
715
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
716
|
-
d: I$
|
|
715
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
716
|
+
d: I$5.checkCircle,
|
|
717
717
|
size: 15
|
|
718
718
|
})
|
|
719
719
|
})
|
|
@@ -723,8 +723,8 @@ window.__ModuleLoader__.load({
|
|
|
723
723
|
className: "dshr-primaryButton",
|
|
724
724
|
onClick: () => void startLogin(),
|
|
725
725
|
disabled: busy,
|
|
726
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
727
|
-
d: I$
|
|
726
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
727
|
+
d: I$5.add,
|
|
728
728
|
size: 14
|
|
729
729
|
}), "添加链接"]
|
|
730
730
|
})
|
|
@@ -738,8 +738,8 @@ window.__ModuleLoader__.load({
|
|
|
738
738
|
children: [
|
|
739
739
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
740
740
|
className: "dshr-linkEmptyIcon",
|
|
741
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
742
|
-
d: I$
|
|
741
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
742
|
+
d: I$5.link,
|
|
743
743
|
size: 28
|
|
744
744
|
})
|
|
745
745
|
}),
|
|
@@ -758,8 +758,8 @@ window.__ModuleLoader__.load({
|
|
|
758
758
|
className: "dshr-primaryButton",
|
|
759
759
|
onClick: () => void startLogin(),
|
|
760
760
|
disabled: busy,
|
|
761
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
762
|
-
d: I$
|
|
761
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
762
|
+
d: I$5.add,
|
|
763
763
|
size: 14
|
|
764
764
|
}), "添加链接"]
|
|
765
765
|
})
|
|
@@ -900,8 +900,8 @@ window.__ModuleLoader__.load({
|
|
|
900
900
|
if (renameUid === account.uid) saveRename(account);
|
|
901
901
|
else startRename(account);
|
|
902
902
|
},
|
|
903
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
904
|
-
d: renameUid === account.uid ? I$
|
|
903
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
904
|
+
d: renameUid === account.uid ? I$5.check : I$5.edit,
|
|
905
905
|
size: 15
|
|
906
906
|
})
|
|
907
907
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -909,8 +909,8 @@ window.__ModuleLoader__.load({
|
|
|
909
909
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-linkDelete",
|
|
910
910
|
title: "删除链接",
|
|
911
911
|
onClick: () => setRemoveTarget(account),
|
|
912
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
913
|
-
d: I$
|
|
912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
913
|
+
d: I$5.delete,
|
|
914
914
|
size: 15
|
|
915
915
|
})
|
|
916
916
|
})]
|
|
@@ -954,8 +954,8 @@ window.__ModuleLoader__.load({
|
|
|
954
954
|
setShowAddModel(true);
|
|
955
955
|
},
|
|
956
956
|
disabled: toggling,
|
|
957
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
958
|
-
d: I$
|
|
957
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
958
|
+
d: I$5.add,
|
|
959
959
|
size: 13
|
|
960
960
|
}), "添加模型"]
|
|
961
961
|
}),
|
|
@@ -981,8 +981,8 @@ window.__ModuleLoader__.load({
|
|
|
981
981
|
className: "dshr-modelSearch",
|
|
982
982
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
983
983
|
className: "dshr-modelSearchIcon",
|
|
984
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
985
|
-
d: I$
|
|
984
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
985
|
+
d: I$5.search,
|
|
986
986
|
size: 13
|
|
987
987
|
})
|
|
988
988
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -1013,8 +1013,8 @@ window.__ModuleLoader__.load({
|
|
|
1013
1013
|
onClick: () => void toggleModel(model.id, false),
|
|
1014
1014
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1015
1015
|
className: "dshr-modelChipStatus",
|
|
1016
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1017
|
-
d: status === "ok" ? I$
|
|
1016
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1017
|
+
d: status === "ok" ? I$5.checkCircle : status === "error" ? I$5.cancel : I$5.robot,
|
|
1018
1018
|
size: 13
|
|
1019
1019
|
})
|
|
1020
1020
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1031,8 +1031,8 @@ window.__ModuleLoader__.load({
|
|
|
1031
1031
|
onClick: () => void testModel(model.id),
|
|
1032
1032
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1033
1033
|
className: testingIds.has(model.id) ? "dshr-spin" : void 0,
|
|
1034
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1035
|
-
d: testingIds.has(model.id) ? I$
|
|
1034
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1035
|
+
d: testingIds.has(model.id) ? I$5.loading : I$5.test,
|
|
1036
1036
|
size: 13
|
|
1037
1037
|
})
|
|
1038
1038
|
})
|
|
@@ -1042,8 +1042,8 @@ window.__ModuleLoader__.load({
|
|
|
1042
1042
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1043
1043
|
title: "复制模型名",
|
|
1044
1044
|
onClick: () => void copyModel(model.id),
|
|
1045
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1046
|
-
d: copiedModel === model.id ? I$
|
|
1045
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1046
|
+
d: copiedModel === model.id ? I$5.check : I$5.copy,
|
|
1047
1047
|
size: 13
|
|
1048
1048
|
})
|
|
1049
1049
|
}),
|
|
@@ -1052,8 +1052,8 @@ window.__ModuleLoader__.load({
|
|
|
1052
1052
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-modelDelete",
|
|
1053
1053
|
title: "删除自定义模型",
|
|
1054
1054
|
onClick: () => setRemoveModelTarget(model.id),
|
|
1055
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1056
|
-
d: I$
|
|
1055
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1056
|
+
d: I$5.delete,
|
|
1057
1057
|
size: 13
|
|
1058
1058
|
})
|
|
1059
1059
|
})
|
|
@@ -1173,7 +1173,7 @@ window.__ModuleLoader__.load({
|
|
|
1173
1173
|
}),
|
|
1174
1174
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1175
1175
|
className: "dshr-muted",
|
|
1176
|
-
children: "回退 = 按连接池顺序取第一个健康链接;轮询 =
|
|
1176
|
+
children: "回退 = 按连接池顺序取第一个健康链接;轮询 = 按会话前缀粘住同一链接(前缀亲和,多会话不打架),拿不到会话信息时退回块轮转。"
|
|
1177
1177
|
}),
|
|
1178
1178
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1179
1179
|
className: "dshr-select dshr-input",
|
|
@@ -1419,7 +1419,7 @@ window.__ModuleLoader__.load({
|
|
|
1419
1419
|
* API Endpoint 卡片 + API Keys 卡片(Require API key + flex 行 key 列表 +
|
|
1420
1420
|
* Create/暂停确认/Created 弹窗)。
|
|
1421
1421
|
*/
|
|
1422
|
-
function Icon$
|
|
1422
|
+
function Icon$4({ d, size = 18 }) {
|
|
1423
1423
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1424
1424
|
viewBox: "0 0 24 24",
|
|
1425
1425
|
width: size,
|
|
@@ -1433,7 +1433,7 @@ window.__ModuleLoader__.load({
|
|
|
1433
1433
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
1434
1434
|
});
|
|
1435
1435
|
}
|
|
1436
|
-
var I$
|
|
1436
|
+
var I$4 = {
|
|
1437
1437
|
api: "M4 4h16v16H4zM9 9h6v6H9zM12 9V7M12 17v-2M9 12H7M17 12h-2",
|
|
1438
1438
|
key: "M14 7a4 4 0 1 1-1.4 7.6L9 18H6v-3l4.4-4.4A4 4 0 0 1 14 7zM18 7h.01",
|
|
1439
1439
|
visibility: "M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
|
|
@@ -1582,7 +1582,7 @@ window.__ModuleLoader__.load({
|
|
|
1582
1582
|
className: "dshr-cardHead",
|
|
1583
1583
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1584
1584
|
className: "dshr-cardIcon",
|
|
1585
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1585
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, { d: I$4.api })
|
|
1586
1586
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1587
1587
|
className: "dshr-cardTitle",
|
|
1588
1588
|
children: "API Endpoint"
|
|
@@ -1606,8 +1606,8 @@ window.__ModuleLoader__.load({
|
|
|
1606
1606
|
className: "dshr-iconBtn",
|
|
1607
1607
|
title: "复制",
|
|
1608
1608
|
onClick: () => void copyText(endpointBase, "__endpoint"),
|
|
1609
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1610
|
-
d: copiedKeyId === "__endpoint" ? I$
|
|
1609
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1610
|
+
d: copiedKeyId === "__endpoint" ? I$4.check : I$4.copy,
|
|
1611
1611
|
size: 16
|
|
1612
1612
|
})
|
|
1613
1613
|
})
|
|
@@ -1627,7 +1627,7 @@ window.__ModuleLoader__.load({
|
|
|
1627
1627
|
children: [
|
|
1628
1628
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1629
1629
|
className: "dshr-cardIcon",
|
|
1630
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1630
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, { d: I$4.key })
|
|
1631
1631
|
}),
|
|
1632
1632
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1633
1633
|
className: "dshr-cardTitle",
|
|
@@ -1637,8 +1637,8 @@ window.__ModuleLoader__.load({
|
|
|
1637
1637
|
type: "button",
|
|
1638
1638
|
className: "dshr-primaryButton dshr-cardAction",
|
|
1639
1639
|
onClick: openCreate,
|
|
1640
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1641
|
-
d: I$
|
|
1640
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1641
|
+
d: I$4.add,
|
|
1642
1642
|
size: 14
|
|
1643
1643
|
}), "创建 Key"]
|
|
1644
1644
|
})
|
|
@@ -1673,8 +1673,8 @@ window.__ModuleLoader__.load({
|
|
|
1673
1673
|
children: [
|
|
1674
1674
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1675
1675
|
className: "dshr-keyEmptyIcon",
|
|
1676
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1677
|
-
d: I$
|
|
1676
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1677
|
+
d: I$4.key,
|
|
1678
1678
|
size: 30
|
|
1679
1679
|
})
|
|
1680
1680
|
}),
|
|
@@ -1690,8 +1690,8 @@ window.__ModuleLoader__.load({
|
|
|
1690
1690
|
type: "button",
|
|
1691
1691
|
className: "dshr-primaryButton",
|
|
1692
1692
|
onClick: openCreate,
|
|
1693
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1694
|
-
d: I$
|
|
1693
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1694
|
+
d: I$4.add,
|
|
1695
1695
|
size: 14
|
|
1696
1696
|
}), "创建 Key"]
|
|
1697
1697
|
})
|
|
@@ -1713,8 +1713,8 @@ window.__ModuleLoader__.load({
|
|
|
1713
1713
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-keyDelete",
|
|
1714
1714
|
title: "删除",
|
|
1715
1715
|
onClick: () => setDeleteTarget(key),
|
|
1716
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1717
|
-
d: I$
|
|
1716
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1717
|
+
d: I$4.delete,
|
|
1718
1718
|
size: 14
|
|
1719
1719
|
})
|
|
1720
1720
|
})]
|
|
@@ -1731,8 +1731,8 @@ window.__ModuleLoader__.load({
|
|
|
1731
1731
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1732
1732
|
title: visibleKeys.has(key.id) ? "隐藏" : "显示",
|
|
1733
1733
|
onClick: () => toggleVisible(key.id),
|
|
1734
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1735
|
-
d: visibleKeys.has(key.id) ? I$
|
|
1734
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1735
|
+
d: visibleKeys.has(key.id) ? I$4.visibilityOff : I$4.visibility,
|
|
1736
1736
|
size: 14
|
|
1737
1737
|
})
|
|
1738
1738
|
}),
|
|
@@ -1741,8 +1741,8 @@ window.__ModuleLoader__.load({
|
|
|
1741
1741
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1742
1742
|
title: "复制",
|
|
1743
1743
|
onClick: () => void copyText(key.key, key.id),
|
|
1744
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1745
|
-
d: copiedKeyId === key.id ? I$
|
|
1744
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
1745
|
+
d: copiedKeyId === key.id ? I$4.check : I$4.copy,
|
|
1746
1746
|
size: 14
|
|
1747
1747
|
})
|
|
1748
1748
|
})
|
|
@@ -1919,7 +1919,7 @@ window.__ModuleLoader__.load({
|
|
|
1919
1919
|
* 组合 = 命名的一组模型(完整名 alias/id),请求 model 传组合名时,
|
|
1920
1920
|
* 按策略(回退/轮询)命中其中一个。可创建/编辑/删除。
|
|
1921
1921
|
*/
|
|
1922
|
-
function Icon$
|
|
1922
|
+
function Icon$3({ d, size = 18 }) {
|
|
1923
1923
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1924
1924
|
viewBox: "0 0 24 24",
|
|
1925
1925
|
width: size,
|
|
@@ -1933,7 +1933,7 @@ window.__ModuleLoader__.load({
|
|
|
1933
1933
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
1934
1934
|
});
|
|
1935
1935
|
}
|
|
1936
|
-
var I$
|
|
1936
|
+
var I$3 = {
|
|
1937
1937
|
layers: "M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5M3 17l9 5 9-5",
|
|
1938
1938
|
add: "M12 5v14M5 12h14",
|
|
1939
1939
|
copy: "M9 9h11v11H9zM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1",
|
|
@@ -2155,8 +2155,8 @@ window.__ModuleLoader__.load({
|
|
|
2155
2155
|
children: [
|
|
2156
2156
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2157
2157
|
className: "dshr-cardIcon",
|
|
2158
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2159
|
-
d: I$
|
|
2158
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2159
|
+
d: I$3.layers,
|
|
2160
2160
|
size: 16
|
|
2161
2161
|
})
|
|
2162
2162
|
}),
|
|
@@ -2172,8 +2172,8 @@ window.__ModuleLoader__.load({
|
|
|
2172
2172
|
type: "button",
|
|
2173
2173
|
className: "dshr-primaryButton dshr-cardAction",
|
|
2174
2174
|
onClick: openCreate,
|
|
2175
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2176
|
-
d: I$
|
|
2175
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2176
|
+
d: I$3.add,
|
|
2177
2177
|
size: 14
|
|
2178
2178
|
}), "创建组合"]
|
|
2179
2179
|
})
|
|
@@ -2199,8 +2199,8 @@ window.__ModuleLoader__.load({
|
|
|
2199
2199
|
children: [
|
|
2200
2200
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2201
2201
|
className: "dshr-keyEmptyIcon",
|
|
2202
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2203
|
-
d: I$
|
|
2202
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2203
|
+
d: I$3.layers,
|
|
2204
2204
|
size: 30
|
|
2205
2205
|
})
|
|
2206
2206
|
}),
|
|
@@ -2216,8 +2216,8 @@ window.__ModuleLoader__.load({
|
|
|
2216
2216
|
type: "button",
|
|
2217
2217
|
className: "dshr-primaryButton",
|
|
2218
2218
|
onClick: openCreate,
|
|
2219
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2220
|
-
d: I$
|
|
2219
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2220
|
+
d: I$3.add,
|
|
2221
2221
|
size: 14
|
|
2222
2222
|
}), "创建组合"]
|
|
2223
2223
|
})
|
|
@@ -2231,8 +2231,8 @@ window.__ModuleLoader__.load({
|
|
|
2231
2231
|
children: [
|
|
2232
2232
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2233
2233
|
className: "dshr-cardIcon",
|
|
2234
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2235
|
-
d: I$
|
|
2234
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2235
|
+
d: I$3.layers,
|
|
2236
2236
|
size: 16
|
|
2237
2237
|
})
|
|
2238
2238
|
}),
|
|
@@ -2265,8 +2265,8 @@ window.__ModuleLoader__.load({
|
|
|
2265
2265
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
2266
2266
|
title: "复制组合名",
|
|
2267
2267
|
onClick: () => void copyName(combo.name, combo.id),
|
|
2268
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2269
|
-
d: copied === combo.id ? I$
|
|
2268
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2269
|
+
d: copied === combo.id ? I$3.check : I$3.copy,
|
|
2270
2270
|
size: 15
|
|
2271
2271
|
})
|
|
2272
2272
|
}),
|
|
@@ -2275,8 +2275,8 @@ window.__ModuleLoader__.load({
|
|
|
2275
2275
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
2276
2276
|
title: "编辑",
|
|
2277
2277
|
onClick: () => openEdit(combo),
|
|
2278
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2279
|
-
d: I$
|
|
2278
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2279
|
+
d: I$3.edit,
|
|
2280
2280
|
size: 15
|
|
2281
2281
|
})
|
|
2282
2282
|
}),
|
|
@@ -2285,8 +2285,8 @@ window.__ModuleLoader__.load({
|
|
|
2285
2285
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboOpBtn-danger",
|
|
2286
2286
|
title: "删除",
|
|
2287
2287
|
onClick: () => setDeleteTarget(combo),
|
|
2288
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2289
|
-
d: I$
|
|
2288
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2289
|
+
d: I$3.delete,
|
|
2290
2290
|
size: 15
|
|
2291
2291
|
})
|
|
2292
2292
|
})
|
|
@@ -2382,8 +2382,8 @@ window.__ModuleLoader__.load({
|
|
|
2382
2382
|
]
|
|
2383
2383
|
}), form.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2384
2384
|
className: "dshr-comboEmptyStepsBox",
|
|
2385
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2386
|
-
d: I$
|
|
2385
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2386
|
+
d: I$3.layers,
|
|
2387
2387
|
size: 20
|
|
2388
2388
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2389
2389
|
className: "dshr-muted",
|
|
@@ -2405,8 +2405,8 @@ window.__ModuleLoader__.load({
|
|
|
2405
2405
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2406
2406
|
className: "dshr-comboGrip",
|
|
2407
2407
|
title: "拖拽排序",
|
|
2408
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2409
|
-
d: I$
|
|
2408
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2409
|
+
d: I$3.grip,
|
|
2410
2410
|
size: 14
|
|
2411
2411
|
})
|
|
2412
2412
|
}),
|
|
@@ -2423,8 +2423,8 @@ window.__ModuleLoader__.load({
|
|
|
2423
2423
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboStepRemove",
|
|
2424
2424
|
title: "移除",
|
|
2425
2425
|
onClick: () => removeModel(index),
|
|
2426
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2427
|
-
d: I$
|
|
2426
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2427
|
+
d: I$3.delete,
|
|
2428
2428
|
size: 14
|
|
2429
2429
|
})
|
|
2430
2430
|
})
|
|
@@ -2444,8 +2444,8 @@ window.__ModuleLoader__.load({
|
|
|
2444
2444
|
className: "dshr-modelSearch dshr-comboModelSearch",
|
|
2445
2445
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2446
2446
|
className: "dshr-modelSearchIcon",
|
|
2447
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2448
|
-
d: I$
|
|
2447
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2448
|
+
d: I$3.search,
|
|
2449
2449
|
size: 13
|
|
2450
2450
|
})
|
|
2451
2451
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -2562,7 +2562,7 @@ window.__ModuleLoader__.load({
|
|
|
2562
2562
|
* 要真状态的升级路径:给 supplier 契约加 `checkinStatus?()`,核心加一条
|
|
2563
2563
|
* `/suppliers/:id/checkin/status` 汇总路由,这里改成读它即可(卡片不用改)。
|
|
2564
2564
|
*/
|
|
2565
|
-
function Icon$
|
|
2565
|
+
function Icon$2({ d, size = 14 }) {
|
|
2566
2566
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2567
2567
|
viewBox: "0 0 24 24",
|
|
2568
2568
|
width: size,
|
|
@@ -2576,7 +2576,7 @@ window.__ModuleLoader__.load({
|
|
|
2576
2576
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
2577
2577
|
});
|
|
2578
2578
|
}
|
|
2579
|
-
var I$
|
|
2579
|
+
var I$2 = {
|
|
2580
2580
|
check: "M4 12.5l5 5L20 6.5",
|
|
2581
2581
|
loading: "M12 3a9 9 0 1 0 9 9"
|
|
2582
2582
|
};
|
|
@@ -2696,8 +2696,8 @@ window.__ModuleLoader__.load({
|
|
|
2696
2696
|
title: hintTitle(),
|
|
2697
2697
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2698
2698
|
className: busy ? "dshr-spin" : void 0,
|
|
2699
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2700
|
-
d: busy ? I$
|
|
2699
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
|
|
2700
|
+
d: busy ? I$2.loading : I$2.check,
|
|
2701
2701
|
size: 14
|
|
2702
2702
|
})
|
|
2703
2703
|
}), busy ? "签到中…" : "一键签到"]
|
|
@@ -2727,7 +2727,7 @@ window.__ModuleLoader__.load({
|
|
|
2727
2727
|
* token 口径见 `router/usage-tokens.ts`:上游不发 usage 时按字符数估算,
|
|
2728
2728
|
* 估算过的字段标 `~`,鼠标悬停会说明。
|
|
2729
2729
|
*/
|
|
2730
|
-
function Icon({ d, size = 18 }) {
|
|
2730
|
+
function Icon$1({ d, size = 18 }) {
|
|
2731
2731
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2732
2732
|
viewBox: "0 0 24 24",
|
|
2733
2733
|
width: size,
|
|
@@ -2741,7 +2741,7 @@ window.__ModuleLoader__.load({
|
|
|
2741
2741
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
2742
2742
|
});
|
|
2743
2743
|
}
|
|
2744
|
-
var I = {
|
|
2744
|
+
var I$1 = {
|
|
2745
2745
|
chart: "M3 3v18h18M7 15l4-5 3 3 5-7",
|
|
2746
2746
|
bolt: "M13 2L4 14h6l-1 8 9-12h-6z",
|
|
2747
2747
|
refresh: "M21 12a9 9 0 1 1-3-6.7M21 4v5h-5",
|
|
@@ -2885,8 +2885,8 @@ window.__ModuleLoader__.load({
|
|
|
2885
2885
|
className: "dshr-trendEmpty",
|
|
2886
2886
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2887
2887
|
className: "dshr-trendEmptyIcon",
|
|
2888
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2889
|
-
d: I.empty,
|
|
2888
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
2889
|
+
d: I$1.empty,
|
|
2890
2890
|
size: 26
|
|
2891
2891
|
})
|
|
2892
2892
|
}), "这个周期还没有请求"]
|
|
@@ -3001,7 +3001,7 @@ window.__ModuleLoader__.load({
|
|
|
3001
3001
|
children: [
|
|
3002
3002
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3003
3003
|
className: "dshr-cardIcon",
|
|
3004
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { d: I.chart })
|
|
3004
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, { d: I$1.chart })
|
|
3005
3005
|
}),
|
|
3006
3006
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3007
3007
|
className: "dshr-cardTitle",
|
|
@@ -3086,7 +3086,7 @@ window.__ModuleLoader__.load({
|
|
|
3086
3086
|
children: [
|
|
3087
3087
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3088
3088
|
className: "dshr-cardIcon",
|
|
3089
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { d: I.bolt })
|
|
3089
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, { d: I$1.bolt })
|
|
3090
3090
|
}),
|
|
3091
3091
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3092
3092
|
className: "dshr-cardTitle",
|
|
@@ -3241,8 +3241,8 @@ window.__ModuleLoader__.load({
|
|
|
3241
3241
|
"aria-label": refreshing ? "刷新中" : "刷新统计",
|
|
3242
3242
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3243
3243
|
className: refreshing ? "dshr-spin" : void 0,
|
|
3244
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3245
|
-
d: I.refresh,
|
|
3244
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3245
|
+
d: I$1.refresh,
|
|
3246
3246
|
size: 16
|
|
3247
3247
|
})
|
|
3248
3248
|
})
|
|
@@ -3312,7 +3312,7 @@ window.__ModuleLoader__.load({
|
|
|
3312
3312
|
children: [
|
|
3313
3313
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3314
3314
|
className: "dshr-cardIcon",
|
|
3315
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { d: I.chart })
|
|
3315
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, { d: I$1.chart })
|
|
3316
3316
|
}),
|
|
3317
3317
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3318
3318
|
className: "dshr-cardTitle",
|
|
@@ -3346,13 +3346,176 @@ window.__ModuleLoader__.load({
|
|
|
3346
3346
|
});
|
|
3347
3347
|
}
|
|
3348
3348
|
//#endregion
|
|
3349
|
+
//#region src/client/ExtTab.tsx
|
|
3350
|
+
/**
|
|
3351
|
+
* 扩展 (Ext) 页 —— 列出所有注册进 `router.ext` 的扩展插件,每个一个开关。
|
|
3352
|
+
*
|
|
3353
|
+
* 数据来自 `/router/api/ext`:核心把自己存的开关(`ext.json`)+ 扩展器报的
|
|
3354
|
+
* 运行时事实(`ready` / `detail`)合并后给面板。扩展器只实现具体能力(如 RTK),
|
|
3355
|
+
* 开关与渲染都在核心。
|
|
3356
|
+
*
|
|
3357
|
+
* 布局贴近 EndpointTab 的「Require API key」开关行:每个扩展器一张卡片。
|
|
3358
|
+
* 未装任何扩展 → 空状态提示。
|
|
3359
|
+
*/
|
|
3360
|
+
function Icon({ d, size = 18 }) {
|
|
3361
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3362
|
+
viewBox: "0 0 24 24",
|
|
3363
|
+
width: size,
|
|
3364
|
+
height: size,
|
|
3365
|
+
fill: "none",
|
|
3366
|
+
stroke: "currentColor",
|
|
3367
|
+
strokeWidth: "1.8",
|
|
3368
|
+
strokeLinecap: "round",
|
|
3369
|
+
strokeLinejoin: "round",
|
|
3370
|
+
"aria-hidden": "true",
|
|
3371
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
var I = {
|
|
3375
|
+
bolt: "M13 2L4 14h6l-1 8 9-12h-6l1-8z",
|
|
3376
|
+
puzzle: "M4 7h3a2 2 0 0 1 4 0h5a1 1 0 0 1 1 1v4a2 2 0 0 0 0 4v4H8v-3a2 2 0 0 1 0-4H4V7z"
|
|
3377
|
+
};
|
|
3378
|
+
function ExtTab() {
|
|
3379
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
3380
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
3381
|
+
const [error, setError] = (0, react.useState)("");
|
|
3382
|
+
const mounted = (0, react.useRef)(true);
|
|
3383
|
+
(0, react.useEffect)(() => {
|
|
3384
|
+
mounted.current = true;
|
|
3385
|
+
load();
|
|
3386
|
+
return () => {
|
|
3387
|
+
mounted.current = false;
|
|
3388
|
+
};
|
|
3389
|
+
}, []);
|
|
3390
|
+
const load = async () => {
|
|
3391
|
+
try {
|
|
3392
|
+
const data = await (await fetch(`${ROUTER_API_BASE}/ext`, { cache: "no-store" })).json();
|
|
3393
|
+
if (mounted.current) {
|
|
3394
|
+
setError(data.ok ? "" : data.error ?? "加载扩展失败");
|
|
3395
|
+
setItems(data.enhancers ?? []);
|
|
3396
|
+
setLoaded(true);
|
|
3397
|
+
}
|
|
3398
|
+
} catch (err) {
|
|
3399
|
+
if (mounted.current) {
|
|
3400
|
+
setError(err.message);
|
|
3401
|
+
setLoaded(true);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
};
|
|
3405
|
+
const toggle = async (item, value) => {
|
|
3406
|
+
setItems((prev) => prev.map((i) => i.id === item.id ? {
|
|
3407
|
+
...i,
|
|
3408
|
+
enabled: value
|
|
3409
|
+
} : i));
|
|
3410
|
+
setError("");
|
|
3411
|
+
try {
|
|
3412
|
+
const data = await (await fetch(`${ROUTER_API_BASE}/ext`, {
|
|
3413
|
+
method: "PATCH",
|
|
3414
|
+
headers: { "Content-Type": "application/json" },
|
|
3415
|
+
body: JSON.stringify({
|
|
3416
|
+
id: item.id,
|
|
3417
|
+
enabled: value
|
|
3418
|
+
}),
|
|
3419
|
+
cache: "no-store"
|
|
3420
|
+
})).json();
|
|
3421
|
+
if (mounted.current && data.enhancers) setItems(data.enhancers);
|
|
3422
|
+
else if (mounted.current && !data.ok) {
|
|
3423
|
+
setError(data.error ?? "切换失败");
|
|
3424
|
+
setItems((prev) => prev.map((i) => i.id === item.id ? {
|
|
3425
|
+
...i,
|
|
3426
|
+
enabled: !value
|
|
3427
|
+
} : i));
|
|
3428
|
+
}
|
|
3429
|
+
} catch (err) {
|
|
3430
|
+
if (mounted.current) {
|
|
3431
|
+
setError(err.message);
|
|
3432
|
+
setItems((prev) => prev.map((i) => i.id === item.id ? {
|
|
3433
|
+
...i,
|
|
3434
|
+
enabled: !value
|
|
3435
|
+
} : i));
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3440
|
+
className: "dshr-tabBody",
|
|
3441
|
+
children: [error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3442
|
+
className: "dshr-alert",
|
|
3443
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "出错了" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
3444
|
+
}), !loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3445
|
+
className: "dshr-empty",
|
|
3446
|
+
children: "加载中…"
|
|
3447
|
+
}) : items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3448
|
+
className: "dshr-keyEmpty",
|
|
3449
|
+
children: [
|
|
3450
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3451
|
+
className: "dshr-keyEmptyIcon",
|
|
3452
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3453
|
+
d: I.puzzle,
|
|
3454
|
+
size: 30
|
|
3455
|
+
})
|
|
3456
|
+
}),
|
|
3457
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3458
|
+
className: "dshr-keyEmptyTitle",
|
|
3459
|
+
children: "暂无扩展"
|
|
3460
|
+
}),
|
|
3461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3462
|
+
className: "dshr-keyEmptyDesc",
|
|
3463
|
+
children: [
|
|
3464
|
+
"安装扩展插件(如 ",
|
|
3465
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "dsh-router-ext-rtk" }),
|
|
3466
|
+
")后,这里会出现开关"
|
|
3467
|
+
]
|
|
3468
|
+
})
|
|
3469
|
+
]
|
|
3470
|
+
}) : items.map((item) => {
|
|
3471
|
+
const notReady = item.ready === false;
|
|
3472
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3473
|
+
className: "dshr-card",
|
|
3474
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3475
|
+
className: "dshr-cardHead",
|
|
3476
|
+
children: [
|
|
3477
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3478
|
+
className: "dshr-cardIcon",
|
|
3479
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { d: I.bolt })
|
|
3480
|
+
}),
|
|
3481
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3482
|
+
className: "dshr-cardTitle",
|
|
3483
|
+
children: item.name
|
|
3484
|
+
}),
|
|
3485
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3486
|
+
type: "button",
|
|
3487
|
+
className: `dshr-toggle dshr-cardAction ${item.enabled ? "dshr-toggle-on" : ""}`,
|
|
3488
|
+
role: "switch",
|
|
3489
|
+
"aria-checked": !!item.enabled,
|
|
3490
|
+
disabled: notReady && !item.enabled,
|
|
3491
|
+
onClick: () => void toggle(item, !item.enabled),
|
|
3492
|
+
title: notReady && !item.enabled ? "当前不可用,无法开启" : item.enabled ? `关闭 ${item.name}` : `开启 ${item.name}`,
|
|
3493
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshr-toggleKnob" })
|
|
3494
|
+
})
|
|
3495
|
+
]
|
|
3496
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3497
|
+
className: "dshr-extBody",
|
|
3498
|
+
children: [item.description !== void 0 && item.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3499
|
+
className: "dshr-muted",
|
|
3500
|
+
children: item.description
|
|
3501
|
+
}), notReady && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3502
|
+
className: "dshr-ext-error",
|
|
3503
|
+
children: item.detail !== void 0 && item.detail !== "" ? item.detail : "未就绪,无法开启"
|
|
3504
|
+
})]
|
|
3505
|
+
})]
|
|
3506
|
+
}, item.id);
|
|
3507
|
+
})]
|
|
3508
|
+
});
|
|
3509
|
+
}
|
|
3510
|
+
//#endregion
|
|
3349
3511
|
//#region src/client/RouterView.tsx
|
|
3350
3512
|
/**
|
|
3351
3513
|
* 路由系统 (Routing System) panel — a simplified 9router.
|
|
3352
3514
|
*
|
|
3353
|
-
*
|
|
3515
|
+
* Five tabs fed by the host half (/router/api/*), mirroring 9router's
|
|
3354
3516
|
* dashboard: 概览 (usage), 供应商 (supplier cards → detail: accounts +
|
|
3355
3517
|
* add-account + models), 组合 (combo fallback chains),
|
|
3518
|
+
* 扩展 (extension plugins — one toggle each, self-checked),
|
|
3356
3519
|
* 端点与密钥 (endpoint URL + API keys + auth).
|
|
3357
3520
|
*
|
|
3358
3521
|
* **挂载在「设置」里**(`settings.section` 座位),所以布局规则跟中心栏劫持时期不同:
|
|
@@ -3496,6 +3659,14 @@ window.__ModuleLoader__.load({
|
|
|
3496
3659
|
children: combos.length
|
|
3497
3660
|
})]
|
|
3498
3661
|
}),
|
|
3662
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3663
|
+
type: "button",
|
|
3664
|
+
role: "tab",
|
|
3665
|
+
"aria-selected": tab === "ext",
|
|
3666
|
+
className: `dshr-tab${tab === "ext" ? " dshr-tab-on" : ""}`,
|
|
3667
|
+
onClick: () => setTab("ext"),
|
|
3668
|
+
children: "扩展"
|
|
3669
|
+
}),
|
|
3499
3670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3500
3671
|
type: "button",
|
|
3501
3672
|
role: "tab",
|
|
@@ -3558,7 +3729,8 @@ window.__ModuleLoader__.load({
|
|
|
3558
3729
|
aliases: snapshot.combos?.aliases ?? [],
|
|
3559
3730
|
onRefresh: () => void refresh()
|
|
3560
3731
|
})),
|
|
3561
|
-
tab === "endpoint" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndpointTab, {})
|
|
3732
|
+
tab === "endpoint" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndpointTab, {}),
|
|
3733
|
+
tab === "ext" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExtTab, {})
|
|
3562
3734
|
]
|
|
3563
3735
|
})]
|
|
3564
3736
|
});
|