dsh-router-core 0.3.0 → 0.3.2

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.
@@ -3,7 +3,7 @@
3
3
  try {
4
4
  if (typeof document != "undefined") {
5
5
  var elementStyle = document.createElement("style");
6
- 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 — center-column panel\n ============================================================ */\n\n[data-pane='conversation'] {\n position: relative;\n}\n\n.dshDesktopConversationSurface {\n position: relative;\n}\n\n[data-dsh-router-view] {\n position: absolute;\n z-index: 60;\n inset: 0;\n display: none;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n}\n\n.dshr-panelView {\n isolation: isolate;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([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/* ============================================================\n dsh-router — panel shell\n ============================================================ */\n\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 display: flex;\n height: 100%;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n color: var(--rs-text);\n background: var(--rs-bg);\n font: 13px/1.55 var(--rs-sans);\n -webkit-tap-highlight-color: transparent;\n}\n\n.dshr-shell *,\n.dshr-shell *::before,\n.dshr-shell *::after { box-sizing: border-box; }\n\n.dshr-shell button,\n.dshr-shell input,\n.dshr-shell select,\n.dshr-shell textarea { color: inherit; font: inherit; }\n\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.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.dshr-tabBody {\n display: flex;\n flex: 1 1 auto; /* 填满 content;自身滚动 */\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n gap: 14px;\n padding: 14px 18px;\n overflow-y: auto;\n}\n\n.dshr-card {\n flex: none; /* 不参与 flex 伸缩:高度=内容,避免被压缩裁切 */\n min-width: 0;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n overflow: hidden;\n}\n\n.dshr-cardHead {\n display: flex;\n min-height: 44px;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-cardTitle {\n font-size: 13px;\n font-weight: 650;\n}\n\n.dshr-cardMeta {\n margin-left: auto;\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 组合链 */\n.dshr-comboChain {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-comboStep {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dshr-comboArrow {\n color: var(--rs-faint);\n margin-right: 8px;\n}\n\n.dshr-comboSupplier {\n font-family: var(--rs-mono);\n font-size: 12px;\n}\n\n/* 端点与密钥 */\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px;\n}\n\n.dshr-endpointCode {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-2);\n color: var(--rs-text);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-miniButton {\n display: inline-flex;\n flex: none;\n height: 28px;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-miniButton:hover { color: var(--rs-text); background: var(--rs-hover); }\n\n.dshr-code {\n margin: 0;\n padding: 12px 14px;\n overflow-x: auto;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font: 12px/1.6 var(--rs-mono);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n/* ============================================================\n dsh-router — 供应商详情 / 端点与密钥 / 通用控件\n ============================================================ */\n\n/* 详情页头 */\n.dshr-detailHead {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 36px;\n}\n\n.dshr-detailTitle {\n font-size: 14px;\n font-weight: 650;\n}\n\n/* 可点击卡片 */\n.dshr-card-click {\n cursor: pointer;\n transition: border-color .12s ease;\n}\n\n.dshr-card-click:hover { border-color: var(--rs-accent); }\n\n.dshr-cardHint {\n padding: 0 14px 12px;\n}\n\n/* 供应商小卡片(grid 布局,替代整行大卡片) */\n.dshr-supplierGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));\n gap: 12px;\n}\n\n/* 供应商分组(内置 / 插件分开) */\n.dshr-supplierGroup {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-supplierGroup + .dshr-supplierGroup {\n margin-top: 22px;\n}\n\n.dshr-supplierGroupTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-muted);\n letter-spacing: .04em;\n padding-left: 2px;\n}\n\n.dshr-supplierCard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n padding: 16px 18px;\n border: 1px solid var(--rs-line);\n border-radius: 12px;\n background: var(--rs-layer-1);\n cursor: pointer;\n transition: border-color .12s ease;\n}\n\n.dshr-supplierCard:hover { border-color: var(--rs-accent); }\n\n.dshr-supplierName {\n font-size: 14px;\n font-weight: 650;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-supplierRow {\n display: flex;\n align-items: center;\n gap: 9px;\n min-width: 0;\n}\n\n.dshr-supplierIcon {\n width: 24px;\n height: 24px;\n flex: none;\n border-radius: 5px;\n object-fit: contain;\n color: var(--rs-faint);\n}\n\n.dshr-supplierMeta {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-chevron {\n color: var(--rs-faint);\n font-size: 18px;\n line-height: 1;\n margin-left: auto;\n}\n\n/* 登录框 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n background: var(--rs-layer-2);\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 输入框 */\n.dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-text);\n font-size: 12px;\n}\n\n.dshr-input:focus {\n outline: none;\n border-color: var(--rs-accent);\n}\n\n/* 成功提示 */\n.dshr-alert-ok {\n border-color: color-mix(in srgb, var(--rs-ok) 45%, transparent);\n background: color-mix(in srgb, var(--rs-ok) 10%, transparent);\n}\n\n.dshr-createdKey {\n display: block;\n margin: 6px 0;\n padding: 8px 10px;\n border-radius: 6px;\n background: var(--rs-layer-1);\n word-break: break-all;\n}\n\n/* 模型卡片行 */\n.dshr-modelRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dshr-modelCard-off {\n opacity: .55;\n}\n\n/* 开关 */\n.dshr-toggle {\n position: relative;\n flex: none;\n width: 30px;\n height: 16px;\n border: none;\n border-radius: 8px;\n background: var(--rs-line);\n cursor: pointer;\n padding: 0;\n transition: background .15s ease;\n}\n\n.dshr-toggle-on { background: var(--rs-ok); }\n\n.dshr-toggleKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n transition: transform .15s ease;\n}\n\n.dshr-toggle-on .dshr-toggleKnob { transform: translateX(14px); }\n\n/* 操作按钮组 */\n.dshr-ops {\n display: flex;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dshr-danger { color: var(--rs-danger); }\n.dshr-danger:hover { color: var(--rs-danger); border-color: var(--rs-danger); }\n\n/* ============================================================\n dsh-router — 弹窗 + 按钮 + 整体打磨\n ============================================================ */\n\n/* 弹窗 */\n.dshr-modalOverlay {\n position: fixed;\n z-index: 1000;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: color-mix(in srgb, #000 42%, transparent);\n backdrop-filter: blur(2px);\n animation: dshrFadeIn .12s ease;\n}\n\n.dshr-modal {\n width: min(560px, 100%);\n max-height: min(80vh, 640px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--rs-line-strong);\n border-radius: 12px;\n background: var(--rs-layer-1);\n box-shadow: 0 20px 50px rgb(0 0 0 / .35), 0 0 0 1px rgb(0 0 0 / .05);\n animation: dshrModalIn .16s ease;\n}\n\n.dshr-modalHead {\n display: flex;\n min-height: 48px;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n padding: 0 16px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-modalTitle {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n font-size: 14px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modalClose {\n display: inline-flex;\n flex: none;\n width: 26px;\n height: 26px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n}\n\n.dshr-modalClose:hover { color: var(--rs-text); background: var(--rs-hover); }\n\n.dshr-modalBody {\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.dshr-modalActions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n\n@keyframes dshrFadeIn { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dshrModalIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }\n\n/* 按钮 */\n.dshr-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-shell .dshr-primaryButton:hover { filter: brightness(1.08); }\n.dshr-shell .dshr-primaryButton:disabled { opacity: .55; cursor: default; }\n\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-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/* 模型 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/* 周期按钮三态。特异性阶梯(类/伪类数 , 元素数):\n 文件顶部有 `.dshr-shell button { color: inherit; font: inherit }` = (1,1),\n 单类的 .dshr-period (1,0) 会被它打死(白字、600 字重全丢),所以下面每条\n 都必须带 .dshr-shell 抬到 (2,0) 以上。\n 两种 hover 用 :not() 做成互斥,谁赢跟源码顺序无关(免得以后有人挪块就翻车)。 */\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-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-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-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.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/*$vite$:1*/"));
6
+ elementStyle.appendChild(document.createTextNode("/* ============================================================\n dsh-router — sidebar entry\n ============================================================ */\n\n.dshr-entry {\n box-sizing: border-box;\n display: flex;\n width: 100%;\n min-height: 36px;\n align-items: center;\n gap: 10px;\n border: none;\n border-radius: 8px;\n padding: 0 10px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-entry:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshr-entry[data-active] {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-active);\n font-weight: 600;\n}\n\n.dshr-entryIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n\n.dshr-entryIcon svg {\n display: block;\n width: 18px;\n height: 18px;\n}\n\n.dshr-entryLabel {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entry {\n width: 36px;\n min-height: 36px;\n justify-content: center;\n margin: 0 auto 12px;\n padding: 0;\n border-radius: 50%;\n}\n\n[data-dsh-frame][data-sidebar-collapsed] .dshr-entryLabel {\n display: none;\n}\n\n/* ============================================================\n dsh-router — 主题变量(设置页 .dshr-settings / 老中心栏 .dshr-shell 共用)\n ============================================================ */\n\n.dshr-settings,\n.dshr-shell {\n --rs-bg: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n --rs-layer-1: var(--dsw-alias-bg-layer-1);\n --rs-layer-2: var(--dsw-alias-bg-layer-2);\n --rs-input: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-2));\n --rs-text: var(--dsw-alias-label-primary);\n --rs-muted: var(--dsw-alias-label-secondary);\n --rs-faint: var(--dsw-alias-label-tertiary);\n --rs-line: var(--dsw-alias-border-l1);\n --rs-line-strong: var(--dsw-alias-border-l2);\n --rs-accent: var(--dsw-alias-state-business-primary);\n --rs-hover: var(--dsw-alias-interactive-bg-hover);\n --rs-danger: var(--dsw-alias-state-error-primary);\n --rs-success: var(--dsw-alias-state-success-primary);\n --rs-ok: var(--rs-success);\n --rs-warn: #9a6a18;\n --rs-mono: var(--ds-font-family-code, 'SFMono-Regular', Consolas, monospace);\n --rs-sans: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);\n box-sizing: border-box;\n min-width: 0;\n color: var(--rs-text);\n font: 13px/1.55 var(--rs-sans);\n -webkit-tap-highlight-color: transparent;\n}\n\n/* ============================================================\n dsh-router — 设置页容器(settings.section 座位)\n ============================================================ */\n\n/* 容器几何(浏览器实测,改之前先量别凭感觉):\n - 内容宽 564px(面板 612 − 左右 padding 24)\n - 外壳 .VOzbGW_options 是 `overflow-y:auto` + `flex:1 1 0%`,高度确定\n (视口 813 时可视高 703~746)\n - 根节点外面套了一层 display:contents(宿主 slot renderer 给的),**没有\n 盒子** —— 但实测 `height:100%` 仍能穿过它解析到外壳高度\n (验过:root 从 1332px 溢出态变成 722px,正好填满容器)\n\n 布局规则:**tab 条固定,只有下面的内容区滚**。\n 所以根节点必须限高(不限高内容会把外壳撑开,变成整页滚),内容区再\n min-height:0 + overflow-y:auto 自己滚。\n 关键字是 min-height:0 —— flex 子项默认 min-height:auto,不给 0 的话\n 子项缩不小,overflow 永远不触发。 */\n.dshr-settings {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after { box-sizing: border-box; }\n\n/* 主导航 = tab 条。照 DSH 原生的内容区 tab(插件页那套 pbvGtq_tab):\n 容器一条 1px 底部分隔线,激活项用 ::after 画 2px 下划线压在线上。\n 刻意**不用**填充色块——那会跟下面的周期按钮组撞成同一种控件,\n 层级也反了(导航是页面级,周期是筛选级)。 */\n.dshr-tabs {\n display: flex;\n flex: none; /* 固定:内容区滚,tab 条不动 */\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--rs-line);\n /* 右端跟卡片对齐(实测数字,别凭感觉调):\n root = 564;内容区因 padding-right 8px → 内容可视宽 556;\n 卡片再贴着可视区排 → 548。\n tab 条是 .dshr-settings 的直接子项,起点是 root(564),\n 要落到 548 就得让掉 8(padding) + 8(滚动条) = 16px。\n 写成 margin 而不用 padding,是为了不把分隔线画到 padding 区域外。 */\n margin-right: 16px;\n}\n\n.dshr-tab {\n position: relative;\n min-width: 0;\n height: 36px;\n padding: 0 12px;\n border: none;\n border-radius: 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n transition: color .12s ease;\n}\n\n.dshr-tab:hover { color: var(--rs-text); }\n\n/* 激活:主文字色 + 2px 下划线(bottom:-1px 压在容器的分隔线上)。\n 双类是因为上面有 `.dshr-settings button { color: inherit; font: inherit }` (1,1),\n 单类的 .dshr-tab-on 只有 (1,0),颜色和字号会被吃掉。 */\n.dshr-settings .dshr-tab-on {\n color: var(--rs-text);\n font-size: 13px;\n font-weight: 500;\n}\n\n.dshr-settings .dshr-tab-on::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: '';\n background: var(--rs-text);\n}\n\n.dshr-tabBack {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.dshr-tabBack:hover { color: var(--rs-text); background: var(--rs-hover); }\n\n/* 内容区:吃掉剩余高度并**自己滚**,这样 tab 条永远留在顶部。\n flex:1 + min-height:0 是必须的(见 .dshr-settings 的注释)。 */\n.dshr-settingsBody {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow-y: auto;\n /* 恒定预留滚动条槽位。\n 实测不设它时 clientWidth 在 564(内容短、无滚动条)和 556(内容长、有滚动条)\n 之间跳,卡片宽也跟着 556/548 跳 —— 切 tab 时内容会横向抖一下。\n 不支持 scrollbar-gutter 的浏览器只是退化成「照样占宽」,不会坏。 */\n scrollbar-gutter: stable;\n /* 右内边距:内容别贴到滚动条上。滚动条宽 8px 且占布局宽度\n (offsetWidth 564 vs clientWidth 556),不撑开的话内容右边缘正好压在它上面。 */\n padding-right: 8px;\n}\n\n/* 细滚动条:跟 .dshr-content / .dshr-modalBody 一个套路 */\n.dshr-settingsBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-settingsBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-settingsBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 老的中心栏劫持骨架(settings 不可用时的回退,非主路径)\n ============================================================ */\n\n[data-pane='conversation'] { position: relative; }\n.dshDesktopConversationSurface { position: relative; }\n\n[data-dsh-router-view] {\n position: absolute;\n z-index: 60;\n inset: 0;\n display: none;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-base));\n}\n\n.dshr-panelView { isolation: isolate; }\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-dsh-router-view] {\n display: block;\n}\n\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [data-pane='conversation'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) [class*='centerCol'] > :not([data-dsh-router-view]),\nhtml[data-dsh-router-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]):not([data-dsh-mnemon-active]) .dshDesktopConversationSurface > :not([data-dsh-router-view]) {\n display: none !important;\n}\n\n.dshr-shell {\n display: flex;\n height: 100%;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--rs-bg);\n}\n\n.dshr-settings *,\n.dshr-settings *::before,\n.dshr-settings *::after,\n.dshr-shell *,\n.dshr-shell *::before,\n.dshr-shell *::after { box-sizing: border-box; }\n\n/* 按钮/表单重置 + 聚焦环:两个挂载容器都要覆盖。\n 只写 .dshr-shell 的话设置页整套控件样式全掉(踩过:周期按钮变裸文字)。 */\n.dshr-settings button,\n.dshr-settings input,\n.dshr-settings select,\n.dshr-settings textarea,\n.dshr-shell button,\n.dshr-shell input,\n.dshr-shell select,\n.dshr-shell textarea { color: inherit; font: inherit; }\n\n.dshr-settings button:focus-visible,\n.dshr-settings input:focus-visible,\n.dshr-settings select:focus-visible,\n.dshr-settings textarea:focus-visible,\n.dshr-shell button:focus-visible,\n.dshr-shell input:focus-visible,\n.dshr-shell select:focus-visible,\n.dshr-shell textarea:focus-visible {\n outline: 2px solid var(--rs-accent);\n outline-offset: 2px;\n}\n\n/* Masthead */\n.dshr-masthead {\n display: flex;\n min-height: 56px;\n flex: 0 0 auto;\n align-items: center;\n gap: 16px;\n padding: 0 18px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-brand {\n display: grid;\n min-width: 0;\n flex: 0 1 auto;\n align-content: center;\n gap: 1px 9px;\n}\n\n.dshr-brandTitle {\n overflow: hidden;\n font-size: 15px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-brandSub {\n overflow: hidden;\n color: var(--rs-faint);\n font-size: 10px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-statusCluster {\n display: inline-flex;\n min-width: 0;\n flex: 0 0 auto;\n align-items: center;\n gap: 7px;\n margin-left: auto;\n color: var(--rs-muted);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.dshr-statusEndpoint {\n overflow: hidden;\n max-width: 220px;\n color: var(--rs-faint);\n font: 11px var(--rs-mono);\n text-overflow: ellipsis;\n}\n\n.dshr-headerActions {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n}\n\n.dshr-refreshedAt {\n color: var(--rs-faint);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-iconButton {\n display: inline-flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n cursor: pointer;\n}\n\n.dshr-iconButton:hover { color: var(--rs-text); background: var(--rs-hover); }\n.dshr-iconButton:disabled { opacity: 0.5; cursor: default; }\n\n.dshr-backButton {\n display: inline-flex;\n height: 28px;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-text);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-backButton:hover { background: var(--rs-hover); }\n.dshr-backButton svg { display: block; }\n\n/* Alert banner */\n.dshr-alert {\n display: flex;\n flex: 0 0 auto;\n align-items: baseline;\n gap: 10px;\n margin: 12px 18px 0;\n padding: 10px 12px;\n border: 1px solid var(--rs-line-strong);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-alert strong { color: var(--rs-danger); font-size: 12px; }\n.dshr-alert span { color: var(--rs-muted); font-size: 12px; }\n\n/* Nav tabs */\n.dshr-nav {\n display: flex;\n flex: 0 0 auto;\n gap: 2px;\n padding: 0 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-nav button {\n position: relative;\n min-height: 42px;\n padding: 0 12px;\n border: none;\n color: var(--rs-muted);\n background: transparent;\n font-size: 13px;\n cursor: pointer;\n}\n\n.dshr-nav button:hover { color: var(--rs-text); }\n\n.dshr-nav button[aria-current='page'] {\n color: var(--rs-text);\n font-weight: 600;\n}\n\n.dshr-nav button[aria-current='page']::after {\n position: absolute;\n right: 0;\n bottom: -1px;\n left: 0;\n height: 2px;\n border-radius: 2px 2px 0 0;\n content: '';\n background: var(--rs-accent);\n}\n\n.dshr-navCount {\n display: inline-block;\n margin-left: 5px;\n padding: 0 6px;\n border-radius: 9px;\n color: var(--rs-faint);\n background: var(--rs-layer-2);\n font-size: 11px;\n line-height: 16px;\n}\n\n/* Content */\n.dshr-content {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n overflow: auto;\n}\n\n.dshr-empty {\n display: flex;\n min-height: 120px;\n align-items: center;\n justify-content: center;\n color: var(--rs-faint);\n font-size: 13px;\n}\n\n/* Accounts table */\n.dshr-tableWrap {\n overflow: auto;\n padding: 12px 18px;\n}\n\n.dshr-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.dshr-table th {\n position: sticky;\n top: 0;\n padding: 8px 10px;\n border-bottom: 1px solid var(--rs-line-strong);\n color: var(--rs-faint);\n background: var(--rs-bg);\n font-size: 11px;\n font-weight: 600;\n text-align: left;\n white-space: nowrap;\n}\n\n.dshr-table td {\n padding: 7px 10px;\n border-bottom: 1px solid var(--rs-line);\n vertical-align: middle;\n}\n\n.dshr-table tbody tr:hover { background: var(--rs-hover); }\n\n.dshr-mono { font-family: var(--rs-mono); font-size: 11px; }\n.dshr-num { text-align: right; }\n.dshr-muted { color: var(--rs-faint); }\n.dshr-reason { color: var(--rs-muted); font-size: 11px; }\n\n.dshr-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 9px;\n font-size: 11px;\n line-height: 16px;\n}\n\n.dshr-badge-ok { color: var(--rs-success); background: color-mix(in srgb, var(--rs-success) 12%, transparent); }\n.dshr-badge-warn { color: var(--rs-warn); background: color-mix(in srgb, var(--rs-warn) 14%, transparent); }\n.dshr-badge-danger { color: var(--rs-danger); background: color-mix(in srgb, var(--rs-danger) 12%, transparent); }\n\n/* Models grid */\n.dshr-modelGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: 10px;\n padding: 14px 18px;\n}\n\n/* 设置页里横向 padding 归零:内容区已经贴着容器边,再加 18px 会把卡片挤成\n 1 列、图表也跟着变窄。上下 padding 保留。 */\n.dshr-settings .dshr-modelGrid,\n.dshr-settings .dshr-tableWrap {\n padding-right: 0;\n padding-left: 0;\n}\n\n.dshr-modelCard {\n display: flex;\n min-width: 0;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-1);\n}\n\n.dshr-modelCard:hover { border-color: var(--rs-line-strong); }\n\n.dshr-modelId {\n overflow: hidden;\n font-family: var(--rs-mono);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modelCtx {\n flex: none;\n color: var(--rs-faint);\n font-size: 10px;\n}\n\n/* ============================================================\n dsh-router — 9router 风格 tab 内容(供应商 / 组合 / 端点与密钥)\n ============================================================ */\n\n/* tab 内容:设置页里**不自己滚** —— 滚动交给 .dshr-settingsBody(tab 条下方\n 那一块)。这里再给 overflow 就会出现嵌套滚动条(内容区一个、tabBody 一个)。\n 老的 .dshr-shell 中心栏挂载方式没有 settingsBody 那层,滚动由它自己承担。 */\n.dshr-tabBody {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 14px;\n padding: 0;\n}\n\n.dshr-shell .dshr-tabBody {\n flex: 1 1 auto;\n min-height: 0;\n padding: 14px 18px;\n overflow-y: auto;\n}\n\n.dshr-card {\n flex: none; /* 不参与 flex 伸缩:高度=内容,避免被压缩裁切 */\n min-width: 0;\n border: 1px solid var(--rs-line);\n border-radius: 10px;\n background: var(--rs-layer-1);\n overflow: hidden;\n}\n\n.dshr-cardHead {\n display: flex;\n min-height: 44px;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-cardTitle {\n font-size: 13px;\n font-weight: 650;\n}\n\n.dshr-cardMeta {\n margin-left: auto;\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* 组合链 */\n.dshr-comboChain {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 14px;\n}\n\n.dshr-comboStep {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dshr-comboArrow {\n color: var(--rs-faint);\n margin-right: 8px;\n}\n\n.dshr-comboSupplier {\n font-family: var(--rs-mono);\n font-size: 12px;\n}\n\n/* 端点与密钥 */\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px;\n}\n\n.dshr-endpointCode {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-2);\n color: var(--rs-text);\n font-size: 12px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-miniButton {\n display: inline-flex;\n flex: none;\n height: 28px;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-1);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.dshr-miniButton:hover { color: var(--rs-text); background: var(--rs-hover); }\n\n.dshr-code {\n margin: 0;\n padding: 12px 14px;\n overflow-x: auto;\n color: var(--rs-text);\n background: var(--rs-layer-2);\n font: 12px/1.6 var(--rs-mono);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n/* ============================================================\n dsh-router — 供应商详情 / 端点与密钥 / 通用控件\n ============================================================ */\n\n/* 详情页头 */\n.dshr-detailHead {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 36px;\n}\n\n.dshr-detailTitle {\n font-size: 14px;\n font-weight: 650;\n}\n\n/* 可点击卡片 */\n.dshr-card-click {\n cursor: pointer;\n transition: border-color .12s ease;\n}\n\n.dshr-card-click:hover { border-color: var(--rs-accent); }\n\n.dshr-cardHint {\n padding: 0 14px 12px;\n}\n\n/* 供应商小卡片(grid 布局,替代整行大卡片) */\n.dshr-supplierGrid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));\n gap: 12px;\n}\n\n/* 供应商分组(内置 / 插件分开) */\n.dshr-supplierGroup {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-supplierGroup + .dshr-supplierGroup {\n margin-top: 22px;\n}\n\n.dshr-supplierGroupTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--rs-muted);\n letter-spacing: .04em;\n padding-left: 2px;\n}\n\n.dshr-supplierCard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n padding: 16px 18px;\n border: 1px solid var(--rs-line);\n border-radius: 12px;\n background: var(--rs-layer-1);\n cursor: pointer;\n transition: border-color .12s ease;\n}\n\n.dshr-supplierCard:hover { border-color: var(--rs-accent); }\n\n.dshr-supplierName {\n font-size: 14px;\n font-weight: 650;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dshr-supplierRow {\n display: flex;\n align-items: center;\n gap: 9px;\n min-width: 0;\n}\n\n.dshr-supplierIcon {\n width: 24px;\n height: 24px;\n flex: none;\n border-radius: 5px;\n object-fit: contain;\n color: var(--rs-faint);\n}\n\n.dshr-supplierMeta {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n white-space: nowrap;\n}\n\n.dshr-chevron {\n color: var(--rs-faint);\n font-size: 18px;\n line-height: 1;\n margin-left: auto;\n}\n\n/* 登录框 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n background: var(--rs-layer-2);\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 8px 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 输入框 */\n.dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--rs-line);\n border-radius: 6px;\n background: var(--rs-layer-1);\n color: var(--rs-text);\n font-size: 12px;\n}\n\n.dshr-input:focus {\n outline: none;\n border-color: var(--rs-accent);\n}\n\n/* 成功提示 */\n.dshr-alert-ok {\n border-color: color-mix(in srgb, var(--rs-ok) 45%, transparent);\n background: color-mix(in srgb, var(--rs-ok) 10%, transparent);\n}\n\n.dshr-createdKey {\n display: block;\n margin: 6px 0;\n padding: 8px 10px;\n border-radius: 6px;\n background: var(--rs-layer-1);\n word-break: break-all;\n}\n\n/* 模型卡片行 */\n.dshr-modelRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dshr-modelCard-off {\n opacity: .55;\n}\n\n/* 开关 */\n.dshr-toggle {\n position: relative;\n flex: none;\n width: 30px;\n height: 16px;\n border: none;\n border-radius: 8px;\n background: var(--rs-line);\n cursor: pointer;\n padding: 0;\n transition: background .15s ease;\n}\n\n.dshr-toggle-on { background: var(--rs-ok); }\n\n.dshr-toggleKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n transition: transform .15s ease;\n}\n\n.dshr-toggle-on .dshr-toggleKnob { transform: translateX(14px); }\n\n/* 操作按钮组 */\n.dshr-ops {\n display: flex;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dshr-danger { color: var(--rs-danger); }\n.dshr-danger:hover { color: var(--rs-danger); border-color: var(--rs-danger); }\n\n/* ============================================================\n dsh-router — 弹窗 + 按钮 + 整体打磨\n ============================================================ */\n\n/* 弹窗 */\n.dshr-modalOverlay {\n position: fixed;\n z-index: 1000;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: color-mix(in srgb, #000 42%, transparent);\n backdrop-filter: blur(2px);\n animation: dshrFadeIn .12s ease;\n}\n\n.dshr-modal {\n width: min(560px, 100%);\n max-height: min(80vh, 640px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--rs-line-strong);\n border-radius: 12px;\n background: var(--rs-layer-1);\n box-shadow: 0 20px 50px rgb(0 0 0 / .35), 0 0 0 1px rgb(0 0 0 / .05);\n animation: dshrModalIn .16s ease;\n}\n\n.dshr-modalHead {\n display: flex;\n min-height: 48px;\n flex: 0 0 auto;\n align-items: center;\n gap: 10px;\n padding: 0 16px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-modalTitle {\n min-width: 0;\n flex: 1 1 auto;\n overflow: hidden;\n font-size: 14px;\n font-weight: 650;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-modalClose {\n display: inline-flex;\n flex: none;\n width: 26px;\n height: 26px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n}\n\n.dshr-modalClose:hover { color: var(--rs-text); background: var(--rs-hover); }\n\n.dshr-modalBody {\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.dshr-modalActions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n\n@keyframes dshrFadeIn { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dshrModalIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }\n\n/* 按钮 */\n.dshr-settings .dshr-primaryButton,\n.dshr-shell .dshr-primaryButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-accent);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter .12s ease;\n}\n\n.dshr-settings .dshr-primaryButton:hover,\n.dshr-shell .dshr-primaryButton:hover { filter: brightness(1.08); }\n.dshr-settings .dshr-primaryButton:disabled,\n.dshr-shell .dshr-primaryButton:disabled { opacity: .55; cursor: default; }\n\n.dshr-settings .dshr-dangerButton,\n.dshr-shell .dshr-dangerButton {\n display: inline-flex;\n height: 30px;\n align-items: center;\n justify-content: center;\n padding: 0 14px;\n border: none;\n border-radius: 7px;\n color: #fff;\n background: var(--rs-danger);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: filter .12s ease;\n}\n\n.dshr-settings .dshr-dangerButton:hover,\n.dshr-shell .dshr-dangerButton:hover { filter: brightness(1.1); }\n\n/* 登录步骤 */\n.dshr-loginBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-loginStep {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n color: var(--rs-muted);\n font-size: 12px;\n line-height: 1.6;\n}\n\n.dshr-loginNum {\n display: inline-flex;\n flex: none;\n width: 18px;\n height: 18px;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 14%, transparent);\n font-size: 11px;\n font-weight: 700;\n}\n\n.dshr-loginUrl {\n display: block;\n overflow: hidden;\n padding: 9px 11px;\n border: 1px solid var(--rs-line);\n border-radius: 8px;\n background: var(--rs-layer-2);\n color: var(--rs-accent);\n font: 11px/1.5 var(--rs-mono);\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n}\n\n/* 新 Key 弹窗 */\n.dshr-createdKeyBox {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-createdKey {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--rs-layer-2);\n word-break: break-all;\n user-select: all;\n}\n\n/* ============================================================\n dsh-router — 整体打磨\n ============================================================ */\n\n/* Masthead: 细腻渐变 + 底部微阴影 */\n.dshr-masthead {\n background: linear-gradient(to bottom, color-mix(in srgb, var(--rs-layer-1) 62%, transparent), transparent);\n}\n\n/* Brand 标题更突出 */\n.dshr-brandTitle {\n letter-spacing: .01em;\n}\n\n/* 卡片:阴影 + hover 微抬升 */\n.dshr-card {\n box-shadow: 0 1px 2px rgb(0 0 0 / .04);\n transition: box-shadow .15s ease, transform .15s ease, border-color .15s ease;\n}\n\n.dshr-card-click:hover {\n transform: translateY(-1px);\n border-color: color-mix(in srgb, var(--rs-accent) 55%, var(--rs-line));\n box-shadow: 0 6px 18px rgb(0 0 0 / .10);\n}\n\n/* Tab:悬停背景 + 计数徽标配色 */\n.dshr-nav button {\n transition: color .12s ease, background .12s ease;\n}\n\n.dshr-nav button:hover {\n background: var(--rs-hover);\n border-radius: 6px 6px 0 0;\n}\n\n.dshr-navCount {\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n color: var(--rs-accent);\n font-weight: 600;\n}\n\n/* 徽标:更柔和 */\n.dshr-badge {\n box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 12%, transparent);\n}\n\n/* 表格行悬停 + 表头层级 */\n.dshr-table tbody tr { transition: background .1s ease; }\n.dshr-table th { letter-spacing: .02em; }\n\n/* 输入框聚焦 */\n.dshr-input:focus {\n border-color: var(--rs-accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent);\n}\n\n/* 开关微过渡 */\n.dshr-toggle { transition: background .15s ease, box-shadow .15s ease; }\n.dshr-toggle:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--rs-accent) 18%, transparent); }\n\n/* 内容区细滚动条 */\n.dshr-content::-webkit-scrollbar,\n.dshr-tableWrap::-webkit-scrollbar,\n.dshr-modalBody::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.dshr-content::-webkit-scrollbar-thumb,\n.dshr-tableWrap::-webkit-scrollbar-thumb,\n.dshr-modalBody::-webkit-scrollbar-thumb {\n border-radius: 4px;\n background: var(--rs-line-strong);\n}\n\n.dshr-content::-webkit-scrollbar-track,\n.dshr-tableWrap::-webkit-scrollbar-track,\n.dshr-modalBody::-webkit-scrollbar-track {\n background: transparent;\n}\n\n/* ============================================================\n dsh-router — 端点与密钥(9router 风格布局)\n ============================================================ */\n\n/* 卡片头图标 */\n.dshr-cardIcon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 6px;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 12%, transparent);\n}\n\n.dshr-cardAction {\n margin-left: auto;\n}\n\n/* Endpoint 列表 */\n.dshr-endpointList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 12px 14px;\n}\n\n.dshr-endpointRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-endpointLabel {\n flex: none;\n min-width: 88px;\n padding: 3px 8px;\n border-radius: 6px;\n color: var(--rs-muted);\n background: var(--rs-layer-2);\n font: 11px var(--rs-mono);\n text-align: center;\n}\n\n.dshr-endpointRow .dshr-input {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--rs-muted);\n}\n\n.dshr-endpointNote {\n padding: 2px 2px 0;\n}\n\n/* 图标按钮 */\n.dshr-iconBtn {\n display: inline-flex;\n flex: none;\n width: 30px;\n height: 30px;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n color: var(--rs-muted);\n background: transparent;\n cursor: pointer;\n transition: color .12s ease, background .12s ease;\n}\n\n.dshr-iconBtn:hover { color: var(--rs-accent); background: var(--rs-hover); }\n\n.dshr-iconBtn svg { display: block; }\n\n.dshr-iconBtn-sm { width: 26px; height: 26px; }\n\n/* 禁用态必须看得出来:纯图标按钮没有文字可读(不像「刷新中…」),\n 只靠 hover 反馈会让人以为还能点。 */\n.dshr-iconBtn:disabled { opacity: .4; cursor: default; }\n\n.dshr-iconBtn:disabled:hover { color: var(--rs-muted); background: transparent; }\n\n/* Require API key 行 */\n.dshr-requireRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n}\n\n.dshr-requireTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-requireDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 空状态 */\n.dshr-keyEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 40px 16px 44px;\n}\n\n.dshr-keyEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n margin-bottom: 6px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-keyEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-keyEmptyDesc {\n margin: 0 0 8px;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* key 列表(flex 行,非表格) */\n.dshr-keyList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-keyRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: opacity .12s ease;\n}\n\n.dshr-keyRow:last-child { border-bottom: none; }\n.dshr-keyRow-paused { opacity: .6; }\n\n.dshr-keyInfo {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-keyNameRow {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.dshr-keyName {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshr-keyValue {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-top: 2px;\n}\n\n.dshr-keyValue code {\n overflow: hidden;\n color: var(--rs-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-keyCreated {\n margin: 3px 0 0;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-keyPaused { color: var(--rs-warn); }\n\n.dshr-keyOps {\n display: flex;\n flex: none;\n align-items: center;\n gap: 4px;\n}\n\n/* 删除按钮 hover 才明显(9router:group-hover 显示) */\n.dshr-keyDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-keyRow:hover .dshr-keyDelete { opacity: 1; }\n.dshr-keyDelete:focus-visible { opacity: 1; }\n.dshr-keyDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 弹窗表单 */\n.dshr-modalForm {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.dshr-fieldLabel {\n font-size: 12px;\n font-weight: 650;\n color: var(--rs-text);\n margin-top: 4px;\n}\n\n.dshr-warnBox {\n padding: 12px 14px;\n border: 1px solid color-mix(in srgb, var(--rs-warn) 40%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--rs-warn) 8%, transparent);\n}\n\n.dshr-warnTitle {\n margin: 0 0 4px;\n color: var(--rs-warn);\n font-size: 13px;\n font-weight: 700;\n}\n\n.dshr-warnDesc {\n margin: 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n.dshr-createdRow { padding: 0; }\n\n.dshr-createdRow .dshr-input { color: var(--rs-text); }\n\n.dshr-fullButton { width: 100%; }\n\n/* ============================================================\n dsh-router — 供应商详情(9router 风格)\n ============================================================ */\n\n/* Header */\n.dshr-providerHead {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-backLink {\n display: inline-flex;\n align-items: center;\n align-self: flex-start;\n gap: 4px;\n border: none;\n padding: 2px 0;\n color: var(--rs-muted);\n background: transparent;\n font-size: 12px;\n cursor: pointer;\n transition: color .12s ease;\n}\n\n.dshr-backLink:hover { color: var(--rs-accent); }\n\n.dshr-providerTitleRow {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dshr-providerIcon {\n display: inline-flex;\n flex: none;\n width: 44px;\n height: 44px;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n}\n\n.dshr-providerImg {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.dshr-providerName {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n letter-spacing: -.01em;\n}\n\n.dshr-providerCount {\n margin: 2px 0 0;\n color: var(--rs-muted);\n font-size: 12px;\n}\n\n/* 链接池空状态 */\n.dshr-linkEmpty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n padding: 36px 16px 40px;\n}\n\n.dshr-linkEmptyIcon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n color: var(--rs-accent);\n background: color-mix(in srgb, var(--rs-accent) 10%, transparent);\n}\n\n.dshr-linkEmptyText { text-align: center; }\n\n.dshr-linkEmptyTitle {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n}\n\n.dshr-linkEmptyDesc {\n margin: 2px 0 0;\n color: var(--rs-faint);\n font-size: 12px;\n}\n\n/* 链接列表(9router ConnectionRow 风格行) */\n.dshr-linkList {\n display: flex;\n flex-direction: column;\n}\n\n.dshr-linkRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--rs-line);\n transition: background .1s ease;\n}\n\n.dshr-linkRow:last-child { border-bottom: none; }\n.dshr-linkRow:hover { background: var(--rs-hover); }\n.dshr-linkRow[draggable='true'] { cursor: grab; }\n.dshr-linkRow[draggable='true']:active { cursor: grabbing; }\n.dshr-linkRowDragging { opacity: .45; background: var(--rs-hover); }\n\n.dshr-linkGrip {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--rs-muted);\n flex: 0 0 auto;\n cursor: grab;\n padding: 4px 2px;\n user-select: none;\n}\n.dshr-linkGrip:active { cursor: grabbing; }\n.dshr-linkGrip svg { opacity: .65; }\n.dshr-linkRow:hover .dshr-linkGrip svg { opacity: 1; }\n\n.dshr-linkMain {\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.dshr-linkTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dshr-linkName {\n overflow: hidden;\n font-size: 13px;\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-linkSub {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 3px;\n color: var(--rs-faint);\n font-size: 11px;\n}\n\n.dshr-linkUid { color: var(--rs-faint); }\n.dshr-linkCredits { color: var(--rs-muted); }\n.dshr-linkErr { color: var(--rs-warn); }\n.dshr-linkCooling { color: var(--rs-warn); }\n.dshr-linkDot { color: var(--rs-faint); opacity: .6; }\n\n.dshr-linkOps {\n display: flex;\n flex: none;\n align-items: center;\n}\n\n/* 删除链接 hover 显示 + 红色 */\n.dshr-linkDelete {\n color: var(--rs-muted);\n opacity: 0;\n transition: opacity .12s ease, color .12s ease, background .12s ease;\n}\n\n.dshr-linkRow:hover .dshr-linkDelete { opacity: 1; }\n.dshr-linkDelete:focus-visible { opacity: 1; }\n.dshr-linkDelete:hover {\n color: var(--rs-danger);\n background: color-mix(in srgb, var(--rs-danger) 14%, transparent);\n}\n\n/* 模型 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*/"));
7
7
  document.head.appendChild(elementStyle);
8
8
  }
9
9
  } catch (e) {
@@ -24,7 +24,7 @@ window.__ModuleLoader__.load({
24
24
  /** Active-state attribute on <html> that shows the panel. */
25
25
  var ROUTER_ACTIVE_ATTR = "data-dsh-router-active";
26
26
  /** The panel's visible title (also the sidebar entry label). */
27
- var ROUTER_TITLE = "路由系统";
27
+ var ROUTER_TITLE = "路由";
28
28
  /** Same-origin API base the client panel fetches (host half serves it). */
29
29
  var ROUTER_API_BASE = "/router/api";
30
30
  //#endregion
@@ -88,7 +88,7 @@ window.__ModuleLoader__.load({
88
88
  * + 可用模型卡片(flex-wrap chips,禁用模型单独列出可恢复)。
89
89
  * 加链接流程由供应商能力驱动(登录链接 / API key / 轮询登录)。
90
90
  */
91
- function Icon$3({ d, size = 18 }) {
91
+ function Icon$4({ d, size = 18 }) {
92
92
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
93
93
  viewBox: "0 0 24 24",
94
94
  width: size,
@@ -102,7 +102,7 @@ window.__ModuleLoader__.load({
102
102
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
103
103
  });
104
104
  }
105
- var I$3 = {
105
+ var I$4 = {
106
106
  back: "M19 12H5M12 19l-7-7 7-7",
107
107
  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",
108
108
  add: "M12 5v14M5 12h14",
@@ -595,8 +595,8 @@ window.__ModuleLoader__.load({
595
595
  type: "button",
596
596
  className: "dshr-backLink",
597
597
  onClick: onBack,
598
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
599
- d: I$3.back,
598
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
599
+ d: I$4.back,
600
600
  size: 16
601
601
  }), "返回"]
602
602
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -607,9 +607,12 @@ window.__ModuleLoader__.load({
607
607
  children: supplier.icon !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
608
608
  className: "dshr-providerImg",
609
609
  src: supplier.icon,
610
- alt: ""
611
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
612
- d: I$3.link,
610
+ alt: "",
611
+ onError: (e) => {
612
+ e.currentTarget.style.display = "none";
613
+ }
614
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
615
+ d: I$4.link,
613
616
  size: 22
614
617
  })
615
618
  }),
@@ -637,8 +640,8 @@ window.__ModuleLoader__.load({
637
640
  setAliasError("");
638
641
  setShowAlias(true);
639
642
  },
640
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
641
- d: I$3.edit,
643
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
644
+ d: I$4.edit,
642
645
  size: 15
643
646
  })
644
647
  })
@@ -668,8 +671,8 @@ window.__ModuleLoader__.load({
668
671
  "aria-label": "刷新积分并探测健康",
669
672
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
670
673
  className: refreshing ? "dshr-spin" : void 0,
671
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
672
- d: I$3.refresh,
674
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
675
+ d: I$4.refresh,
673
676
  size: 15
674
677
  })
675
678
  })
@@ -683,8 +686,8 @@ window.__ModuleLoader__.load({
683
686
  "aria-label": "签到所有链接",
684
687
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
685
688
  className: checkingIn ? "dshr-spin" : void 0,
686
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
687
- d: I$3.checkCircle,
689
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
690
+ d: I$4.checkCircle,
688
691
  size: 15
689
692
  })
690
693
  })
@@ -694,8 +697,8 @@ window.__ModuleLoader__.load({
694
697
  className: "dshr-primaryButton",
695
698
  onClick: () => void startLogin(),
696
699
  disabled: busy,
697
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
698
- d: I$3.add,
700
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
701
+ d: I$4.add,
699
702
  size: 14
700
703
  }), "添加链接"]
701
704
  })
