dsh-any-deepseek 0.0.19 โ†’ 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +44 -97
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -201,7 +201,7 @@ window.__ModuleLoader__.load({
201
201
  }
202
202
  //#endregion
203
203
  //#region \0ad-css:src/client/SettingsPage
204
- const css = "/* Panel styles.\n *\n * ๐Ÿ”ด There are NO `--dsw-alias-*` design tokens on this host (measured 2026-10-21: the document\n * declares 29 custom properties and not one is a colour). This file used to reference a dozen of\n * them, every one falling back to a hardcoded LIGHT value (`rgba(0,0,0,0.12)` and friends) โ€” so\n * on the dark theme the borders and dividers were effectively invisible.\n *\n * โ‡’ Derive everything from `currentColor`, which the host sets per theme. `color-mix` gives the\n * low-contrast borders and fills that a token would have provided, and it follows the theme\n * automatically instead of assuming one palette.\n */\n\n.ad-page {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px 2px 12px;\n}\n\n/* One place to change the two theme-derived values the whole panel uses. */\n.ad-page {\n --ad-line: color-mix(in srgb, currentColor 18%, transparent);\n --ad-line-strong: color-mix(in srgb, currentColor 32%, transparent);\n --ad-fill-subtle: color-mix(in srgb, currentColor 7%, transparent);\n /* The current row's fill: visible against both themes because it is derived from the text\n colour the host already picked, not from an assumed palette. */\n --ad-fill-current: color-mix(in srgb, currentColor 11%, transparent);\n --ad-danger: #e5534b;\n}\n\n.ad-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n\n.ad-intro {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n opacity: 0.72;\n}\n\n.ad-error {\n border: 1px solid var(--ad-danger);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.ad-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.ad-sectionTitle {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n opacity: 0.6;\n}\n\n.ad-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/*\n * ๐Ÿ”ด Compact rows (owner request 2026-10-24: ใ€Œๆฏไธชๆธ ้“้ซ˜ๅบฆๅธŒๆœ›ๅฐไธ€ไบ›๏ผŒ็ดงๅ‡‘ไบ›ใ€).\n *\n * The row used to be a four-line stack โ€” title / modelยทapiยทbaseURL / key line / balance โ€” and\n * with five channels that is most of the panel spent on air. The fix is layout, not information:\n * everything stays on screen, but the secondary facts move BESIDE the name instead of under it.\n *\n * What changed and why:\n * ยท padding 10pxโ†’7px and gap 6pxโ†’4px: the row now reads as one line of text plus a hairline of\n * metadata, instead of a card with its own interior spacing;\n * ยท `ad-rowMain` became a ROW (was a column): the title line and the meta line share one axis,\n * so the row is two text lines tall at most, not four;\n * ยท the meta line keeps `flex-wrap` โ€” on a narrow panel it may wrap, and that is the correct\n * trade: a row that stays readable beats one that clips the endpoint URL.\n */\n.ad-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px solid var(--ad-line);\n border-radius: 8px;\n padding: 7px 10px;\n /*\n * The left edge is reserved on EVERY row so the current one does not shift its content when\n * selected โ€” a row that reindents on click makes the list feel like it moved.\n */\n border-left: 3px solid transparent;\n}\n\n/*\n * ๐Ÿ”ด The current channel is marked three ways, not one.\n *\n * It used to be a small badge plus a barely-different border, and the owner could not tell at a\n * glance which relay was live โ€” glanceability is the whole point of a single-choice list. So:\n * a colour bar down the left edge, a stronger border, and a tinted fill. All derived from\n * `currentColor`, because this host ships no colour tokens (measured: 29 custom properties, none\n * of them a colour), so any hardcoded value would assume the wrong theme.\n */\n.ad-row-current {\n border-color: var(--ad-line-strong);\n border-left-color: currentColor;\n background: var(--ad-fill-current);\n}\n\n/* The ๅฝ“ๅ‰ badge keeps its place as the textual confirmation, for when colour is not enough. */\n.ad-row-current .ad-badge {\n border-color: currentColor;\n opacity: 1;\n font-weight: 600;\n}\n\n/* A rename button must not look like the row's own selection affordance. */\n.ad-renameBtn {\n margin-left: auto;\n flex: none;\n}\n\n/*\n * The row's content is a COLUMN of at most two lines: (1) name + status, (2) everything else.\n * It was a four-line stack before the compactness pass โ€” see the note on `.ad-row`.\n */\n.ad-rowMain {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.ad-rowTitleLine {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 500;\n min-width: 0;\n}\n\n.ad-rowTitle {\n white-space: nowrap;\n}\n\n.ad-badge {\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 999px;\n border: 1px solid currentColor;\n opacity: 0.8;\n}\n\n.ad-warn {\n color: #b7791f;\n cursor: help;\n}\n\n/*\n * The single detail line under a channel's name.\n *\n * It carries what used to be FOUR stacked lines (endpoint identity / key state / probe verdict /\n * balance). `flex-wrap` is deliberate: on a narrow panel these wrap to a second line, which is a\n * better outcome than clipping the endpoint URL. `row-gap` is tighter than `column-gap` so a wrap\n * still reads as one paragraph rather than as separate rows.\n */\n.ad-rowDetail {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n column-gap: 6px;\n row-gap: 2px;\n font-size: 11px;\n opacity: 0.66;\n min-width: 0;\n}\n\n.ad-mono {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n word-break: break-all;\n}\n\n.ad-sep {\n opacity: 0.5;\n}\n\n.ad-rowActions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n/*\n * Buttons: sized to be hit, not guessed at.\n *\n * The previous 4px/10px padding made a 22px-tall target, which is below the comfortable\n * threshold and made the row actions (ๆต‹่ฏ• / ็งป้™ค) look like text rather than controls. 6px/12px\n * with a minimum width gives each one a real box while keeping the row compact โ€” a row action is\n * secondary, so it should be easy to hit and easy to ignore.\n */\n.ad-btn {\n font-size: 11px;\n font-family: inherit;\n line-height: 1.5;\n padding: 5px 12px;\n min-width: 56px;\n min-height: 26px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n transition: background-color 120ms ease, border-color 120ms ease, opacity 120ms ease;\n}\n\n.ad-btn:hover:not(:disabled) {\n border-color: var(--ad-line-strong);\n background: var(--ad-fill-subtle);\n}\n\n.ad-btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.ad-btn-quiet {\n opacity: 0.85;\n}\n\n.ad-probe {\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-probe-ok {\n color: #2f855a;\n}\n\n.ad-probe-bad {\n color: #c53030;\n word-break: break-word;\n}\n\n.ad-hint,\n.ad-empty,\n.ad-note {\n margin: 0;\n font-size: 11px;\n line-height: 1.6;\n opacity: 0.66;\n}\n\n.ad-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n\n.ad-dot-on {\n background: #2f855a;\n}\n\n.ad-dot-off {\n border: 1px solid currentColor;\n opacity: 0.5;\n}\n\n.ad-dot-unknown {\n background: linear-gradient(90deg, currentColor 50%, transparent 50%);\n opacity: 0.5;\n}\n\n/* The channel row is a radio \"pick\" zone: the whole left side is clickable, and the radio sits\n at the start of the line so \"which one is live\" is readable down a single column. */\n.ad-rowPick {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n flex: 1;\n min-width: 0;\n cursor: pointer;\n}\n\n.ad-rowPick input[type='radio'] {\n flex: none;\n /* Optically centred against the first text line rather than the row box. */\n margin-top: 3px;\n accent-color: var(--ad-line-strong);\n cursor: pointer;\n}\n\n/* The switch is gone: selection is the only axis (see `channels.ts`). Keeping the rules would\n invite someone to bring the second axis back. */\n\n/* โ”€โ”€ top-level tabs (ๆธ ้“ / ็”จ้‡) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * The panel answers two questions asked at different moments: \"which relay serves me and does\n * its key work\" and \"what has each relay cost\". They used to be stacked on one scroll, so\n * reading the cost table meant scrolling past every channel row. As tabs, each answer gets the\n * whole panel and the reader never scrolls to reach the second one.\n *\n * The selected tab is marked by a bottom rule plus full opacity โ€” not by colour alone, because\n * this host ships no colour tokens and a colour-only cue would vary with the theme.\n */\n.ad-tabs {\n display: flex;\n gap: 4px;\n border-bottom: 1px solid var(--ad-line);\n}\n\n.ad-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Comfortable click target: 28px tall is enough to hit without aiming, and still compact\n enough that two tabs do not dominate the panel. */\n padding: 6px 14px;\n font-size: 12px;\n font-family: inherit;\n border: none;\n border-bottom: 2px solid transparent;\n /* Pulled down over the container's rule so the selected tab's underline replaces it. */\n margin-bottom: -1px;\n background: transparent;\n color: inherit;\n opacity: 0.62;\n cursor: pointer;\n}\n\n.ad-tab:hover {\n opacity: 0.85;\n}\n\n.ad-tab-on {\n opacity: 1;\n font-weight: 600;\n border-bottom-color: currentColor;\n}\n\n/* A count on the channel tab, so \"how many do I have\" is answered without opening it. */\n.ad-tabCount {\n font-size: 10px;\n font-weight: 500;\n min-width: 16px;\n padding: 0 4px;\n border-radius: 999px;\n text-align: center;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* โ”€โ”€ segmented control (ๆŒ‰ๆธ ้“ / ๆŒ‰ๅคฉ / ๅˆทๆ–ฐ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * A segmented control rather than free-floating pill buttons: these three share one meaning\n * (\"how the table below is grouped, and re-read it\"), so they are drawn as one object. The\n * borders collapse to avoid double lines between neighbours.\n */\n.ad-segmented {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--ad-line);\n border-radius: 7px;\n overflow: hidden;\n}\n\n.ad-seg {\n padding: 4px 11px;\n font-size: 11px;\n font-family: inherit;\n border: none;\n border-right: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n opacity: 0.7;\n cursor: pointer;\n}\n\n.ad-seg:last-child {\n border-right: none;\n}\n\n.ad-seg:hover:not(:disabled) {\n opacity: 1;\n background: var(--ad-fill-subtle);\n}\n\n.ad-seg-on {\n opacity: 1;\n font-weight: 600;\n background: var(--ad-fill-subtle);\n}\n\n/* Refresh is an action, not a grouping โ€” a gap separates it from the choice it refreshes. */\n.ad-seg-refresh {\n border-left: 1px solid var(--ad-line);\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n.ad-table th,\n.ad-table td {\n padding: 5px 8px;\n border-bottom: 1px solid var(--ad-line);\n text-align: left;\n}\n\n.ad-table th {\n font-weight: 500;\n opacity: 0.6;\n}\n\n.ad-num {\n text-align: right;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n\n.ad-total td {\n font-weight: 600;\n border-bottom: none;\n}\n\n/* โ”€โ”€ add-channel form โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n.ad-addForm {\n display: flex;\n flex-direction: column;\n gap: 14px;\n border: 1px dashed var(--ad-line);\n border-radius: 8px;\n padding: 14px;\n}\n\n.ad-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n font-size: 11px;\n}\n\n/* The label row: the field name, plus a required/optional marker that says which is which. */\n.ad-field > span:first-child {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n opacity: 0.9;\n}\n\n/*\n * ๅฟ…้€‰ / ๅฏ็ฉบ as a small marker rather than a sentence.\n *\n * The add form used to leave the reader guessing which fields could be skipped โ€” and the one\n * field that could not be skipped (the key) looked exactly like the two that could. The marker\n * is text, not colour, so it survives both themes and any colour-blindness.\n */\n.ad-req,\n.ad-opt {\n font-style: normal;\n font-size: 10px;\n font-weight: 400;\n padding: 1px 5px;\n border-radius: 4px;\n opacity: 0.7;\n}\n\n.ad-req {\n border: 1px solid var(--ad-line-strong);\n}\n\n.ad-opt {\n border: 1px dotted var(--ad-line);\n}\n\n/* Why this field exists, in one line โ€” printed under the control, not hidden in a tooltip. */\n.ad-fieldHelp {\n font-size: 11px;\n line-height: 1.55;\n opacity: 0.6;\n}\n\n.ad-input {\n font-size: 12px;\n font-family: inherit;\n /* 6px/9px keeps the control the same height as a `.ad-btn` (26px), so a field and its button\n on one line align without a per-case override. */\n padding: 5px 9px;\n min-height: 26px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n}\n\n.ad-input:focus-visible {\n outline: 2px solid var(--ad-line-strong);\n outline-offset: 1px;\n border-color: var(--ad-line-strong);\n}\n\n/*\n * ๐Ÿ”ด A native `<select>` cannot be themed by leaving the background transparent.\n *\n * The popup list is drawn by the browser, not by the closed control's box: the options inherit\n * nothing from it, so `background: transparent` made them render in the platform default (light)\n * palette against this dark theme โ€” reported by the owner and measured on the real host.\n *\n * ๐Ÿ”ด SECOND correction (measured 2026-10-21): this file used `var(--dsw-alias-color-*, #fallback)`\n * throughout, and **none of those variables exist**. The host declares only 29 custom properties\n * in total and not one is a colour token, so every `var()` here was falling back โ€” and a fallback\n * that happens to be set inside a shorthand can be silently dropped (the select's background\n * computed to `rgba(0,0,0,0)`, i.e. still transparent, which is exactly the bug).\n *\n * โ‡’ Use only values this file controls, derived from `currentColor` (the host sets a light text\n * colour on both themes) plus explicit translucent layers. No host token is assumed.\n */\nselect.ad-input {\n /* Tells the engine which palette to use for the popup. This part DID work. */\n color-scheme: dark light;\n /*\n * Paint the closed box with a layer that follows the surrounding text colour, so it matches\n * whichever theme is active instead of hardcoding a dark value into a light theme.\n */\n background-color: color-mix(in srgb, currentColor 8%, transparent);\n color: inherit;\n cursor: pointer;\n padding-right: 22px;\n /* Room for the chevron drawn below, so text never runs under it. */\n appearance: none;\n background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),\n linear-gradient(135deg, currentColor 50%, transparent 50%);\n background-position: calc(100% - 12px) calc(50% + 1px), calc(100% - 8px) calc(50% + 1px);\n background-size: 4px 4px, 4px 4px;\n background-repeat: no-repeat;\n}\n\nselect.ad-input:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 14%, transparent);\n}\n\nselect.ad-input:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n}\n\n/*\n * The popup itself follows `color-scheme`; these keep the option rows explicit for engines that\n * honour per-option colours. `color-scheme: dark` above is what actually stops the light-on-dark\n * list, because the popup is native chrome rather than a styled element.\n */\nselect.ad-input option {\n background-color: Canvas;\n color: CanvasText;\n}\n\n.ad-fieldError {\n margin: 0;\n font-size: 11px;\n color: #c53030;\n}\n\n/* โ”€โ”€ key row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * The key line states a FACT and offers the fix beside it: \"configured / not configured\",\n * the reference name the secret is filed under, and one text button that opens the input.\n *\n * The value is never shown โ€” the harness credential seam answers \"configured?\" and refuses to\n * answer anything else, so there is nothing to render even if we wanted to.\n *\n * ๐Ÿ”ด `inline-flex`, not `flex`, and NO vertical padding: since the compactness pass this sits\n * INSIDE `.ad-rowDetail` (one wrapping line), so a block-level flex box with `padding-top: 2px`\n * would add a break and a 2px bump to every row. Inline keeps \"ๅฏ†้’ฅๅทฒ้…็ฝฎ ๆ›ดๆข\" together as one\n * token in the flow.\n */\n.ad-keyLine {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n}\n\n.ad-keyOn {\n color: #2f855a;\n}\n\n.ad-keyOff {\n color: #b7791f;\n}\n\n/*\n * Remaining balance.\n *\n * Same line shape as the key state โ€” it is another fact about this channel, not a control. The\n * subtle fill marks it as a measurement (like `ad-keyName`) rather than a warning: the words\n * themselves say whether it is fine (\"72.50 USD\") or not (\"๏ผˆไฝ™้ขไธ่ถณ๏ผ‰\").\n */\n.ad-balance {\n font-variant-numeric: tabular-nums;\n font-size: 11px;\n padding: 1px 6px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n}\n\n/* A section heading with one action on its right (currently: ๆŸฅ่ฏขไฝ™้ข). */\n.ad-sectionBar {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n/* The reference name, not a secret: monospace so it lines up and misreads as a name, not a value. */\n.ad-keyName {\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* A text-shaped action. Styled as a link because it opens an inline editor, not a dialog. */\n.ad-link {\n font-size: 11px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n opacity: 0.75;\n cursor: pointer;\n}\n\n.ad-link:hover {\n opacity: 1;\n}\n\n/* The inline key editor: one masked field and one button, on the same line as the fact above. */\n.ad-keyForm {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-top: 4px;\n}\n\n.ad-keyForm .ad-input {\n flex: 1;\n min-width: 0;\n}\n\n/* A destructive action is quiet until hovered: it should be findable, not inviting. */\n.ad-btn-danger {\n opacity: 0.6;\n}\n\n.ad-btn-danger:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--ad-danger);\n color: var(--ad-danger);\n}\n\n/*\n * The one hint that must not be skimmed past: where the key ends up.\n *\n * It carries a left rule and slightly higher opacity than an ordinary hint, because a reader who\n * skips it has been told something false by omission โ€” the key IS in the profile document.\n */\n.ad-hint-warn {\n padding: 8px 10px;\n border-left: 3px solid var(--ad-line-strong);\n background: var(--ad-fill-subtle);\n border-radius: 0 6px 6px 0;\n opacity: 0.82;\n}\n\n/*\n * The table can be wider than a narrow settings pane (ten columns of numbers). Scrolling it\n * inside its own box keeps the tab strip and the channel list reachable, instead of pushing the\n * whole panel sideways.\n */\n.ad-tableWrap {\n overflow-x: auto;\n /* A hint that there is more to the right, without a scrollbar competing with the borders. */\n scrollbar-width: thin;\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n/* The first column is a name, not a number: keep it from being squeezed by the numeric columns. */\n.ad-table th:first-child,\n.ad-table td:first-child {\n white-space: nowrap;\n padding-right: 14px;\n}\n\n/* โ”€โ”€ footer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * States what this thing is (MIT, open, improvable) and where it lives. A user deciding whether\n * to fork it or add a relay should not have to search for the source.\n */\n.ad-foot {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: 8px;\n padding-top: 10px;\n border-top: 1px solid var(--ad-line);\n font-size: 11px;\n opacity: 0.6;\n}\n\n.ad-footLink {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 10px;\n}\n\n.ad-footLink:hover {\n opacity: 0.9;\n}\n";
204
+ const css = "/* Panel styles.\n *\n * ๐Ÿ”ด There are NO `--dsw-alias-*` design tokens on this host (measured 2026-10-21: the document\n * declares 29 custom properties and not one is a colour). This file used to reference a dozen of\n * them, every one falling back to a hardcoded LIGHT value (`rgba(0,0,0,0.12)` and friends) โ€” so\n * on the dark theme the borders and dividers were effectively invisible.\n *\n * โ‡’ Derive everything from `currentColor`, which the host sets per theme. `color-mix` gives the\n * low-contrast borders and fills that a token would have provided, and it follows the theme\n * automatically instead of assuming one palette.\n */\n\n.ad-page {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px 2px 12px;\n}\n\n/* One place to change the two theme-derived values the whole panel uses. */\n.ad-page {\n --ad-line: color-mix(in srgb, currentColor 18%, transparent);\n --ad-line-strong: color-mix(in srgb, currentColor 32%, transparent);\n --ad-fill-subtle: color-mix(in srgb, currentColor 7%, transparent);\n /*\n * ๐Ÿ”ด The ACTIVE channel is green (owner request 2026-10-25: ใ€Œๆฟ€ๆดป็š„ๆ ทๅผ็”จ็ปฟ่‰ฒใ€).\n *\n * It used to be derived from `currentColor`, which made the live row look like every other\n * \"slightly emphasised\" row โ€” the same neutral tint as a hover or a selected input. Green says\n * \"this one is ON\" in a way a grey tint cannot, and it matches the key-configured dot and the\n * ๅฏ†้’ฅๅทฒ้…็ฝฎ text that were already green, so the panel now uses one colour for one meaning:\n * green = working / live. Red stays reserved for destructive and for failures.\n *\n * Kept as one token so the mark (border + bar + fill + badge) can never drift apart between\n * the four places it is applied.\n */\n --ad-active: #2f855a;\n --ad-active-fill: color-mix(in srgb, var(--ad-active) 12%, transparent);\n --ad-danger: #e5534b;\n}\n\n.ad-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n\n.ad-intro {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n opacity: 0.72;\n}\n\n.ad-error {\n border: 1px solid var(--ad-danger);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.ad-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.ad-sectionTitle {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n opacity: 0.6;\n}\n\n.ad-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/*\n * ๐Ÿ”ด Compact rows (owner request 2026-10-24: ใ€Œๆฏไธชๆธ ้“้ซ˜ๅบฆๅธŒๆœ›ๅฐไธ€ไบ›๏ผŒ็ดงๅ‡‘ไบ›ใ€).\n *\n * The row used to be a four-line stack โ€” title / modelยทapiยทbaseURL / key line / balance โ€” and\n * with five channels that is most of the panel spent on air. The fix is layout, not information:\n * everything stays on screen, but the secondary facts move BESIDE the name instead of under it.\n *\n * What changed and why:\n * ยท padding 10pxโ†’7px and gap 6pxโ†’4px: the row now reads as one line of text plus a hairline of\n * metadata, instead of a card with its own interior spacing;\n * ยท `ad-rowMain` became a ROW (was a column): the title line and the meta line share one axis,\n * so the row is two text lines tall at most, not four;\n * ยท the meta line keeps `flex-wrap` โ€” on a narrow panel it may wrap, and that is the correct\n * trade: a row that stays readable beats one that clips the endpoint URL.\n */\n.ad-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px solid var(--ad-line);\n border-radius: 8px;\n padding: 7px 10px;\n /*\n * The left edge is reserved on EVERY row so the current one does not shift its content when\n * selected โ€” a row that reindents on click makes the list feel like it moved.\n */\n border-left: 3px solid transparent;\n}\n\n/*\n * ๐Ÿ”ด The current channel is marked in GREEN (owner 2026-10-25: ใ€Œๆฟ€ๆดป็š„ๆ ทๅผ็”จ็ปฟ่‰ฒใ€).\n *\n * Previously the marks were derived from `currentColor` โ€” a neutral bar, a stronger border and a\n * grey tint. All three were so low-contrast that the live row read as \"merely hovered\", which is\n * exactly what the owner was reacting to.\n *\n * Green is also the coherent choice: the key dot and ๅฏ†้’ฅๅทฒ้…็ฝฎ text were already green, so the\n * panel now uses ONE colour for ONE meaning (green = working / live), and red stays reserved for\n * destructive actions and failures.\n *\n * Four marks read from the same token so they cannot drift apart: left bar, border, fill, badge.\n */\n.ad-row-current {\n border-color: var(--ad-active);\n border-left-color: var(--ad-active);\n background: var(--ad-active-fill);\n}\n\n/* The ๅฝ“ๅ‰ badge is the textual confirmation, for when colour alone is not enough (colour-blind\n readers, screenshots, forced-colours mode). */\n.ad-row-current .ad-badge {\n border-color: var(--ad-active);\n color: var(--ad-active);\n opacity: 1;\n font-weight: 600;\n}\n\n/* The live row's NAME goes green too: the name is where the eye lands first, so marking only the\n border leaves the reader to connect two separate cues. */\n.ad-row-current .ad-rowTitle {\n color: var(--ad-active);\n}\n\n/*\n * The row's content is a COLUMN of at most two lines: (1) name + status, (2) everything else.\n * It was a four-line stack before the compactness pass โ€” see the note on `.ad-row`.\n */\n.ad-rowMain {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.ad-rowTitleLine {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 500;\n min-width: 0;\n}\n\n.ad-rowTitle {\n white-space: nowrap;\n}\n\n.ad-badge {\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 999px;\n border: 1px solid currentColor;\n opacity: 0.8;\n}\n\n.ad-warn {\n color: #b7791f;\n cursor: help;\n}\n\n/*\n * The live-state line under a channel's name: probe verdict and/or balance, nothing else.\n *\n * ๐Ÿ”ด It shrank twice at the owner's request, and the history matters:\n * 2026-10-24 ใ€Œ็ดงๅ‡‘ไบ›ใ€ โ€” four stacked lines (endpoint identity / key state / verdict / balance)\n * became one wrapping line;\n * 2026-10-25 ใ€Œ่ฟ™้ƒจๅˆ†ไธ่ฆไบ†ใ€ โ€” endpoint identity and key state were removed outright. What\n * remains is only what changes at runtime: did the probe pass, what is\n * the balance. Those two cannot be derived from anything else on screen,\n * which is why they survived while model/api/baseURL did not.\n *\n * The element is rendered ONLY when one of them exists, so an unprobed, unpriced channel shows a\n * single-line row with no orphan gap.\n */\n.ad-rowDetail {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n column-gap: 6px;\n row-gap: 2px;\n font-size: 11px;\n opacity: 0.66;\n min-width: 0;\n}\n\n.ad-mono {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n word-break: break-all;\n}\n\n.ad-sep {\n opacity: 0.5;\n}\n\n.ad-rowActions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n/*\n * Buttons: sized to be hit, not guessed at.\n *\n * The previous 4px/10px padding made a 22px-tall target, which is below the comfortable\n * threshold and made the row actions (ๆต‹่ฏ• / ็งป้™ค) look like text rather than controls. 6px/12px\n * with a minimum width gives each one a real box while keeping the row compact โ€” a row action is\n * secondary, so it should be easy to hit and easy to ignore.\n */\n.ad-btn {\n font-size: 11px;\n font-family: inherit;\n line-height: 1.5;\n padding: 5px 12px;\n min-width: 56px;\n min-height: 26px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n transition: background-color 120ms ease, border-color 120ms ease, opacity 120ms ease;\n}\n\n.ad-btn:hover:not(:disabled) {\n border-color: var(--ad-line-strong);\n background: var(--ad-fill-subtle);\n}\n\n.ad-btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.ad-btn-quiet {\n opacity: 0.85;\n}\n\n.ad-probe {\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-probe-ok {\n color: #2f855a;\n}\n\n.ad-probe-bad {\n color: #c53030;\n word-break: break-word;\n}\n\n.ad-hint,\n.ad-empty,\n.ad-note {\n margin: 0;\n font-size: 11px;\n line-height: 1.6;\n opacity: 0.66;\n}\n\n.ad-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n\n.ad-dot-on {\n background: #2f855a;\n}\n\n.ad-dot-off {\n border: 1px solid currentColor;\n opacity: 0.5;\n}\n\n.ad-dot-unknown {\n background: linear-gradient(90deg, currentColor 50%, transparent 50%);\n opacity: 0.5;\n}\n\n/* The channel row is a radio \"pick\" zone: the whole left side is clickable, and the radio sits\n at the start of the line so \"which one is live\" is readable down a single column. */\n.ad-rowPick {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n flex: 1;\n min-width: 0;\n cursor: pointer;\n}\n\n.ad-rowPick input[type='radio'] {\n flex: none;\n /* Optically centred against the first text line rather than the row box. */\n margin-top: 3px;\n accent-color: var(--ad-line-strong);\n cursor: pointer;\n}\n\n/* The switch is gone: selection is the only axis (see `channels.ts`). Keeping the rules would\n invite someone to bring the second axis back. */\n\n/* โ”€โ”€ top-level tabs (ๆธ ้“ / ็”จ้‡) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * The panel answers two questions asked at different moments: \"which relay serves me and does\n * its key work\" and \"what has each relay cost\". They used to be stacked on one scroll, so\n * reading the cost table meant scrolling past every channel row. As tabs, each answer gets the\n * whole panel and the reader never scrolls to reach the second one.\n *\n * The selected tab is marked by a bottom rule plus full opacity โ€” not by colour alone, because\n * this host ships no colour tokens and a colour-only cue would vary with the theme.\n */\n.ad-tabs {\n display: flex;\n gap: 4px;\n border-bottom: 1px solid var(--ad-line);\n}\n\n.ad-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Comfortable click target: 28px tall is enough to hit without aiming, and still compact\n enough that two tabs do not dominate the panel. */\n padding: 6px 14px;\n font-size: 12px;\n font-family: inherit;\n border: none;\n border-bottom: 2px solid transparent;\n /* Pulled down over the container's rule so the selected tab's underline replaces it. */\n margin-bottom: -1px;\n background: transparent;\n color: inherit;\n opacity: 0.62;\n cursor: pointer;\n}\n\n.ad-tab:hover {\n opacity: 0.85;\n}\n\n.ad-tab-on {\n opacity: 1;\n font-weight: 600;\n border-bottom-color: currentColor;\n}\n\n/* A count on the channel tab, so \"how many do I have\" is answered without opening it. */\n.ad-tabCount {\n font-size: 10px;\n font-weight: 500;\n min-width: 16px;\n padding: 0 4px;\n border-radius: 999px;\n text-align: center;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* โ”€โ”€ segmented control (ๆŒ‰ๆธ ้“ / ๆŒ‰ๅคฉ / ๅˆทๆ–ฐ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * A segmented control rather than free-floating pill buttons: these three share one meaning\n * (\"how the table below is grouped, and re-read it\"), so they are drawn as one object. The\n * borders collapse to avoid double lines between neighbours.\n */\n.ad-segmented {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--ad-line);\n border-radius: 7px;\n overflow: hidden;\n}\n\n.ad-seg {\n padding: 4px 11px;\n font-size: 11px;\n font-family: inherit;\n border: none;\n border-right: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n opacity: 0.7;\n cursor: pointer;\n}\n\n.ad-seg:last-child {\n border-right: none;\n}\n\n.ad-seg:hover:not(:disabled) {\n opacity: 1;\n background: var(--ad-fill-subtle);\n}\n\n.ad-seg-on {\n opacity: 1;\n font-weight: 600;\n background: var(--ad-fill-subtle);\n}\n\n/* Refresh is an action, not a grouping โ€” a gap separates it from the choice it refreshes. */\n.ad-seg-refresh {\n border-left: 1px solid var(--ad-line);\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n.ad-table th,\n.ad-table td {\n padding: 5px 8px;\n border-bottom: 1px solid var(--ad-line);\n text-align: left;\n}\n\n.ad-table th {\n font-weight: 500;\n opacity: 0.6;\n}\n\n.ad-num {\n text-align: right;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n\n.ad-total td {\n font-weight: 600;\n border-bottom: none;\n}\n\n/* โ”€โ”€ add-channel form โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n.ad-addForm {\n display: flex;\n flex-direction: column;\n gap: 14px;\n border: 1px dashed var(--ad-line);\n border-radius: 8px;\n padding: 14px;\n}\n\n.ad-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n font-size: 11px;\n}\n\n/* The label row: the field name, plus a required/optional marker that says which is which. */\n.ad-field > span:first-child {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n opacity: 0.9;\n}\n\n/*\n * ๅฟ…้€‰ / ๅฏ็ฉบ as a small marker rather than a sentence.\n *\n * The add form used to leave the reader guessing which fields could be skipped โ€” and the one\n * field that could not be skipped (the key) looked exactly like the two that could. The marker\n * is text, not colour, so it survives both themes and any colour-blindness.\n */\n.ad-req,\n.ad-opt {\n font-style: normal;\n font-size: 10px;\n font-weight: 400;\n padding: 1px 5px;\n border-radius: 4px;\n opacity: 0.7;\n}\n\n.ad-req {\n border: 1px solid var(--ad-line-strong);\n}\n\n.ad-opt {\n border: 1px dotted var(--ad-line);\n}\n\n/* Why this field exists, in one line โ€” printed under the control, not hidden in a tooltip. */\n.ad-fieldHelp {\n font-size: 11px;\n line-height: 1.55;\n opacity: 0.6;\n}\n\n.ad-input {\n font-size: 12px;\n font-family: inherit;\n /* 6px/9px keeps the control the same height as a `.ad-btn` (26px), so a field and its button\n on one line align without a per-case override. */\n padding: 5px 9px;\n min-height: 26px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n}\n\n.ad-input:focus-visible {\n outline: 2px solid var(--ad-line-strong);\n outline-offset: 1px;\n border-color: var(--ad-line-strong);\n}\n\n/*\n * ๐Ÿ”ด A native `<select>` cannot be themed by leaving the background transparent.\n *\n * The popup list is drawn by the browser, not by the closed control's box: the options inherit\n * nothing from it, so `background: transparent` made them render in the platform default (light)\n * palette against this dark theme โ€” reported by the owner and measured on the real host.\n *\n * ๐Ÿ”ด SECOND correction (measured 2026-10-21): this file used `var(--dsw-alias-color-*, #fallback)`\n * throughout, and **none of those variables exist**. The host declares only 29 custom properties\n * in total and not one is a colour token, so every `var()` here was falling back โ€” and a fallback\n * that happens to be set inside a shorthand can be silently dropped (the select's background\n * computed to `rgba(0,0,0,0)`, i.e. still transparent, which is exactly the bug).\n *\n * โ‡’ Use only values this file controls, derived from `currentColor` (the host sets a light text\n * colour on both themes) plus explicit translucent layers. No host token is assumed.\n */\nselect.ad-input {\n /* Tells the engine which palette to use for the popup. This part DID work. */\n color-scheme: dark light;\n /*\n * Paint the closed box with a layer that follows the surrounding text colour, so it matches\n * whichever theme is active instead of hardcoding a dark value into a light theme.\n */\n background-color: color-mix(in srgb, currentColor 8%, transparent);\n color: inherit;\n cursor: pointer;\n padding-right: 22px;\n /* Room for the chevron drawn below, so text never runs under it. */\n appearance: none;\n background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),\n linear-gradient(135deg, currentColor 50%, transparent 50%);\n background-position: calc(100% - 12px) calc(50% + 1px), calc(100% - 8px) calc(50% + 1px);\n background-size: 4px 4px, 4px 4px;\n background-repeat: no-repeat;\n}\n\nselect.ad-input:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 14%, transparent);\n}\n\nselect.ad-input:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n}\n\n/*\n * The popup itself follows `color-scheme`; these keep the option rows explicit for engines that\n * honour per-option colours. `color-scheme: dark` above is what actually stops the light-on-dark\n * list, because the popup is native chrome rather than a styled element.\n */\nselect.ad-input option {\n background-color: Canvas;\n color: CanvasText;\n}\n\n.ad-fieldError {\n margin: 0;\n font-size: 11px;\n color: #c53030;\n}\n\n/* โ”€โ”€ balance โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * Remaining balance.\n *\n * A fact about this channel, not a control. The subtle fill marks it as a measurement rather than\n * a warning: the words themselves say whether it is fine (\"72.50 USD\") or not (\"๏ผˆไฝ™้ขไธ่ถณ๏ผ‰\").\n */\n.ad-balance {\n font-variant-numeric: tabular-nums;\n font-size: 11px;\n padding: 1px 6px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n}\n\n/* A section heading with one action on its right (currently: ๆŸฅ่ฏขไฝ™้ข). */\n.ad-sectionBar {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n/* The reference name, not a secret: monospace so it lines up and misreads as a name, not a value. */\n.ad-keyName {\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* A text-shaped action. Styled as a link because it opens an inline editor, not a dialog. */\n.ad-link {\n font-size: 11px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n opacity: 0.75;\n cursor: pointer;\n}\n\n.ad-link:hover {\n opacity: 1;\n}\n\n/* The inline key editor: one masked field and one button, on the same line as the fact above. */\n.ad-keyForm {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-top: 4px;\n}\n\n.ad-keyForm .ad-input {\n flex: 1;\n min-width: 0;\n}\n\n/* A destructive action is quiet until hovered: it should be findable, not inviting. */\n.ad-btn-danger {\n opacity: 0.6;\n}\n\n.ad-btn-danger:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--ad-danger);\n color: var(--ad-danger);\n}\n\n/*\n * The one hint that must not be skimmed past: where the key ends up.\n *\n * It carries a left rule and slightly higher opacity than an ordinary hint, because a reader who\n * skips it has been told something false by omission โ€” the key IS in the profile document.\n */\n.ad-hint-warn {\n padding: 8px 10px;\n border-left: 3px solid var(--ad-line-strong);\n background: var(--ad-fill-subtle);\n border-radius: 0 6px 6px 0;\n opacity: 0.82;\n}\n\n/*\n * The table can be wider than a narrow settings pane (ten columns of numbers). Scrolling it\n * inside its own box keeps the tab strip and the channel list reachable, instead of pushing the\n * whole panel sideways.\n */\n.ad-tableWrap {\n overflow-x: auto;\n /* A hint that there is more to the right, without a scrollbar competing with the borders. */\n scrollbar-width: thin;\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n/* The first column is a name, not a number: keep it from being squeezed by the numeric columns. */\n.ad-table th:first-child,\n.ad-table td:first-child {\n white-space: nowrap;\n padding-right: 14px;\n}\n\n/* โ”€โ”€ footer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */\n\n/*\n * States what this thing is (MIT, open, improvable) and where it lives. A user deciding whether\n * to fork it or add a relay should not have to search for the source.\n */\n.ad-foot {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: 8px;\n padding-top: 10px;\n border-top: 1px solid var(--ad-line);\n font-size: 11px;\n opacity: 0.6;\n}\n\n.ad-footLink {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 10px;\n}\n\n.ad-footLink:hover {\n opacity: 0.9;\n}\n";
205
205
  if (typeof document !== "undefined" && document.querySelector("style[data-ad-css=\"SettingsPage\"]") === null) {
206
206
  const tag = document.createElement("style");
207
207
  tag.setAttribute("data-ad-css", "SettingsPage");
@@ -622,18 +622,6 @@ window.__ModuleLoader__.load({
622
622
  className: "ad-warn",
623
623
  title: row.warnings.join("\n"),
624
624
  children: "โš "
625
- }),
626
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
627
- type: "button",
628
- className: "ad-link ad-renameBtn",
629
- title: "ๆ”นๆ˜พ็คบๅ๏ผˆๆธ ้“ id ไธๅ˜๏ผ‰",
630
- onClick: (event) => {
631
- event.preventDefault();
632
- event.stopPropagation();
633
- setRenaming(renaming === row.id ? null : row.id);
634
- setRenameDraft(renaming === row.id ? "" : row.label);
635
- },
636
- children: renaming === row.id ? "ๅ–ๆถˆ" : "ๆ”นๅ"
637
625
  })
