dsh-router-core 0.3.19 → 0.3.21
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 +1 -1
- package/lib/client-registry.js +449 -125
- package/lib/client.js +449 -125
- package/lib/index.js +42 -6
- package/lib/suppliers/opencode.js +103 -19
- package/lib/types/ext/contract.d.ts +11 -0
- package/lib/types/shared.d.ts +19 -0
- package/package.json +1 -1
- package/src/client/ExtDetail.tsx +257 -0
- package/src/client/ExtTab.tsx +38 -38
- package/src/client/router.css +28 -0
- package/src/ext/contract.ts +11 -0
- package/src/index.ts +29 -0
- package/src/llm/adapter.test.ts +54 -1
- package/src/llm/adapter.ts +15 -3
- package/src/router/disconnect-leak.test.ts +50 -0
- package/src/router/index.ts +16 -3
- package/src/shared.ts +21 -0
- package/src/suppliers/opencode/plugin.test.ts +176 -0
- package/src/suppliers/opencode/plugin.ts +103 -21
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-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); }})();
|
|
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/* watch 进程行动作区(复制 PID / kill 命令,只读) */\n.dshr-procOps {\n display: inline-flex;\n align-items: center;\n justify-content: flex-end;\n gap: 4px;\n white-space: nowrap;\n}\n\n/* 扩展供应商式小卡片:未就绪红字(描述只在详情页,卡片不放) */\n.dshr-extCardError {\n color: var(--rs-danger);\n font-size: 11px;\n}\n\n/* 扩展卡 logo(如 rtk 白底 logo):黑底 + 内边距,白色 logo 在黑底上显形 */\n.dshr-extLogo {\n background: #000;\n padding: 3px;\n box-sizing: border-box;\n object-fit: contain;\n}\n\n/* 扩展详情头部右侧的开关(顶到标题行右边,同供应商详情的编辑按钮位) */\n.dshr-extDetailToggle {\n margin-left: auto;\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$6({ 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$6 = {
|
|
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",
|
|
@@ -617,8 +617,8 @@ window.__ModuleLoader__.load({
|
|
|
617
617
|
type: "button",
|
|
618
618
|
className: "dshr-backLink",
|
|
619
619
|
onClick: onBack,
|
|
620
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
621
|
-
d: I$
|
|
620
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
621
|
+
d: I$6.back,
|
|
622
622
|
size: 16
|
|
623
623
|
}), "返回"]
|
|
624
624
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -633,8 +633,8 @@ window.__ModuleLoader__.load({
|
|
|
633
633
|
onError: (e) => {
|
|
634
634
|
e.currentTarget.style.display = "none";
|
|
635
635
|
}
|
|
636
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
637
|
-
d: I$
|
|
636
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
637
|
+
d: I$6.link,
|
|
638
638
|
size: 22
|
|
639
639
|
})
|
|
640
640
|
}),
|
|
@@ -662,8 +662,8 @@ window.__ModuleLoader__.load({
|
|
|
662
662
|
setAliasError("");
|
|
663
663
|
setShowAlias(true);
|
|
664
664
|
},
|
|
665
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
666
|
-
d: I$
|
|
665
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
666
|
+
d: I$6.edit,
|
|
667
667
|
size: 15
|
|
668
668
|
})
|
|
669
669
|
})
|
|
@@ -693,8 +693,8 @@ window.__ModuleLoader__.load({
|
|
|
693
693
|
"aria-label": "刷新积分并探测健康",
|
|
694
694
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
695
695
|
className: refreshing ? "dshr-spin" : void 0,
|
|
696
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
697
|
-
d: I$
|
|
696
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
697
|
+
d: I$6.refresh,
|
|
698
698
|
size: 15
|
|
699
699
|
})
|
|
700
700
|
})
|
|
@@ -708,8 +708,8 @@ window.__ModuleLoader__.load({
|
|
|
708
708
|
"aria-label": "签到所有链接",
|
|
709
709
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
710
710
|
className: checkingIn ? "dshr-spin" : void 0,
|
|
711
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
712
|
-
d: I$
|
|
711
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
712
|
+
d: I$6.checkCircle,
|
|
713
713
|
size: 15
|
|
714
714
|
})
|
|
715
715
|
})
|
|
@@ -719,8 +719,8 @@ window.__ModuleLoader__.load({
|
|
|
719
719
|
className: "dshr-primaryButton",
|
|
720
720
|
onClick: () => void startLogin(),
|
|
721
721
|
disabled: busy,
|
|
722
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
723
|
-
d: I$
|
|
722
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
723
|
+
d: I$6.add,
|
|
724
724
|
size: 14
|
|
725
725
|
}), "添加链接"]
|
|
726
726
|
})
|
|
@@ -734,8 +734,8 @@ window.__ModuleLoader__.load({
|
|
|
734
734
|
children: [
|
|
735
735
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
736
736
|
className: "dshr-linkEmptyIcon",
|
|
737
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
738
|
-
d: I$
|
|
737
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
738
|
+
d: I$6.link,
|
|
739
739
|
size: 28
|
|
740
740
|
})
|
|
741
741
|
}),
|
|
@@ -754,8 +754,8 @@ window.__ModuleLoader__.load({
|
|
|
754
754
|
className: "dshr-primaryButton",
|
|
755
755
|
onClick: () => void startLogin(),
|
|
756
756
|
disabled: busy,
|
|
757
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
758
|
-
d: I$
|
|
757
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
758
|
+
d: I$6.add,
|
|
759
759
|
size: 14
|
|
760
760
|
}), "添加链接"]
|
|
761
761
|
})
|
|
@@ -896,8 +896,8 @@ window.__ModuleLoader__.load({
|
|
|
896
896
|
if (renameUid === account.uid) saveRename(account);
|
|
897
897
|
else startRename(account);
|
|
898
898
|
},
|
|
899
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
900
|
-
d: renameUid === account.uid ? I$
|
|
899
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
900
|
+
d: renameUid === account.uid ? I$6.check : I$6.edit,
|
|
901
901
|
size: 15
|
|
902
902
|
})
|
|
903
903
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -905,8 +905,8 @@ window.__ModuleLoader__.load({
|
|
|
905
905
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-linkDelete",
|
|
906
906
|
title: "删除链接",
|
|
907
907
|
onClick: () => setRemoveTarget(account),
|
|
908
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
909
|
-
d: I$
|
|
908
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
909
|
+
d: I$6.delete,
|
|
910
910
|
size: 15
|
|
911
911
|
})
|
|
912
912
|
})]
|
|
@@ -950,8 +950,8 @@ window.__ModuleLoader__.load({
|
|
|
950
950
|
setShowAddModel(true);
|
|
951
951
|
},
|
|
952
952
|
disabled: toggling,
|
|
953
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
954
|
-
d: I$
|
|
953
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
954
|
+
d: I$6.add,
|
|
955
955
|
size: 13
|
|
956
956
|
}), "添加模型"]
|
|
957
957
|
}),
|
|
@@ -977,8 +977,8 @@ window.__ModuleLoader__.load({
|
|
|
977
977
|
className: "dshr-modelSearch",
|
|
978
978
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
979
979
|
className: "dshr-modelSearchIcon",
|
|
980
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
981
|
-
d: I$
|
|
980
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
981
|
+
d: I$6.search,
|
|
982
982
|
size: 13
|
|
983
983
|
})
|
|
984
984
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -1009,8 +1009,8 @@ window.__ModuleLoader__.load({
|
|
|
1009
1009
|
onClick: () => void toggleModel(model.id, false),
|
|
1010
1010
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1011
1011
|
className: "dshr-modelChipStatus",
|
|
1012
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1013
|
-
d: status === "ok" ? I$
|
|
1012
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
1013
|
+
d: status === "ok" ? I$6.checkCircle : status === "error" ? I$6.cancel : I$6.robot,
|
|
1014
1014
|
size: 13
|
|
1015
1015
|
})
|
|
1016
1016
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1027,8 +1027,8 @@ window.__ModuleLoader__.load({
|
|
|
1027
1027
|
onClick: () => void testModel(model.id),
|
|
1028
1028
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1029
1029
|
className: testingIds.has(model.id) ? "dshr-spin" : void 0,
|
|
1030
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1031
|
-
d: testingIds.has(model.id) ? I$
|
|
1030
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
1031
|
+
d: testingIds.has(model.id) ? I$6.loading : I$6.test,
|
|
1032
1032
|
size: 13
|
|
1033
1033
|
})
|
|
1034
1034
|
})
|
|
@@ -1038,8 +1038,8 @@ window.__ModuleLoader__.load({
|
|
|
1038
1038
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1039
1039
|
title: "复制模型名",
|
|
1040
1040
|
onClick: () => void copyModel(model.id),
|
|
1041
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1042
|
-
d: copiedModel === model.id ? I$
|
|
1041
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
1042
|
+
d: copiedModel === model.id ? I$6.check : I$6.copy,
|
|
1043
1043
|
size: 13
|
|
1044
1044
|
})
|
|
1045
1045
|
}),
|
|
@@ -1048,8 +1048,8 @@ window.__ModuleLoader__.load({
|
|
|
1048
1048
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-modelDelete",
|
|
1049
1049
|
title: "删除自定义模型",
|
|
1050
1050
|
onClick: () => setRemoveModelTarget(model.id),
|
|
1051
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1052
|
-
d: I$
|
|
1051
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$6, {
|
|
1052
|
+
d: I$6.delete,
|
|
1053
1053
|
size: 13
|
|
1054
1054
|
})
|
|
1055
1055
|
})
|
|
@@ -1415,7 +1415,7 @@ window.__ModuleLoader__.load({
|
|
|
1415
1415
|
* API Endpoint 卡片 + API Keys 卡片(Require API key + flex 行 key 列表 +
|
|
1416
1416
|
* Create/暂停确认/Created 弹窗)。
|
|
1417
1417
|
*/
|
|
1418
|
-
function Icon$
|
|
1418
|
+
function Icon$5({ d, size = 18 }) {
|
|
1419
1419
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1420
1420
|
viewBox: "0 0 24 24",
|
|
1421
1421
|
width: size,
|
|
@@ -1429,7 +1429,7 @@ window.__ModuleLoader__.load({
|
|
|
1429
1429
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
1430
1430
|
});
|
|
1431
1431
|
}
|
|
1432
|
-
var I$
|
|
1432
|
+
var I$5 = {
|
|
1433
1433
|
api: "M4 4h16v16H4zM9 9h6v6H9zM12 9V7M12 17v-2M9 12H7M17 12h-2",
|
|
1434
1434
|
key: "M14 7a4 4 0 1 1-1.4 7.6L9 18H6v-3l4.4-4.4A4 4 0 0 1 14 7zM18 7h.01",
|
|
1435
1435
|
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",
|
|
@@ -1578,7 +1578,7 @@ window.__ModuleLoader__.load({
|
|
|
1578
1578
|
className: "dshr-cardHead",
|
|
1579
1579
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1580
1580
|
className: "dshr-cardIcon",
|
|
1581
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1581
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, { d: I$5.api })
|
|
1582
1582
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1583
1583
|
className: "dshr-cardTitle",
|
|
1584
1584
|
children: "API Endpoint"
|
|
@@ -1602,8 +1602,8 @@ window.__ModuleLoader__.load({
|
|
|
1602
1602
|
className: "dshr-iconBtn",
|
|
1603
1603
|
title: "复制",
|
|
1604
1604
|
onClick: () => void copyText(endpointBase, "__endpoint"),
|
|
1605
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1606
|
-
d: copiedKeyId === "__endpoint" ? I$
|
|
1605
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1606
|
+
d: copiedKeyId === "__endpoint" ? I$5.check : I$5.copy,
|
|
1607
1607
|
size: 16
|
|
1608
1608
|
})
|
|
1609
1609
|
})
|
|
@@ -1623,7 +1623,7 @@ window.__ModuleLoader__.load({
|
|
|
1623
1623
|
children: [
|
|
1624
1624
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1625
1625
|
className: "dshr-cardIcon",
|
|
1626
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1626
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, { d: I$5.key })
|
|
1627
1627
|
}),
|
|
1628
1628
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1629
1629
|
className: "dshr-cardTitle",
|
|
@@ -1633,8 +1633,8 @@ window.__ModuleLoader__.load({
|
|
|
1633
1633
|
type: "button",
|
|
1634
1634
|
className: "dshr-primaryButton dshr-cardAction",
|
|
1635
1635
|
onClick: openCreate,
|
|
1636
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1637
|
-
d: I$
|
|
1636
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1637
|
+
d: I$5.add,
|
|
1638
1638
|
size: 14
|
|
1639
1639
|
}), "创建 Key"]
|
|
1640
1640
|
})
|
|
@@ -1669,8 +1669,8 @@ window.__ModuleLoader__.load({
|
|
|
1669
1669
|
children: [
|
|
1670
1670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1671
1671
|
className: "dshr-keyEmptyIcon",
|
|
1672
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1673
|
-
d: I$
|
|
1672
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1673
|
+
d: I$5.key,
|
|
1674
1674
|
size: 30
|
|
1675
1675
|
})
|
|
1676
1676
|
}),
|
|
@@ -1686,8 +1686,8 @@ window.__ModuleLoader__.load({
|
|
|
1686
1686
|
type: "button",
|
|
1687
1687
|
className: "dshr-primaryButton",
|
|
1688
1688
|
onClick: openCreate,
|
|
1689
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1690
|
-
d: I$
|
|
1689
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1690
|
+
d: I$5.add,
|
|
1691
1691
|
size: 14
|
|
1692
1692
|
}), "创建 Key"]
|
|
1693
1693
|
})
|
|
@@ -1709,8 +1709,8 @@ window.__ModuleLoader__.load({
|
|
|
1709
1709
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-keyDelete",
|
|
1710
1710
|
title: "删除",
|
|
1711
1711
|
onClick: () => setDeleteTarget(key),
|
|
1712
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1713
|
-
d: I$
|
|
1712
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1713
|
+
d: I$5.delete,
|
|
1714
1714
|
size: 14
|
|
1715
1715
|
})
|
|
1716
1716
|
})]
|
|
@@ -1727,8 +1727,8 @@ window.__ModuleLoader__.load({
|
|
|
1727
1727
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1728
1728
|
title: visibleKeys.has(key.id) ? "隐藏" : "显示",
|
|
1729
1729
|
onClick: () => toggleVisible(key.id),
|
|
1730
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1731
|
-
d: visibleKeys.has(key.id) ? I$
|
|
1730
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1731
|
+
d: visibleKeys.has(key.id) ? I$5.visibilityOff : I$5.visibility,
|
|
1732
1732
|
size: 14
|
|
1733
1733
|
})
|
|
1734
1734
|
}),
|
|
@@ -1737,8 +1737,8 @@ window.__ModuleLoader__.load({
|
|
|
1737
1737
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
1738
1738
|
title: "复制",
|
|
1739
1739
|
onClick: () => void copyText(key.key, key.id),
|
|
1740
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
1741
|
-
d: copiedKeyId === key.id ? I$
|
|
1740
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$5, {
|
|
1741
|
+
d: copiedKeyId === key.id ? I$5.check : I$5.copy,
|
|
1742
1742
|
size: 14
|
|
1743
1743
|
})
|
|
1744
1744
|
})
|
|
@@ -1915,7 +1915,7 @@ window.__ModuleLoader__.load({
|
|
|
1915
1915
|
* 组合 = 命名的一组模型(完整名 alias/id),请求 model 传组合名时,
|
|
1916
1916
|
* 按策略(回退/轮询)命中其中一个。可创建/编辑/删除。
|
|
1917
1917
|
*/
|
|
1918
|
-
function Icon$
|
|
1918
|
+
function Icon$4({ d, size = 18 }) {
|
|
1919
1919
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1920
1920
|
viewBox: "0 0 24 24",
|
|
1921
1921
|
width: size,
|
|
@@ -1929,7 +1929,7 @@ window.__ModuleLoader__.load({
|
|
|
1929
1929
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
1930
1930
|
});
|
|
1931
1931
|
}
|
|
1932
|
-
var I$
|
|
1932
|
+
var I$4 = {
|
|
1933
1933
|
layers: "M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5M3 17l9 5 9-5",
|
|
1934
1934
|
add: "M12 5v14M5 12h14",
|
|
1935
1935
|
copy: "M9 9h11v11H9zM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1",
|
|
@@ -2151,8 +2151,8 @@ window.__ModuleLoader__.load({
|
|
|
2151
2151
|
children: [
|
|
2152
2152
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2153
2153
|
className: "dshr-cardIcon",
|
|
2154
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2155
|
-
d: I$
|
|
2154
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2155
|
+
d: I$4.layers,
|
|
2156
2156
|
size: 16
|
|
2157
2157
|
})
|
|
2158
2158
|
}),
|
|
@@ -2168,8 +2168,8 @@ window.__ModuleLoader__.load({
|
|
|
2168
2168
|
type: "button",
|
|
2169
2169
|
className: "dshr-primaryButton dshr-cardAction",
|
|
2170
2170
|
onClick: openCreate,
|
|
2171
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2172
|
-
d: I$
|
|
2171
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2172
|
+
d: I$4.add,
|
|
2173
2173
|
size: 14
|
|
2174
2174
|
}), "创建组合"]
|
|
2175
2175
|
})
|
|
@@ -2195,8 +2195,8 @@ window.__ModuleLoader__.load({
|
|
|
2195
2195
|
children: [
|
|
2196
2196
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2197
2197
|
className: "dshr-keyEmptyIcon",
|
|
2198
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2199
|
-
d: I$
|
|
2198
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2199
|
+
d: I$4.layers,
|
|
2200
2200
|
size: 30
|
|
2201
2201
|
})
|
|
2202
2202
|
}),
|
|
@@ -2212,8 +2212,8 @@ window.__ModuleLoader__.load({
|
|
|
2212
2212
|
type: "button",
|
|
2213
2213
|
className: "dshr-primaryButton",
|
|
2214
2214
|
onClick: openCreate,
|
|
2215
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2216
|
-
d: I$
|
|
2215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2216
|
+
d: I$4.add,
|
|
2217
2217
|
size: 14
|
|
2218
2218
|
}), "创建组合"]
|
|
2219
2219
|
})
|
|
@@ -2227,8 +2227,8 @@ window.__ModuleLoader__.load({
|
|
|
2227
2227
|
children: [
|
|
2228
2228
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2229
2229
|
className: "dshr-cardIcon",
|
|
2230
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2231
|
-
d: I$
|
|
2230
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2231
|
+
d: I$4.layers,
|
|
2232
2232
|
size: 16
|
|
2233
2233
|
})
|
|
2234
2234
|
}),
|
|
@@ -2261,8 +2261,8 @@ window.__ModuleLoader__.load({
|
|
|
2261
2261
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
2262
2262
|
title: "复制组合名",
|
|
2263
2263
|
onClick: () => void copyName(combo.name, combo.id),
|
|
2264
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2265
|
-
d: copied === combo.id ? I$
|
|
2264
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2265
|
+
d: copied === combo.id ? I$4.check : I$4.copy,
|
|
2266
2266
|
size: 15
|
|
2267
2267
|
})
|
|
2268
2268
|
}),
|
|
@@ -2271,8 +2271,8 @@ window.__ModuleLoader__.load({
|
|
|
2271
2271
|
className: "dshr-iconBtn dshr-iconBtn-sm",
|
|
2272
2272
|
title: "编辑",
|
|
2273
2273
|
onClick: () => openEdit(combo),
|
|
2274
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2275
|
-
d: I$
|
|
2274
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2275
|
+
d: I$4.edit,
|
|
2276
2276
|
size: 15
|
|
2277
2277
|
})
|
|
2278
2278
|
}),
|
|
@@ -2281,8 +2281,8 @@ window.__ModuleLoader__.load({
|
|
|
2281
2281
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboOpBtn-danger",
|
|
2282
2282
|
title: "删除",
|
|
2283
2283
|
onClick: () => setDeleteTarget(combo),
|
|
2284
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2285
|
-
d: I$
|
|
2284
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2285
|
+
d: I$4.delete,
|
|
2286
2286
|
size: 15
|
|
2287
2287
|
})
|
|
2288
2288
|
})
|
|
@@ -2378,8 +2378,8 @@ window.__ModuleLoader__.load({
|
|
|
2378
2378
|
]
|
|
2379
2379
|
}), form.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2380
2380
|
className: "dshr-comboEmptyStepsBox",
|
|
2381
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2382
|
-
d: I$
|
|
2381
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2382
|
+
d: I$4.layers,
|
|
2383
2383
|
size: 20
|
|
2384
2384
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2385
2385
|
className: "dshr-muted",
|
|
@@ -2401,8 +2401,8 @@ window.__ModuleLoader__.load({
|
|
|
2401
2401
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2402
2402
|
className: "dshr-comboGrip",
|
|
2403
2403
|
title: "拖拽排序",
|
|
2404
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2405
|
-
d: I$
|
|
2404
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2405
|
+
d: I$4.grip,
|
|
2406
2406
|
size: 14
|
|
2407
2407
|
})
|
|
2408
2408
|
}),
|
|
@@ -2419,8 +2419,8 @@ window.__ModuleLoader__.load({
|
|
|
2419
2419
|
className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboStepRemove",
|
|
2420
2420
|
title: "移除",
|
|
2421
2421
|
onClick: () => removeModel(index),
|
|
2422
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2423
|
-
d: I$
|
|
2422
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2423
|
+
d: I$4.delete,
|
|
2424
2424
|
size: 14
|
|
2425
2425
|
})
|
|
2426
2426
|
})
|
|
@@ -2440,8 +2440,8 @@ window.__ModuleLoader__.load({
|
|
|
2440
2440
|
className: "dshr-modelSearch dshr-comboModelSearch",
|
|
2441
2441
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2442
2442
|
className: "dshr-modelSearchIcon",
|
|
2443
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2444
|
-
d: I$
|
|
2443
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
|
|
2444
|
+
d: I$4.search,
|
|
2445
2445
|
size: 13
|
|
2446
2446
|
})
|
|
2447
2447
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -2558,7 +2558,7 @@ window.__ModuleLoader__.load({
|
|
|
2558
2558
|
* 要真状态的升级路径:给 supplier 契约加 `checkinStatus?()`,核心加一条
|
|
2559
2559
|
* `/suppliers/:id/checkin/status` 汇总路由,这里改成读它即可(卡片不用改)。
|
|
2560
2560
|
*/
|
|
2561
|
-
function Icon$
|
|
2561
|
+
function Icon$3({ d, size = 14 }) {
|
|
2562
2562
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2563
2563
|
viewBox: "0 0 24 24",
|
|
2564
2564
|
width: size,
|
|
@@ -2572,7 +2572,7 @@ window.__ModuleLoader__.load({
|
|
|
2572
2572
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
2573
2573
|
});
|
|
2574
2574
|
}
|
|
2575
|
-
var I$
|
|
2575
|
+
var I$3 = {
|
|
2576
2576
|
check: "M4 12.5l5 5L20 6.5",
|
|
2577
2577
|
loading: "M12 3a9 9 0 1 0 9 9"
|
|
2578
2578
|
};
|
|
@@ -2692,8 +2692,8 @@ window.__ModuleLoader__.load({
|
|
|
2692
2692
|
title: hintTitle(),
|
|
2693
2693
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2694
2694
|
className: busy ? "dshr-spin" : void 0,
|
|
2695
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2696
|
-
d: busy ? I$
|
|
2695
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
|
|
2696
|
+
d: busy ? I$3.loading : I$3.check,
|
|
2697
2697
|
size: 14
|
|
2698
2698
|
})
|
|
2699
2699
|
}), busy ? "签到中…" : "一键签到"]
|
|
@@ -2723,7 +2723,7 @@ window.__ModuleLoader__.load({
|
|
|
2723
2723
|
* token 口径见 `router/usage-tokens.ts`:上游不发 usage 时按字符数估算,
|
|
2724
2724
|
* 估算过的字段标 `~`,鼠标悬停会说明。
|
|
2725
2725
|
*/
|
|
2726
|
-
function Icon$
|
|
2726
|
+
function Icon$2({ d, size = 18 }) {
|
|
2727
2727
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2728
2728
|
viewBox: "0 0 24 24",
|
|
2729
2729
|
width: size,
|
|
@@ -2737,7 +2737,7 @@ window.__ModuleLoader__.load({
|
|
|
2737
2737
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
2738
2738
|
});
|
|
2739
2739
|
}
|
|
2740
|
-
var I$
|
|
2740
|
+
var I$2 = {
|
|
2741
2741
|
chart: "M3 3v18h18M7 15l4-5 3 3 5-7",
|
|
2742
2742
|
bolt: "M13 2L4 14h6l-1 8 9-12h-6z",
|
|
2743
2743
|
refresh: "M21 12a9 9 0 1 1-3-6.7M21 4v5h-5",
|
|
@@ -2881,8 +2881,8 @@ window.__ModuleLoader__.load({
|
|
|
2881
2881
|
className: "dshr-trendEmpty",
|
|
2882
2882
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2883
2883
|
className: "dshr-trendEmptyIcon",
|
|
2884
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
2885
|
-
d: I$
|
|
2884
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
|
|
2885
|
+
d: I$2.empty,
|
|
2886
2886
|
size: 26
|
|
2887
2887
|
})
|
|
2888
2888
|
}), "这个周期还没有请求"]
|
|
@@ -2997,7 +2997,7 @@ window.__ModuleLoader__.load({
|
|
|
2997
2997
|
children: [
|
|
2998
2998
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2999
2999
|
className: "dshr-cardIcon",
|
|
3000
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
3000
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, { d: I$2.chart })
|
|
3001
3001
|
}),
|
|
3002
3002
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3003
3003
|
className: "dshr-cardTitle",
|
|
@@ -3082,7 +3082,7 @@ window.__ModuleLoader__.load({
|
|
|
3082
3082
|
children: [
|
|
3083
3083
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3084
3084
|
className: "dshr-cardIcon",
|
|
3085
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
3085
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, { d: I$2.bolt })
|
|
3086
3086
|
}),
|
|
3087
3087
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3088
3088
|
className: "dshr-cardTitle",
|
|
@@ -3238,8 +3238,8 @@ window.__ModuleLoader__.load({
|
|
|
3238
3238
|
"aria-label": refreshing ? "刷新中" : "刷新统计",
|
|
3239
3239
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3240
3240
|
className: refreshing ? "dshr-spin" : void 0,
|
|
3241
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
3242
|
-
d: I$
|
|
3241
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
|
|
3242
|
+
d: I$2.refresh,
|
|
3243
3243
|
size: 16
|
|
3244
3244
|
})
|
|
3245
3245
|
})
|
|
@@ -3310,7 +3310,7 @@ window.__ModuleLoader__.load({
|
|
|
3310
3310
|
children: [
|
|
3311
3311
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3312
3312
|
className: "dshr-cardIcon",
|
|
3313
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$
|
|
3313
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, { d: I$2.chart })
|
|
3314
3314
|
}),
|
|
3315
3315
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3316
3316
|
className: "dshr-cardTitle",
|
|
@@ -3344,6 +3344,315 @@ window.__ModuleLoader__.load({
|
|
|
3344
3344
|
});
|
|
3345
3345
|
}
|
|
3346
3346
|
//#endregion
|
|
3347
|
+
//#region src/client/ExtDetail.tsx
|
|
3348
|
+
/**
|
|
3349
|
+
* 扩展详情页 —— 从设置 → 路由 → 扩展 的卡片点进来。
|
|
3350
|
+
*
|
|
3351
|
+
* 通用:返回键 + 扩展名/说明/就绪态。`watch` 扩展再渲染进程监控面板:
|
|
3352
|
+
* 实时采样「agent 启动的进程」,每行 PID / 命令 / CPU% / 内存%,**只读**——不提供
|
|
3353
|
+
* kill(杀进程是大事,交由用户在 CLI 自己操作)。面板提供「复制 PID」和
|
|
3354
|
+
* 「复制 kill 命令」两个剪贴板动作方便用户去终端处理。
|
|
3355
|
+
* 进程数据走后端 `/router/api/ext/:id/processes`;`ps` 瞬时失败时保留上一帧,别刷成
|
|
3356
|
+
* 整屏错误。
|
|
3357
|
+
*/
|
|
3358
|
+
function Icon$1({ d, size = 18 }) {
|
|
3359
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3360
|
+
viewBox: "0 0 24 24",
|
|
3361
|
+
width: size,
|
|
3362
|
+
height: size,
|
|
3363
|
+
fill: "none",
|
|
3364
|
+
stroke: "currentColor",
|
|
3365
|
+
strokeWidth: "1.8",
|
|
3366
|
+
strokeLinecap: "round",
|
|
3367
|
+
strokeLinejoin: "round",
|
|
3368
|
+
"aria-hidden": "true",
|
|
3369
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
var I$1 = {
|
|
3373
|
+
back: "M19 12H5M12 19l-7-7 7-7",
|
|
3374
|
+
refresh: "M20.5 12a8.5 8.5 0 1 1-2.6-6.1M20.5 4v5h-5",
|
|
3375
|
+
node: "M8 5.5L12 3l4 2.5v5L12 13l-4-2.5v-5zM4 9v5l4 2.5M12 13v5l4-2.5M20 9v5l-4 2.5",
|
|
3376
|
+
bolt: "M13 2L4 14h6l-1 8 9-12h-6z",
|
|
3377
|
+
copy: "M8 8h11v11H8zM5 14H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1",
|
|
3378
|
+
check: "M4 12.5l5 5L20 6.5"
|
|
3379
|
+
};
|
|
3380
|
+
/** 刷新按钮(带禁用中旋转)。 */
|
|
3381
|
+
function RefreshBtn({ onClick, busy }) {
|
|
3382
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3383
|
+
type: "button",
|
|
3384
|
+
className: "dshr-iconButton",
|
|
3385
|
+
onClick,
|
|
3386
|
+
disabled: busy,
|
|
3387
|
+
title: "刷新进程",
|
|
3388
|
+
"aria-label": "刷新进程",
|
|
3389
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3390
|
+
className: busy ? "dshr-spin" : void 0,
|
|
3391
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3392
|
+
d: I$1.refresh,
|
|
3393
|
+
size: 16
|
|
3394
|
+
})
|
|
3395
|
+
})
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
function fmtPct(v) {
|
|
3399
|
+
return `${(v ?? 0).toFixed(1)}%`;
|
|
3400
|
+
}
|
|
3401
|
+
function ExtDetail({ item, onBack, onToggle }) {
|
|
3402
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3403
|
+
className: "dshr-tabBody",
|
|
3404
|
+
children: [
|
|
3405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3406
|
+
className: "dshr-providerHead",
|
|
3407
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3408
|
+
type: "button",
|
|
3409
|
+
className: "dshr-backLink",
|
|
3410
|
+
onClick: onBack,
|
|
3411
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3412
|
+
d: I$1.back,
|
|
3413
|
+
size: 16
|
|
3414
|
+
}), "返回"]
|
|
3415
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3416
|
+
className: "dshr-providerTitleRow",
|
|
3417
|
+
children: [
|
|
3418
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3419
|
+
className: "dshr-providerIcon",
|
|
3420
|
+
style: {
|
|
3421
|
+
color: "var(--rs-faint)",
|
|
3422
|
+
background: "var(--rs-layer-2)"
|
|
3423
|
+
},
|
|
3424
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3425
|
+
d: I$1.bolt,
|
|
3426
|
+
size: 22
|
|
3427
|
+
})
|
|
3428
|
+
}),
|
|
3429
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3430
|
+
className: "dshr-providerMeta",
|
|
3431
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
3432
|
+
className: "dshr-providerName",
|
|
3433
|
+
children: item.name
|
|
3434
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3435
|
+
className: "dshr-providerCount",
|
|
3436
|
+
children: [
|
|
3437
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3438
|
+
className: "dshr-mono",
|
|
3439
|
+
children: item.id
|
|
3440
|
+
}),
|
|
3441
|
+
item.enabled ? " · 已启用" : " · 未启用",
|
|
3442
|
+
item.ready === false ? " · 未就绪" : ""
|
|
3443
|
+
]
|
|
3444
|
+
})]
|
|
3445
|
+
}),
|
|
3446
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3447
|
+
type: "button",
|
|
3448
|
+
className: `dshr-toggle dshr-extDetailToggle ${item.enabled ? "dshr-toggle-on" : ""}`,
|
|
3449
|
+
role: "switch",
|
|
3450
|
+
"aria-checked": !!item.enabled,
|
|
3451
|
+
disabled: item.ready === false && !item.enabled,
|
|
3452
|
+
onClick: () => void onToggle(item, !item.enabled),
|
|
3453
|
+
title: item.ready === false && !item.enabled ? "当前不可用,无法开启" : item.enabled ? `关闭 ${item.name}` : `开启 ${item.name}`,
|
|
3454
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshr-toggleKnob" })
|
|
3455
|
+
})
|
|
3456
|
+
]
|
|
3457
|
+
})]
|
|
3458
|
+
}),
|
|
3459
|
+
item.description !== void 0 && item.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
3460
|
+
className: "dshr-card",
|
|
3461
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3462
|
+
className: "dshr-muted",
|
|
3463
|
+
style: { padding: "12px 14px" },
|
|
3464
|
+
children: item.description
|
|
3465
|
+
})
|
|
3466
|
+
}),
|
|
3467
|
+
item.ready === false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3468
|
+
className: "dshr-alert",
|
|
3469
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "未就绪" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.detail !== void 0 && item.detail !== "" ? item.detail : "当前不可用" })]
|
|
3470
|
+
}),
|
|
3471
|
+
item.id === "watch" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WatchPanel, {}) : null
|
|
3472
|
+
]
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
/** watch 的进程监控面板:轮询采样(只读)+ 复制 PID / 复制 kill 命令。 */
|
|
3476
|
+
function WatchPanel() {
|
|
3477
|
+
const [processes, setProcesses] = (0, react.useState)([]);
|
|
3478
|
+
const [error, setError] = (0, react.useState)("");
|
|
3479
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
3480
|
+
const [copied, setCopied] = (0, react.useState)(null);
|
|
3481
|
+
const alive = (0, react.useRef)(true);
|
|
3482
|
+
const now = (0, react.useRef)(Date.now());
|
|
3483
|
+
(0, react.useEffect)(() => {
|
|
3484
|
+
alive.current = true;
|
|
3485
|
+
return () => {
|
|
3486
|
+
alive.current = false;
|
|
3487
|
+
};
|
|
3488
|
+
}, []);
|
|
3489
|
+
const load = async () => {
|
|
3490
|
+
try {
|
|
3491
|
+
const res = await fetch(`${ROUTER_API_BASE}/ext/watch/processes`, { cache: "no-store" });
|
|
3492
|
+
if (!res.ok) {
|
|
3493
|
+
let reason = "";
|
|
3494
|
+
try {
|
|
3495
|
+
const err = await res.json();
|
|
3496
|
+
if (err.error) reason = ` — ${err.error}`;
|
|
3497
|
+
} catch {}
|
|
3498
|
+
if (alive.current) setError(`HTTP ${res.status}${reason}`);
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
let data;
|
|
3502
|
+
try {
|
|
3503
|
+
data = await res.json();
|
|
3504
|
+
} catch {
|
|
3505
|
+
if (alive.current) setError("响应不是 JSON");
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
if (!alive.current) return;
|
|
3509
|
+
setError(data.ok ? "" : data.error ?? "加载进程失败");
|
|
3510
|
+
if (data.ok && data.processes !== void 0) setProcesses(data.processes);
|
|
3511
|
+
} catch (err) {
|
|
3512
|
+
if (alive.current) setError(err.message);
|
|
3513
|
+
} finally {
|
|
3514
|
+
if (alive.current) setLoading(false);
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
(0, react.useEffect)(() => {
|
|
3518
|
+
load();
|
|
3519
|
+
const timer = setInterval(() => {
|
|
3520
|
+
load();
|
|
3521
|
+
}, 3e3);
|
|
3522
|
+
return () => clearInterval(timer);
|
|
3523
|
+
}, []);
|
|
3524
|
+
const copy = (pid, text) => {
|
|
3525
|
+
const stamp = Date.now();
|
|
3526
|
+
now.current = stamp;
|
|
3527
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
3528
|
+
if (alive.current && now.current === stamp) {
|
|
3529
|
+
setCopied({
|
|
3530
|
+
pid,
|
|
3531
|
+
text
|
|
3532
|
+
});
|
|
3533
|
+
setTimeout(() => {
|
|
3534
|
+
if (alive.current) setCopied((c) => c?.pid === pid ? null : c);
|
|
3535
|
+
}, 1200);
|
|
3536
|
+
}
|
|
3537
|
+
}).catch(() => {
|
|
3538
|
+
if (alive.current) setError("复制失败");
|
|
3539
|
+
});
|
|
3540
|
+
};
|
|
3541
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3542
|
+
className: "dshr-card",
|
|
3543
|
+
children: [
|
|
3544
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3545
|
+
className: "dshr-cardHead",
|
|
3546
|
+
children: [
|
|
3547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3548
|
+
className: "dshr-cardIcon",
|
|
3549
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, { d: I$1.node })
|
|
3550
|
+
}),
|
|
3551
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3552
|
+
className: "dshr-cardTitle",
|
|
3553
|
+
children: "运行进程"
|
|
3554
|
+
}),
|
|
3555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3556
|
+
className: "dshr-cardMeta",
|
|
3557
|
+
children: "agent 启动 · 只读 · 3s 采样"
|
|
3558
|
+
}),
|
|
3559
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefreshBtn, {
|
|
3560
|
+
onClick: () => void load(),
|
|
3561
|
+
busy: loading
|
|
3562
|
+
})
|
|
3563
|
+
]
|
|
3564
|
+
}),
|
|
3565
|
+
error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3566
|
+
className: "dshr-alert",
|
|
3567
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "采样失败" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [error, "(保留上一帧)"] })]
|
|
3568
|
+
}),
|
|
3569
|
+
!loading && processes.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3570
|
+
className: "dshr-empty",
|
|
3571
|
+
children: [
|
|
3572
|
+
"暂无会话中 agent 启动的进程。运行诸如 ",
|
|
3573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "node server.js" }),
|
|
3574
|
+
" 的长驻命令后 会出现在这里。"
|
|
3575
|
+
]
|
|
3576
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3577
|
+
className: "dshr-rankList",
|
|
3578
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3579
|
+
className: "dshr-rankRow dshr-rankHead",
|
|
3580
|
+
children: [
|
|
3581
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3582
|
+
className: "dshr-rankName",
|
|
3583
|
+
children: "PID / 命令"
|
|
3584
|
+
}),
|
|
3585
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3586
|
+
className: "dshr-rankNum",
|
|
3587
|
+
children: "CPU"
|
|
3588
|
+
}),
|
|
3589
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3590
|
+
className: "dshr-rankNum",
|
|
3591
|
+
children: "内存"
|
|
3592
|
+
}),
|
|
3593
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshr-rankNum" })
|
|
3594
|
+
]
|
|
3595
|
+
}), processes.map((p) => {
|
|
3596
|
+
const done = copied?.pid === p.pid;
|
|
3597
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3598
|
+
className: "dshr-rankRow",
|
|
3599
|
+
children: [
|
|
3600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3601
|
+
className: "dshr-rankName dshr-mono",
|
|
3602
|
+
title: p.command,
|
|
3603
|
+
children: [
|
|
3604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3605
|
+
className: "dshr-muted",
|
|
3606
|
+
children: p.pid
|
|
3607
|
+
}),
|
|
3608
|
+
" ",
|
|
3609
|
+
p.command
|
|
3610
|
+
]
|
|
3611
|
+
}),
|
|
3612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3613
|
+
className: "dshr-rankNum",
|
|
3614
|
+
children: fmtPct(p.cpu)
|
|
3615
|
+
}),
|
|
3616
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3617
|
+
className: "dshr-rankNum",
|
|
3618
|
+
children: fmtPct(p.mem)
|
|
3619
|
+
}),
|
|
3620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3621
|
+
className: "dshr-rankNum dshr-procOps",
|
|
3622
|
+
children: done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3623
|
+
className: "dshr-muted",
|
|
3624
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3625
|
+
d: I$1.check,
|
|
3626
|
+
size: 13
|
|
3627
|
+
}), " 已复制"]
|
|
3628
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3629
|
+
type: "button",
|
|
3630
|
+
className: "dshr-miniButton",
|
|
3631
|
+
onClick: () => copy(p.pid, String(p.pid)),
|
|
3632
|
+
title: `复制 PID ${p.pid}`,
|
|
3633
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3634
|
+
d: I$1.copy,
|
|
3635
|
+
size: 13
|
|
3636
|
+
}), " PID"]
|
|
3637
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3638
|
+
type: "button",
|
|
3639
|
+
className: "dshr-miniButton",
|
|
3640
|
+
onClick: () => copy(p.pid, `kill -TERM ${p.pid}`),
|
|
3641
|
+
title: `复制 kill -TERM ${p.pid}`,
|
|
3642
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
|
|
3643
|
+
d: I$1.copy,
|
|
3644
|
+
size: 13
|
|
3645
|
+
}), " kill"]
|
|
3646
|
+
})] })
|
|
3647
|
+
})
|
|
3648
|
+
]
|
|
3649
|
+
}, p.pid);
|
|
3650
|
+
})]
|
|
3651
|
+
})
|
|
3652
|
+
]
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3655
|
+
//#endregion
|
|
3347
3656
|
//#region src/client/ExtTab.tsx
|
|
3348
3657
|
/**
|
|
3349
3658
|
* 扩展 (Ext) 页 —— 列出所有注册进 `router.ext` 的扩展插件,每个一个开关。
|
|
@@ -3377,6 +3686,7 @@ window.__ModuleLoader__.load({
|
|
|
3377
3686
|
const [items, setItems] = (0, react.useState)([]);
|
|
3378
3687
|
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
3379
3688
|
const [error, setError] = (0, react.useState)("");
|
|
3689
|
+
const [selectedId, setSelectedId] = (0, react.useState)(null);
|
|
3380
3690
|
const mounted = (0, react.useRef)(true);
|
|
3381
3691
|
(0, react.useEffect)(() => {
|
|
3382
3692
|
mounted.current = true;
|
|
@@ -3434,6 +3744,12 @@ window.__ModuleLoader__.load({
|
|
|
3434
3744
|
}
|
|
3435
3745
|
}
|
|
3436
3746
|
};
|
|
3747
|
+
const selected = selectedId !== null ? items.find((i) => i.id === selectedId) ?? null : null;
|
|
3748
|
+
if (selected !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExtDetail, {
|
|
3749
|
+
item: selected,
|
|
3750
|
+
onBack: () => setSelectedId(null),
|
|
3751
|
+
onToggle: toggle
|
|
3752
|
+
});
|
|
3437
3753
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3438
3754
|
className: "dshr-tabBody",
|
|
3439
3755
|
children: [error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -3460,48 +3776,56 @@ window.__ModuleLoader__.load({
|
|
|
3460
3776
|
className: "dshr-keyEmptyDesc",
|
|
3461
3777
|
children: [
|
|
3462
3778
|
"安装扩展插件(如 ",
|
|
3463
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "dsh-router-ext-
|
|
3779
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "dsh-router-ext-watch" }),
|
|
3464
3780
|
")后,这里会出现开关"
|
|
3465
3781
|
]
|
|
3466
3782
|
})
|
|
3467
3783
|
]
|
|
3468
|
-
}) :
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
className: "dshr-
|
|
3784
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3785
|
+
className: "dshr-supplierGrid",
|
|
3786
|
+
children: items.map((item) => {
|
|
3787
|
+
const notReady = item.ready === false;
|
|
3788
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3789
|
+
className: "dshr-supplierCard",
|
|
3790
|
+
onClick: () => setSelectedId(item.id),
|
|
3474
3791
|
children: [
|
|
3475
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3476
|
-
className: "dshr-
|
|
3477
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3792
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3793
|
+
className: "dshr-supplierRow",
|
|
3794
|
+
children: [item.icon !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
3795
|
+
className: "dshr-supplierIcon dshr-extLogo",
|
|
3796
|
+
src: item.icon,
|
|
3797
|
+
alt: "",
|
|
3798
|
+
onError: (e) => {
|
|
3799
|
+
e.currentTarget.style.display = "none";
|
|
3800
|
+
}
|
|
3801
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3802
|
+
className: "dshr-supplierIcon",
|
|
3803
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3804
|
+
d: I.bolt,
|
|
3805
|
+
size: 24
|
|
3806
|
+
})
|
|
3807
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3808
|
+
className: "dshr-supplierName",
|
|
3809
|
+
children: item.name
|
|
3810
|
+
})]
|
|
3478
3811
|
}),
|
|
3479
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3480
|
-
className: "dshr-
|
|
3481
|
-
children: item.
|
|
3812
|
+
notReady && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3813
|
+
className: "dshr-extCardError",
|
|
3814
|
+
children: item.detail !== void 0 && item.detail !== "" ? item.detail : "未就绪"
|
|
3482
3815
|
}),
|
|
3483
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3816
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3817
|
+
className: "dshr-supplierMeta",
|
|
3818
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3819
|
+
className: "dshr-muted",
|
|
3820
|
+
children: item.enabled ? "已启用" : "未启用"
|
|
3821
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3822
|
+
className: "dshr-chevron",
|
|
3823
|
+
children: "›"
|
|
3824
|
+
})]
|
|
3492
3825
|
})
|
|
3493
3826
|
]
|
|
3494
|
-
}
|
|
3495
|
-
|
|
3496
|
-
children: [item.description !== void 0 && item.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3497
|
-
className: "dshr-muted",
|
|
3498
|
-
children: item.description
|
|
3499
|
-
}), notReady && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3500
|
-
className: "dshr-ext-error",
|
|
3501
|
-
children: item.detail !== void 0 && item.detail !== "" ? item.detail : "未就绪,无法开启"
|
|
3502
|
-
})]
|
|
3503
|
-
})]
|
|
3504
|
-
}, item.id);
|
|
3827
|
+
}, item.id);
|
|
3828
|
+
})
|
|
3505
3829
|
})]
|
|
3506
3830
|
});
|
|
3507
3831
|
}
|