@@ -709,8 +712,8 @@ window.__ModuleLoader__.load({
709
712
  children: [
710
713
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
711
714
  className: "dshr-linkEmptyIcon",
712
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
713
- d: I$3.link,
715
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
716
+ d: I$4.link,
714
717
  size: 28
715
718
  })
716
719
  }),
@@ -729,8 +732,8 @@ window.__ModuleLoader__.load({
729
732
  className: "dshr-primaryButton",
730
733
  onClick: () => void startLogin(),
731
734
  disabled: busy,
732
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
733
- d: I$3.add,
735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
736
+ d: I$4.add,
734
737
  size: 14
735
738
  }), "添加链接"]
736
739
  })
@@ -855,8 +858,8 @@ window.__ModuleLoader__.load({
855
858
  className: "dshr-iconBtn dshr-iconBtn-sm dshr-linkDelete",
856
859
  title: "删除链接",
857
860
  onClick: () => setRemoveTarget(account),
858
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
859
- d: I$3.delete,
861
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
862
+ d: I$4.delete,
860
863
  size: 15
861
864
  })
862
865
  })
@@ -900,8 +903,8 @@ window.__ModuleLoader__.load({
900
903
  setShowAddModel(true);
901
904
  },
902
905
  disabled: toggling,
903
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
904
- d: I$3.add,
906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
907
+ d: I$4.add,
905
908
  size: 13
906
909
  }), "添加模型"]