638
626
  ]
639
627
  }),
@@ -661,76 +649,15 @@ window.__ModuleLoader__.load({
661
649
  children: "ไฟๅญ˜ๅๅญ—"
662
650
  })]
663
651
  }),
664
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
652
+ (probeResult !== void 0 || balances[row.id] !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
665
653
  className: "ad-rowDetail",
666
- children: [
667
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
668
- className: "ad-mono",
669
- title: "ไธŠๆธธๆจกๅž‹ id",
670
- children: row.model
671
- }),
672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
673
- className: "ad-sep",
674
- children: "ยท"
675
- }),
676
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
677
- title: "ๆŽฅๅฃๅ่ฎฎ",
678
- children: row.api
679
- }),
680
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
681
- className: "ad-sep",
682
- children: "ยท"
683
- }),
684
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
685
- className: "ad-mono",
686
- title: "็ซฏ็‚นๅœฐๅ€",
687
- children: row.baseURL
688
- }),
689
- probeResult !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
690
- className: `ad-probe ad-probe-${probeResult.ok ? "ok" : "bad"}`,
691
- children: probeResult.ok ? `้€š๏ผˆ${probeResult.ms}ms๏ผ‰` : `ไธ้€š๏ผš${probeResult.code ?? "ERROR"}${probeResult.message === void 0 ? "" : ` โ€” ${probeResult.message}`}`
692
- }),
693
- row.keyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
694
- className: "ad-keyLine",
695
- children: [
696
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
697
- className: "ad-keyOn",
698
- children: "ๅฏ†้’ฅๅทฒ้…็ฝฎ"
699
- }),
700
- row.keyKind === "reference" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
701
- className: "ad-mono ad-keyName",
702
- children: row.keyRef
703
- }),
704
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
705
- type: "button",
706
- className: "ad-link",
707
- onClick: (event) => {
708
- event.preventDefault();
709
- setKeying(keying === row.id ? null : row.id);
710
- },
711
- children: keying === row.id ? "ๅ–ๆถˆ" : "ๆ›ดๆข"
712
- })
713
- ]
714
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
715
- className: "ad-keyLine",
716
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
717
- className: "ad-keyOff",
718
- children: "ๆœช้…็ฝฎๅฏ†้’ฅ"
719
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
720
- type: "button",
721
- className: "ad-link",
722
- onClick: (event) => {
723
- event.preventDefault();
724
- setKeying(keying === row.id ? null : row.id);
725
- },
726
- children: keying === row.id ? "ๅ–ๆถˆ" : "ๅกซๅ†™ๅฏ†้’ฅ"
727
- })]
728
- }),
729
- balances[row.id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
730
- className: "ad-balance",
731
- children: formatBalance(balances[row.id])
732
- })
733
- ]
654
+ children: [probeResult !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
655
+ className: `ad-probe ad-probe-${probeResult.ok ? "ok" : "bad"}`,
656
+ children: probeResult.ok ? `้€š๏ผˆ${probeResult.ms}ms๏ผ‰` : `ไธ้€š๏ผš${probeResult.code ?? "ERROR"}${probeResult.message === void 0 ? "" : ` โ€” ${probeResult.message}`}`
657
+ }), balances[row.id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
658
+ className: "ad-balance",
659
+ children: formatBalance(balances[row.id])
660
+ })]
734
661
  }),