907
910
  }),
@@ -927,8 +930,8 @@ window.__ModuleLoader__.load({
927
930
  className: "dshr-modelSearch",
928
931
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
929
932
  className: "dshr-modelSearchIcon",
930
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
931
- d: I$3.search,
933
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
934
+ d: I$4.search,
932
935
  size: 13
933
936
  })
934
937
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -959,8 +962,8 @@ window.__ModuleLoader__.load({
959
962
  onClick: () => void toggleModel(model.id, false),
960
963
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
961
964
  className: "dshr-modelChipStatus",
962
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
963
- d: status === "ok" ? I$3.checkCircle : status === "error" ? I$3.cancel : I$3.robot,
965
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
966
+ d: status === "ok" ? I$4.checkCircle : status === "error" ? I$4.cancel : I$4.robot,
964
967
  size: 13
965
968
  })
966
969
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -977,8 +980,8 @@ window.__ModuleLoader__.load({
977
980
  onClick: () => void testModel(model.id),
978
981
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
979
982
  className: testingIds.has(model.id) ? "dshr-spin" : void 0,
980
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
981
- d: testingIds.has(model.id) ? I$3.loading : I$3.test,
983
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
984
+ d: testingIds.has(model.id) ? I$4.loading : I$4.test,
982
985
  size: 13
983
986
  })
984
987
  })
@@ -988,8 +991,8 @@ window.__ModuleLoader__.load({
988
991
  className: "dshr-iconBtn dshr-iconBtn-sm",
989
992
  title: "复制模型名",
990
993
  onClick: () => void copyModel(model.id),
991
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
992
- d: copiedModel === model.id ? I$3.check : I$3.copy,
994
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
995
+ d: copiedModel === model.id ? I$4.check : I$4.copy,
993
996
  size: 13
994
997
  })
995
998
  }),
@@ -998,8 +1001,8 @@ window.__ModuleLoader__.load({
998
1001
  className: "dshr-iconBtn dshr-iconBtn-sm dshr-modelDelete",
999
1002
  title: "删除自定义模型",
1000
1003
  onClick: () => setRemoveModelTarget(model.id),
1001
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1002
- d: I$3.delete,
1004
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$4, {
1005
+ d: I$4.delete,
1003
1006
  size: 13
1004
1007
  })
1005
1008
  })
@@ -1365,7 +1368,7 @@ window.__ModuleLoader__.load({
1365
1368
  * API Endpoint 卡片 + API Keys 卡片(Require API key + flex 行 key 列表 +
1366
1369
  * Create/暂停确认/Created 弹窗)。
1367
1370
  */
1368
- function Icon$2({ d, size = 18 }) {
1371
+ function Icon$3({ d, size = 18 }) {
1369
1372
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1370
1373
  viewBox: "0 0 24 24",
1371
1374
  width: size,
@@ -1379,7 +1382,7 @@ window.__ModuleLoader__.load({
1379
1382
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
1380
1383
  });
1381
1384
  }
1382
- var I$2 = {
1385
+ var I$3 = {
1383
1386
  api: "M4 4h16v16H4zM9 9h6v6H9zM12 9V7M12 17v-2M9 12H7M17 12h-2",
1384
1387
  key: "M14 7a4 4 0 1 1-1.4 7.6L9 18H6v-3l4.4-4.4A4 4 0 0 1 14 7zM18 7h.01",
1385
1388
  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",
@@ -1528,7 +1531,7 @@ window.__ModuleLoader__.load({
1528
1531
  className: "dshr-cardHead",
1529
1532
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1530
1533
  className: "dshr-cardIcon",
1531
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, { d: I$2.api })
1534
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, { d: I$3.api })
1532
1535
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1533
1536
  className: "dshr-cardTitle",
1534
1537
  children: "API Endpoint"
@@ -1552,8 +1555,8 @@ window.__ModuleLoader__.load({
1552
1555
  className: "dshr-iconBtn",
1553
1556
  title: "复制",
1554
1557
  onClick: () => void copyText(endpointBase, "__endpoint"),
1555
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1556
- d: copiedKeyId === "__endpoint" ? I$2.check : I$2.copy,
1558
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1559
+ d: copiedKeyId === "__endpoint" ? I$3.check : I$3.copy,
1557
1560
  size: 16
1558
1561
  })
1559
1562
  })
@@ -1573,7 +1576,7 @@ window.__ModuleLoader__.load({
1573
1576
  children: [
1574
1577
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1575
1578
  className: "dshr-cardIcon",
1576
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, { d: I$2.key })
1579
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, { d: I$3.key })
1577
1580
  }),
1578
1581
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1579
1582
  className: "dshr-cardTitle",
@@ -1583,8 +1586,8 @@ window.__ModuleLoader__.load({
1583
1586
  type: "button",
1584
1587
  className: "dshr-primaryButton dshr-cardAction",
1585
1588
  onClick: openCreate,
1586
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1587
- d: I$2.add,
1589
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1590
+ d: I$3.add,
1588
1591
  size: 14
1589
1592
  }), "创建 Key"]
1590
1593
  })