735
662
  keying === row.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
736
663
  className: "ad-keyForm",
@@ -755,21 +682,41 @@ window.__ModuleLoader__.load({
755
682
  })]
756
683
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
757
684
  className: "ad-rowActions",
758
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
759
- type: "button",
760
- className: "ad-btn ad-btn-quiet",
761
- disabled: probing === row.id,
762
- onClick: () => {
763
- runProbe(row.id);
764
- },
765
- children: probing === row.id ? "ๆต‹่ฏ•ไธญโ€ฆ" : "ๆต‹่ฏ•"
766
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
767
- type: "button",
768
- className: "ad-btn ad-btn-danger",
769
- title: "็งป้™คๆญคๆธ ้“๏ผˆไธๅˆ ้™คๅ‡ญๆฎๆœฌ่บซ๏ผ‰",
770
- onClick: () => removeChannel(row.id),
771
- children: "็งป้™ค"
772
- })]
685
+ children: [
686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
687
+ type: "button",
688
+ className: "ad-btn ad-btn-quiet",
689
+ title: "ๆ”นๆ˜พ็คบๅ๏ผˆๆธ ้“ id ไธๅ˜๏ผ‰",
690
+ onClick: () => {
691
+ setRenaming(renaming === row.id ? null : row.id);
692
+ setRenameDraft(renaming === row.id ? "" : row.label);
693
+ },
694
+ children: renaming === row.id ? "ๅ–ๆถˆ" : "ๆ”นๅ"
695
+ }),
696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
697
+ type: "button",
698
+ className: "ad-btn ad-btn-quiet",
699
+ title: row.keyConfigured ? "ๆ›ดๆข่ฟ™ไธชๆธ ้“็š„ๅฏ†้’ฅ" : "ไธบ่ฟ™ไธชๆธ ้“ๅกซๅ†™ๅฏ†้’ฅ",
700
+ onClick: () => setKeying(keying === row.id ? null : row.id),
701
+ children: keying === row.id ? "ๅ–ๆถˆ" : "ๆ›ดๆขๅฏ†้’ฅ"
702
+ }),
703
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
704
+ type: "button",
705
+ className: "ad-btn ad-btn-quiet",
706
+ disabled: probing === row.id,
707
+ onClick: () => {
708
+ runProbe(row.id);
709
+ },
710
+ children: probing === row.id ? "ๆต‹่ฏ•ไธญโ€ฆ" : "ๆต‹่ฏ•"
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
713
+ type: "button",
714
+ className: "ad-btn ad-btn-danger",
715
+ title: "็งป้™คๆญคๆธ ้“๏ผˆไธๅˆ ้™คๅ‡ญๆฎๆœฌ่บซ๏ผ‰",
716
+ onClick: () => removeChannel(row.id),
717
+ children: "็งป้™ค"
718
+ })
719
+ ]
773
720
  })]
774
721
  }, row.id);
775
722
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-any-deepseek",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "One DeepSeek V4.1 Flash provider for DeepSeek Harness: paste a key per channel, switch channels manually, token usage booked per real upstream.",
5
5
  "license": "MIT",
6
6
  "repository": {