@@ -1619,8 +1622,8 @@ window.__ModuleLoader__.load({
1619
1622
  children: [
1620
1623
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1621
1624
  className: "dshr-keyEmptyIcon",
1622
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1623
- d: I$2.key,
1625
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1626
+ d: I$3.key,
1624
1627
  size: 30
1625
1628
  })
1626
1629
  }),
@@ -1636,8 +1639,8 @@ window.__ModuleLoader__.load({
1636
1639
  type: "button",
1637
1640
  className: "dshr-primaryButton",
1638
1641
  onClick: openCreate,
1639
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1640
- d: I$2.add,
1642
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1643
+ d: I$3.add,
1641
1644
  size: 14
1642
1645
  }), "创建 Key"]
1643
1646
  })
@@ -1659,8 +1662,8 @@ window.__ModuleLoader__.load({
1659
1662
  className: "dshr-iconBtn dshr-iconBtn-sm dshr-keyDelete",
1660
1663
  title: "删除",
1661
1664
  onClick: () => setDeleteTarget(key),
1662
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1663
- d: I$2.delete,
1665
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1666
+ d: I$3.delete,
1664
1667
  size: 14
1665
1668
  })
1666
1669
  })]
@@ -1677,8 +1680,8 @@ window.__ModuleLoader__.load({
1677
1680
  className: "dshr-iconBtn dshr-iconBtn-sm",
1678
1681
  title: visibleKeys.has(key.id) ? "隐藏" : "显示",
1679
1682
  onClick: () => toggleVisible(key.id),
1680
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1681
- d: visibleKeys.has(key.id) ? I$2.visibilityOff : I$2.visibility,
1683
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1684
+ d: visibleKeys.has(key.id) ? I$3.visibilityOff : I$3.visibility,
1682
1685
  size: 14
1683
1686
  })
1684
1687
  }),
@@ -1687,8 +1690,8 @@ window.__ModuleLoader__.load({
1687
1690
  className: "dshr-iconBtn dshr-iconBtn-sm",
1688
1691
  title: "复制",
1689
1692
  onClick: () => void copyText(key.key, key.id),
1690
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
1691
- d: copiedKeyId === key.id ? I$2.check : I$2.copy,
1693
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$3, {
1694
+ d: copiedKeyId === key.id ? I$3.check : I$3.copy,
1692
1695
  size: 14
1693
1696
  })
1694
1697
  })
@@ -1865,7 +1868,7 @@ window.__ModuleLoader__.load({
1865
1868
  * 组合 = 命名的一组模型(完整名 alias/id),请求 model 传组合名时,
1866
1869
  * 按策略(回退/轮询)命中其中一个。可创建/编辑/删除。
1867
1870
  */
1868
- function Icon$1({ d, size = 18 }) {
1871
+ function Icon$2({ d, size = 18 }) {
1869
1872
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1870
1873
  viewBox: "0 0 24 24",
1871
1874
  width: size,
@@ -1879,7 +1882,7 @@ window.__ModuleLoader__.load({
1879
1882
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
1880
1883
  });
1881
1884
  }
1882
- var I$1 = {
1885
+ var I$2 = {
1883
1886
  layers: "M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5M3 17l9 5 9-5",
1884
1887
  add: "M12 5v14M5 12h14",
1885
1888
  copy: "M9 9h11v11H9zM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1",
@@ -2101,8 +2104,8 @@ window.__ModuleLoader__.load({
2101
2104
  children: [
2102
2105
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2103
2106
  className: "dshr-cardIcon",
2104
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2105
- d: I$1.layers,
2107
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2108
+ d: I$2.layers,
2106
2109
  size: 16
2107
2110
  })
2108
2111
  }),
@@ -2118,8 +2121,8 @@ window.__ModuleLoader__.load({
2118
2121
  type: "button",
2119
2122
  className: "dshr-primaryButton dshr-cardAction",
2120
2123
  onClick: openCreate,
2121
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2122
- d: I$1.add,
2124
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2125
+ d: I$2.add,
2123
2126
  size: 14
2124
2127
  }), "创建组合"]
2125
2128
  })
@@ -2145,8 +2148,8 @@ window.__ModuleLoader__.load({
2145
2148
  children: [
2146
2149
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2147
2150
  className: "dshr-keyEmptyIcon",
2148
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2149
- d: I$1.layers,
2151
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2152
+ d: I$2.layers,
2150
2153
  size: 30
2151
2154
  })
2152
2155
  }),
@@ -2162,8 +2165,8 @@ window.__ModuleLoader__.load({
2162
2165
  type: "button",
2163
2166
  className: "dshr-primaryButton",
2164
2167
  onClick: openCreate,
2165
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2166
- d: I$1.add,
2168
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2169
+ d: I$2.add,
2167
2170
  size: 14
2168
2171
  }), "创建组合"]
2169
2172
  })
@@ -2177,8 +2180,8 @@ window.__ModuleLoader__.load({
2177
2180
  children: [
2178
2181
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2179
2182
  className: "dshr-cardIcon",
2180
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2181
- d: I$1.layers,
2183
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2184
+ d: I$2.layers,
2182
2185
  size: 16
2183
2186
  })
2184
2187
  }),
@@ -2211,8 +2214,8 @@ window.__ModuleLoader__.load({
2211
2214
  className: "dshr-iconBtn dshr-iconBtn-sm",
2212
2215
  title: "复制组合名",
2213
2216
  onClick: () => void copyName(combo.name, combo.id),
2214
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2215
- d: copied === combo.id ? I$1.check : I$1.copy,
2217
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2218
+ d: copied === combo.id ? I$2.check : I$2.copy,
2216
2219
  size: 15
2217
2220
  })
2218
2221
  }),
@@ -2221,8 +2224,8 @@ window.__ModuleLoader__.load({
2221
2224
  className: "dshr-iconBtn dshr-iconBtn-sm",
2222
2225
  title: "编辑",
2223
2226
  onClick: () => openEdit(combo),
2224
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2225
- d: I$1.edit,
2227
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2228
+ d: I$2.edit,
2226
2229
  size: 15
2227
2230
  })
2228
2231
  }),
@@ -2231,8 +2234,8 @@ window.__ModuleLoader__.load({
2231
2234
  className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboOpBtn-danger",
2232
2235
  title: "删除",
2233
2236
  onClick: () => setDeleteTarget(combo),
2234
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2235
- d: I$1.delete,
2237
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2238
+ d: I$2.delete,
2236
2239
  size: 15
2237
2240
  })
2238
2241
  })
@@ -2328,8 +2331,8 @@ window.__ModuleLoader__.load({
2328
2331
  ]
2329
2332
  }), form.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2330
2333
  className: "dshr-comboEmptyStepsBox",
2331
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2332
- d: I$1.layers,
2334
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2335
+ d: I$2.layers,
2333
2336
  size: 20
2334
2337
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2335
2338
  className: "dshr-muted",
@@ -2351,8 +2354,8 @@ window.__ModuleLoader__.load({
2351
2354
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2352
2355
  className: "dshr-comboGrip",
2353
2356
  title: "拖拽排序",
2354
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2355
- d: I$1.grip,
2357
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2358
+ d: I$2.grip,
2356
2359
  size: 14
2357
2360
  })
2358
2361
  }),
@@ -2369,8 +2372,8 @@ window.__ModuleLoader__.load({
2369
2372
  className: "dshr-iconBtn dshr-iconBtn-sm dshr-comboStepRemove",
2370
2373
  title: "移除",
2371
2374
  onClick: () => removeModel(index),
2372
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2373
- d: I$1.delete,
2375
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2376
+ d: I$2.delete,
2374
2377
  size: 14
2375
2378
  })
2376
2379
  })
@@ -2390,8 +2393,8 @@ window.__ModuleLoader__.load({
2390
2393
  className: "dshr-modelSearch dshr-comboModelSearch",
2391
2394
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2392
2395
  className: "dshr-modelSearchIcon",
2393
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2394
- d: I$1.search,
2396
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$2, {
2397
+ d: I$2.search,
2395
2398
  size: 13
2396
2399
  })
2397
2400
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -2495,6 +2498,168 @@ window.__ModuleLoader__.load({
2495
2498
  });
2496
2499
  }
2497
2500
  //#endregion
2501
+ //#region src/client/CheckinCard.tsx
2502
+ /**
2503
+ * 概览的「签到」汇总卡:一键签到所有支持签到的供应商 + 显示今天点过没。
2504
+ *
2505
+ * **「今日已签到」的口径(天花板,刻意不撒谎)**:这里只知道「今天在这个浏览器
2506
+ * 点过这个按钮没」,存在 localStorage,**不知道上游是不是真的签上了**。真凭据是
2507
+ * 上游的 `checked_in`,但那得逐个链接查(traywork 侧实测:积分不可信、
2508
+ * 只有 checked_in 算数),而当前契约里没有「查签到状态」这个能力。
2509
+ * 所以卡片文案是「今日已点」而不是「已签到」,悬停说明里写清这一点。
2510
+ *
2511
+ * 要真状态的升级路径:给 supplier 契约加 `checkinStatus?()`,核心加一条
2512
+ * `/suppliers/:id/checkin/status` 汇总路由,这里改成读它即可(卡片不用改)。
2513
+ */
2514
+ function Icon$1({ d, size = 14 }) {
2515
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2516
+ viewBox: "0 0 24 24",
2517
+ width: size,
2518
+ height: size,
2519
+ fill: "none",
2520
+ stroke: "currentColor",
2521
+ strokeWidth: "2",
2522
+ strokeLinecap: "round",
2523
+ strokeLinejoin: "round",
2524
+ "aria-hidden": "true",
2525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
2526
+ });
2527
+ }
2528
+ var I$1 = {
2529
+ check: "M4 12.5l5 5L20 6.5",
2530
+ loading: "M12 3a9 9 0 1 0 9 9"
2531
+ };
2532
+ /** localStorage key:值 = 本地日期 YYYY-MM-DD。 */
2533
+ var CHECKIN_STAMP_KEY = "dsh-router:checkin-stamp";
2534
+ /** 本地日切(跟用量统计的 localDateKey 同口径,别用 UTC)。 */
2535
+ function todayKey(ts = Date.now()) {
2536
+ const d = new Date(ts);
2537
+ const p = (n) => String(n).padStart(2, "0");
2538
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
2539
+ }
2540
+ function readStamp() {
2541
+ try {
2542
+ return localStorage.getItem(CHECKIN_STAMP_KEY);
2543
+ } catch {
2544
+ return null;
2545
+ }
2546
+ }
2547
+ function writeStamp() {
2548
+ try {
2549
+ localStorage.setItem(CHECKIN_STAMP_KEY, todayKey());
2550
+ } catch {}
2551
+ }
2552
+ /** 可签到的供应商(能力 checkinNow)。 */
2553
+ function checkinable(health) {
2554
+ return (health?.suppliers ?? []).filter((s) => (s.capabilities ?? []).includes("checkinNow")).map((s) => ({
2555
+ id: s.id,
2556
+ name: s.name
2557
+ }));
2558
+ }
2559
+ function CheckinCard({ health, onRefresh }) {
2560
+ const [busy, setBusy] = (0, react.useState)(false);
2561
+ const [stamp, setStamp] = (0, react.useState)(null);
2562
+ const [result, setResult] = (0, react.useState)("");
2563
+ const [error, setError] = (0, react.useState)("");
2564
+ (0, react.useEffect)(() => {
2565
+ setStamp(readStamp());
2566
+ }, []);
2567
+ const targets = checkinable(health);
2568
+ const clickedToday = stamp === todayKey();
2569
+ const run = async () => {
2570
+ if (busy || targets.length === 0) return;
2571
+ setBusy(true);
2572
+ setError("");
2573
+ setResult("");
2574
+ let okCount = 0;
2575
+ let alreadyCount = 0;
2576
+ let totalCount = 0;
2577
+ const failures = [];
2578
+ for (const t of targets) try {
2579
+ const data = await (await fetch(`${ROUTER_API_BASE}/suppliers/${t.id}/checkin`, {
2580
+ method: "POST",
2581
+ cache: "no-store"
2582
+ })).json();
2583
+ if (data.error !== void 0) {
2584
+ failures.push(`${t.name}:${data.error}`);
2585
+ continue;
2586
+ }
2587
+ okCount += data.succeeded;
2588
+ alreadyCount += data.already;
2589
+ totalCount += data.total;
2590
+ if (!data.ok) {
2591
+ const first = (data.results ?? []).find((r) => r.status !== "ok" && r.status !== "already");
2592
+ failures.push(`${t.name}:${first?.message ?? "有链接签到失败"}`);
2593
+ }
2594
+ } catch (err) {
2595
+ failures.push(`${t.name}:${err.message}`);
2596
+ }
2597
+ writeStamp();
2598
+ setStamp(todayKey());
2599
+ const parts = [`${okCount}/${totalCount} 成功`];
2600
+ if (alreadyCount > 0) parts.push(`${alreadyCount} 今日已签`);
2601
+ setResult(parts.join(" · "));
2602
+ if (failures.length > 0) setError(failures.join(";"));
2603
+ onRefresh();
2604
+ setBusy(false);
2605
+ };
2606
+ /**
2607
+ * 第三行文案:所有状态/结果都压在这一行里,行数恒定。
2608
+ * 优先级:失败 > 本次结果 > 今日已点 > 默认供应商数。
2609
+ * 失败时截断显示(全量挂 title),不换行。
2610
+ */
2611
+ const hintText = () => {
2612
+ if (targets.length === 0) return "没有支持签到的供应商";
2613
+ if (busy) return "签到中…";
2614
+ if (error !== "") return `失败 · ${error}`;
2615
+ if (result !== "") return result;
2616
+ return clickedToday ? `今日已点 · ${targets.length} 个供应商` : `${targets.length} 个供应商`;
2617
+ };
2618
+ /** 悬停说明:成功/失败都在这儿补全,正文只放得下一行 */
2619
+ const hintTitle = () => {
2620
+ if (targets.length === 0) return "没有供应商实现了 checkinNow 能力";
2621
+ const lines = [`对 ${targets.length} 个支持签到的供应商逐个签到:${targets.map((t) => t.name).join("、")}`];
2622
+ if (clickedToday) lines.push("今天在这个浏览器点过签到(不代表上游一定签上了)");
2623
+ if (result !== "") lines.push(`上次结果:${result}`);
2624
+ if (error !== "") lines.push(`失败:${error}`);
2625
+ return lines.join("\n");
2626
+ };
2627
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2628
+ className: `dshr-statCard dshr-statCard-checkin${clickedToday ? " dshr-statCard-done" : ""}`,
2629
+ title: hintTitle(),
2630
+ children: [
2631
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2632
+ className: "dshr-statLabel",
2633
+ children: "签到"
2634
+ }),
2635
+ targets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2636
+ className: "dshr-statValue",
2637
+ children: "—"
2638
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2639
+ type: "button",
2640
+ className: `dshr-checkinButton${busy ? " dshr-checkinButton-busy" : ""}`,
2641
+ onClick: () => {
2642
+ run();
2643
+ },
2644
+ disabled: busy,
2645
+ title: hintTitle(),
2646
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2647
+ className: busy ? "dshr-spin" : void 0,
2648
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon$1, {
2649
+ d: busy ? I$1.loading : I$1.check,
2650
+ size: 14
2651
+ })
2652
+ }), busy ? "签到中…" : "一键签到"]
2653
+ }),
2654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2655
+ className: `dshr-statHint${error !== "" ? " dshr-statHint-err" : ""}`,
2656
+ title: error !== "" ? error : void 0,
2657
+ children: hintText()
2658
+ })
2659
+ ]
2660
+ });
2661
+ }
2662
+ //#endregion
2498
2663
  //#region src/client/StatsTab.tsx
2499
2664
  /**
2500
2665
  * 概览 —— 仿 9router `Dashboard → Usage → Overview` 的统计看板。
@@ -2953,7 +3118,7 @@ window.__ModuleLoader__.load({
2953
3118
  })]
2954
3119
  });
2955
3120
  }
2956
- function StatsTab({ onRefresh, refreshing, active }) {
3121
+ function StatsTab({ onRefresh, refreshing, active, health }) {
2957
3122
  const [period, setPeriod] = (0, react.useState)("today");
2958
3123
  const [stats, setStats] = (0, react.useState)(null);
2959
3124
  const [chart, setChart] = (0, react.useState)([]);
@@ -3073,6 +3238,10 @@ window.__ModuleLoader__.load({
3073
3238
  value: fmtDuration(s?.avgDurationMs),
3074
3239
  hint: (s?.avgTtfbMs ?? 0) > 0 ? `首字节 ${fmtDuration(s?.avgTtfbMs)}` : void 0,
3075
3240
  tone: "warn"
3241
+ }),
3242
+ health !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckinCard, {
3243
+ health,
3244
+ onRefresh
3076
3245
  })
3077
3246
  ]
3078
3247
  }),
@@ -3129,12 +3298,20 @@ window.__ModuleLoader__.load({
3129
3298
  /**
3130
3299
  * 路由系统 (Routing System) panel — a simplified 9router.
3131
3300
  *
3132
- * Three tabs fed by the host half (/router/api/*), mirroring 9router's
3133
- * dashboard: 供应商 (supplier cards → detail: accounts + add-account + models),
3134
- * 组合 (combo fallback chains), 端点与密钥 (endpoint URL + API keys + auth).
3301
+ * Four tabs fed by the host half (/router/api/*), mirroring 9router's
3302
+ * dashboard: 概览 (usage), 供应商 (supplier cards → detail: accounts +
3303
+ * add-account + models), 组合 (combo fallback chains),
3304
+ * 端点与密钥 (endpoint URL + API keys + auth).
3135
3305
  *
3136
- * Masthead: 返回会话 button on the LEFT, refresh on the right.
3137
- * No online/offline concept — suppliers render whatever state they have.
3306
+ * **挂载在「设置」里**(`settings.section` 座位),所以布局规则跟中心栏劫持时期不同:
3307
+ * - 没有 masthead / 返回会话按钮 —— 设置面板自带标题栏和关闭
3308
+ * - **tab 条固定、只有内容区滚**:根节点限高 `height:100%`,内容区
3309
+ * `flex:1 + min-height:0 + overflow-y:auto` 自己滚。
3310
+ * 根节点**必须限高** —— 不限高内容会把外壳撑开,退化成整页滚、
3311
+ * tab 条跟着滚上去
3312
+ * - 根节点外面套了一层 `display: contents`(宿主 slot renderer 给的),
3313
+ * 不过实测 `height:100%` 仍能穿过它解析到外壳高度
3314
+ * - 内容宽度实测 564px(面板 612 − 左右 padding 24),卡片 grid 按这个宽度排
3138
3315
  */
3139
3316
  async function getJson(path) {
3140
3317
  const response = await fetch(`${ROUTER_API_BASE}${path}`, { cache: "no-store" });
@@ -3202,7 +3379,10 @@ window.__ModuleLoader__.load({
3202
3379
  children: [supplier.icon !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
3203
3380
  className: "dshr-supplierIcon",
3204
3381
  src: supplier.icon,
3205
- alt: ""
3382
+ alt: "",
3383
+ onError: (e) => {
3384
+ e.currentTarget.style.display = "none";
3385
+ }
3206
3386
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3207
3387
  className: "dshr-supplierName",
3208
3388
  children: supplier.name
@@ -3228,120 +3408,107 @@ window.__ModuleLoader__.load({
3228
3408
  });
3229
3409
  };
3230
3410
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3231
- className: "dshr-shell",
3232
- children: [
3233
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
3234
- className: "dshr-masthead",
3235
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3411
+ className: "dshr-settings",
3412
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
3413
+ className: "dshr-tabs",
3414
+ role: "tablist",
3415
+ "aria-label": "路由分区",
3416
+ children: [
3417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3418
+ type: "button",
3419
+ role: "tab",
3420
+ "aria-selected": tab === "overview",
3421
+ className: `dshr-tab${tab === "overview" ? " dshr-tab-on" : ""}`,
3422
+ onClick: () => setTab("overview"),
3423
+ children: "概览"
3424
+ }),
3425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3426
+ type: "button",
3427
+ role: "tab",
3428
+ "aria-selected": tab === "suppliers",
3429
+ className: `dshr-tab${tab === "suppliers" ? " dshr-tab-on" : ""}`,
3430
+ onClick: () => setTab("suppliers"),
3431
+ children: ["供应商 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3432
+ className: "dshr-navCount",
3433
+ children: suppliers.length
3434
+ })]
3435
+ }),
3436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3236
3437
  type: "button",
3237
- className: "dshr-backButton",
3438
+ role: "tab",
3439
+ "aria-selected": tab === "combos",
3440
+ className: `dshr-tab${tab === "combos" ? " dshr-tab-on" : ""}`,
3441
+ onClick: () => setTab("combos"),
3442
+ children: ["组合 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3443
+ className: "dshr-navCount",
3444
+ children: combos.length
3445
+ })]
3446
+ }),
3447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3448
+ type: "button",
3449
+ role: "tab",
3450
+ "aria-selected": tab === "endpoint",
3451
+ className: `dshr-tab${tab === "endpoint" ? " dshr-tab-on" : ""}`,
3452
+ onClick: () => setTab("endpoint"),
3453
+ children: "端点与密钥"
3454
+ }),
3455
+ onBack !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3456
+ type: "button",
3457
+ className: "dshr-tabBack",
3238
3458
  onClick: onBack,
3239
- title: "返回会话",
3240
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3241
- viewBox: "0 0 16 16",
3242
- width: "15",
3243
- height: "15",
3244
- fill: "none",
3245
- stroke: "currentColor",
3246
- strokeWidth: "1.5",
3247
- strokeLinecap: "round",
3248
- strokeLinejoin: "round",
3249
- "aria-hidden": "true",
3250
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 3 5 8l5 5" })
3251
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "返回会话" })]
3252
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3253
- className: "dshr-brand",
3254
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3255
- className: "dshr-brandTitle",
3256
- children: ROUTER_TITLE
3257
- })
3258
- })]
3259
- }),
3260
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
3261
- className: "dshr-nav",
3262
- children: [
3263
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3264
- type: "button",
3265
- "aria-current": tab === "overview" ? "page" : void 0,
3266
- onClick: () => setTab("overview"),
3267
- children: "概览"
3268
- }),
3269
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3270
- type: "button",
3271
- "aria-current": tab === "suppliers" ? "page" : void 0,
3272
- onClick: () => setTab("suppliers"),
3273
- children: ["供应商 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3274
- className: "dshr-navCount",
3275
- children: suppliers.length
3276
- })]
3277
- }),
3278
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3279
- type: "button",
3280
- "aria-current": tab === "combos" ? "page" : void 0,
3281
- onClick: () => setTab("combos"),
3282
- children: ["组合 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3283
- className: "dshr-navCount",
3284
- children: combos.length
3285
- })]
3286
- }),
3287
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3288
- type: "button",
3289
- "aria-current": tab === "endpoint" ? "page" : void 0,
3290
- onClick: () => setTab("endpoint"),
3291
- children: "端点与密钥"
3292
- })
3293
- ]
3294
- }),
3295
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3296
- className: "dshr-content",
3297
- children: [
3298
- tab === "overview" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTab, {
3299
- active: true,
3300
- refreshing,
3301
- onRefresh: () => void refresh()
3302
- }),
3303
- tab === "suppliers" && (detailSupplier !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SupplierDetail, {
3304
- supplier: detailSupplier,
3305
- accounts,
3306
- statusLoading: snapshot.status === null,
3307
- onBack: () => setDetailSupplier(null),
3308
- onRefresh: () => void refresh()
3309
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3310
- className: "dshr-tabBody",
3311
- children: [
3312
- suppliers.length === 0 && snapshot.health !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3313
- className: "dshr-empty",
3314
- children: "暂无供应商"
3315
- }),
3316
- renderSupplierGroup("内置", suppliers.filter((s) => s.source !== "external")),
3317
- renderSupplierGroup("插件", suppliers.filter((s) => s.source === "external")),
3318
- suppliersError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3319
- className: "dshr-empty",
3320
- children: suppliersError
3321
- })
3322
- ]
3323
- })),
3324
- tab === "combos" && (snapshot.combos === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3325
- className: "dshr-tabBody",
3326
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3459
+ children: "返回会话"
3460
+ })
3461
+ ]
3462
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3463
+ className: "dshr-settingsBody",
3464
+ children: [
3465
+ tab === "overview" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTab, {
3466
+ active: true,
3467
+ refreshing,
3468
+ health: snapshot.health,
3469
+ onRefresh: () => void refresh()
3470
+ }),
3471
+ tab === "suppliers" && (detailSupplier !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SupplierDetail, {
3472
+ supplier: detailSupplier,
3473
+ accounts,
3474
+ statusLoading: snapshot.status === null,
3475
+ onBack: () => setDetailSupplier(null),
3476
+ onRefresh: () => void refresh()
3477
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3478
+ className: "dshr-tabBody",
3479
+ children: [
3480
+ suppliers.length === 0 && snapshot.health !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3327
3481
  className: "dshr-empty",
3328
- children: "加载中…"
3329
- })
3330
- }) : combosError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3331
- className: "dshr-tabBody",
3332
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3482
+ children: "暂无供应商"
3483
+ }),
3484
+ renderSupplierGroup("内置", suppliers.filter((s) => s.source !== "external")),
3485
+ renderSupplierGroup("插件", suppliers.filter((s) => s.source === "external")),
3486
+ suppliersError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3333
3487
  className: "dshr-empty",
3334
- children: combosError
3488
+ children: suppliersError
3335
3489
  })
3336
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CombosTab, {
3337
- combos,
3338
- aliases: snapshot.combos?.aliases ?? [],
3339
- onRefresh: () => void refresh()
3340
- })),
3341
- tab === "endpoint" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndpointTab, {})
3342
- ]
3343
- })
3344
- ]
3490
+ ]
3491
+ })),
3492
+ tab === "combos" && (snapshot.combos === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3493
+ className: "dshr-tabBody",
3494
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3495
+ className: "dshr-empty",
3496
+ children: "加载中…"
3497
+ })
3498
+ }) : combosError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3499
+ className: "dshr-tabBody",
3500
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3501
+ className: "dshr-empty",
3502
+ children: combosError
3503
+ })
3504
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CombosTab, {
3505
+ combos,
3506
+ aliases: snapshot.combos?.aliases ?? [],
3507
+ onRefresh: () => void refresh()
3508
+ })),
3509
+ tab === "endpoint" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndpointTab, {})
3510
+ ]
3511
+ })]
3345
3512
  });
3346
3513
  }
3347
3514
  //#endregion
@@ -3441,9 +3608,9 @@ window.__ModuleLoader__.load({
3441
3608
  let root;
3442
3609
  let placed = false;
3443
3610
  const syncLabel = () => {
3444
- if (entry.getAttribute("aria-label") !== "路由系统") entry.setAttribute("aria-label", ROUTER_TITLE);
3445
- if (entry.title !== "路由系统") entry.title = ROUTER_TITLE;
3446
- if (label.textContent !== "路由系统") label.textContent = ROUTER_TITLE;
3611
+ if (entry.getAttribute("aria-label") !== "路由") entry.setAttribute("aria-label", ROUTER_TITLE);
3612
+ if (entry.title !== "路由") entry.title = ROUTER_TITLE;
3613
+ if (label.textContent !== "路由") label.textContent = ROUTER_TITLE;
3447
3614
  };
3448
3615
  const rootObserver = new MutationObserver(() => {
3449
3616
  if (root === void 0 || !root.isConnected) {
@@ -3655,17 +3822,117 @@ window.__ModuleLoader__.load({
3655
3822
  };
3656
3823
  }
3657
3824
  //#endregion
3825
+ //#region src/client/settings-section.tsx
3826
+ /**
3827
+ * 「设置 → 路由」页 —— 走 DSH 官方 `settings.section` 座位。
3828
+ *
3829
+ * 容器几何(浏览器实测,不是估的)决定了这里的写法:
3830
+ * - 内容宽 564px(面板 612 − 左右 padding 24)
3831
+ * - 外壳 `.VOzbGW_options` 是 `overflow-y:auto` 且高度确定,所以**根节点
3832
+ * 限高**(`height:100%`)+ 内容区自己滚,才能做到「tab 条固定、只有
3833
+ * 内容区滚」。不限高就会退化成整页滚,tab 条跟着滚上去
3834
+ * - 根节点外面套了一层 `display: contents`(宿主 slot renderer 给的),
3835
+ * 但实测 `height:100%` 能穿过它解析到外壳高度
3836
+ * - 横向会溢出:老布局是按整个中心栏宽做的,卡片 grid 必须按 564px 排
3837
+ *
3838
+ * 组件本身无状态、无 props:数据全在 RouterView 里自己 fetch。
3839
+ */
3840
+ /** 设置页入口组件。 */
3841
+ function RouterSettingsSection() {
3842
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouterView, {});
3843
+ }
3844
+ //#endregion
3845
+ //#region src/client/settings-nav-icon.ts
3846
+ /**
3847
+ * 给设置导航里「路由」这一行换掉宿主的默认齿轮。
3848
+ *
3849
+ * 为什么需要这个:DSH 0.1.x 的 `settings.section` 注册**只认 id / order / label
3850
+ * 三个字段**,图标是宿主内部按「内置 id 白名单」选的,插件传不了。所以外部
3851
+ * section 一律拿到通用齿轮(跟「通用设置」「远程访问」同一个)。
3852
+ * 在契约长出 icon 字段之前,只能注册后认领自己的那一行。
3853
+ *
3854
+ * 做法跟 dsh-better-sidebar 的 settings-nav-icon.ts 一致:给自己的行打一个
3855
+ * 属性标记,再由 CSS 用 mask 渲染图标(见 router.css 的
3856
+ * `[data-dsh-router-settings-nav]`)。用 mask + currentColor 是为了跟随
3857
+ * 宿主导航的 hover/active 配色,也保持 16px 的图标节奏。
3858
+ *
3859
+ * 匹配方式:按行的**可见文案**认领(label 可能本地化),且只认 `nav button`,
3860
+ * 碰到同名的别人家 section 不会误伤(我们同时校验它确实在设置面板里)。
3861
+ * 卸载时断开观察并摘掉标记,HMR / 插件热卸载安全。
3862
+ */
3863
+ /** 打在设置导航按钮上的标记属性。 */
3864
+ var SETTINGS_NAV_MARKER = "data-dsh-router-settings-nav";
3865
+ /** 设置面板的导航按钮选择器(宿主结构:dialog 里的 nav)。 */
3866
+ var NAV_BUTTON_SELECTOR = "[role=\"dialog\"] nav button";
3867
+ /**
3868
+ * 持续把标记打在文案等于 `label` 的那一行的身上。
3869
+ * @param label 当前本地化的 section 标题(跟注册时的 label 一致)
3870
+ * @returns 清理函数:断开观察 + 摘掉所有本插件打的标记
3871
+ */
3872
+ function registerSettingsNavIcon(label) {
3873
+ let disposed = false;
3874
+ const wanted = label.trim();
3875
+ const sync = () => {
3876
+ if (disposed || wanted === "") return;
3877
+ for (const button of document.querySelectorAll(NAV_BUTTON_SELECTOR)) if (button.textContent?.trim() === wanted) button.setAttribute(SETTINGS_NAV_MARKER, "");
3878
+ else button.removeAttribute(SETTINGS_NAV_MARKER);
3879
+ };
3880
+ sync();
3881
+ const observer = new MutationObserver(sync);
3882
+ observer.observe(document.body, {
3883
+ childList: true,
3884
+ subtree: true,
3885
+ characterData: true
3886
+ });
3887
+ return () => {
3888
+ disposed = true;
3889
+ observer.disconnect();
3890
+ for (const el of document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`)) el.removeAttribute(SETTINGS_NAV_MARKER);
3891
+ };
3892
+ }
3893
+ //#endregion
3658
3894
  //#region src/client/index.tsx
3659
3895
  /**
3660
- * Client half of dsh-router: mounts the 路由系统 sidebar entry and its
3661
- * center-column panel. Pure DOM + fetch — no injected DSH services needed:
3662
- * the panel reads /router/api/* (same-origin), which the host half serves.
3896
+ * Client half of dsh-router.
3897
+ *
3898
+ * 挂载点是「设置 → 路由」(官方 `settings.section` 座位),不再是侧边栏
3899
+ * DOM 注入 + 中心栏劫持。`slots` / `locale` 由宿主运行时提供(profile 的
3900
+ * node_modules 里 @deepseek-ai 是空的:这些包不入 node_modules,按
3901
+ * package.json 的 dsh.client.inject 声明即可)。
3902
+ *
3903
+ * 拿不到 slots 时(老宿主)回退到原来的中心栏面板,不至于整个插件失效。
3663
3904
  */
3664
- /** No hard service dependencies; the web shell injects react/cordis. */
3665
- var inject = [];
3666
- /** Mount the routing-system workspace once the browser context is ready. */
3905
+ /**
3906
+ * 设置导航里这一页的显示名。同时是注册 label 和「认领图标」时匹配的文案,
3907
+ * 两处必须一致 —— 认领逻辑按可见文案找自己那一行(见 settings-nav-icon.ts)。
3908
+ */
3909
+ var SECTION_LABEL = "路由";
3910
+ /**
3911
+ * 必须声明:Cordis 的 ctx 是 Proxy,未声明就访问 `ctx.slots` 会直接抛
3912
+ * `cannot get property "slots" without inject`(实测错误,不是理论风险)——
3913
+ * 连 `ctx.slots === undefined` 这种防御性判断都跑不到,getter 先炸。
3914
+ */
3915
+ var inject = ["slots"];
3916
+ /** 挂载「设置 → 路由」页;拿不到 slots 时返回 undefined 表示跳过。 */
3917
+ function mountSettingsSection(ctx) {
3918
+ const slots = ctx.slots;
3919
+ if (slots === void 0) return void 0;
3920
+ return slots.inject("settings.section", () => slots.register({
3921
+ name: "settings.section",
3922
+ id: "dsh-router",
3923
+ order: 10,
3924
+ label: SECTION_LABEL
3925
+ }, RouterSettingsSection));
3926
+ }
3667
3927
  function apply(ctx) {
3668
- ctx.effect(() => mountRouterWorkspace(), "dsh-router: routing system workspace");
3928
+ const disposeSettings = mountSettingsSection(ctx);
3929
+ if (disposeSettings === void 0) {
3930
+ console.warn("[dsh-router] 宿主未提供 slots,回退到侧边栏面板");
3931
+ ctx.effect(() => mountRouterWorkspace(), "dsh-router: routing system workspace");
3932
+ return;
3933
+ }
3934
+ ctx.effect(() => disposeSettings, "dsh-router: settings section");
3935
+ ctx.effect(() => registerSettingsNavIcon(SECTION_LABEL), "dsh-router: settings nav icon");
3669
3936
  }
3670
3937
  //#endregion
3671
3938
  exports.apply = apply;