dsh-token-total 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +535 -223
- package/lib/index.js +47 -4
- package/lib/types/client/format.d.ts +8 -4
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/stats-icons.d.ts +4 -0
- package/package.json +1 -1
- package/src/client/ContextUsageEntry.module.css +130 -20
- package/src/client/ContextUsageEntry.tsx +11 -7
- package/src/client/StatsLineEnhanced.module.css +15 -24
- package/src/client/StatsLineEnhanced.tsx +33 -49
- package/src/client/TokenTotalPanel.module.css +259 -99
- package/src/client/TokenTotalPanel.tsx +197 -62
- package/src/client/format.ts +9 -7
- package/src/client/locales.ts +76 -0
- package/src/client/stats-icons.tsx +83 -0
- package/src/query.ts +53 -2
package/lib/client.js
CHANGED
|
@@ -5,9 +5,30 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
14
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
17
|
+
key = keys[i];
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
19
|
+
get: ((k) => from[k]).bind(null, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
26
|
+
value: mod,
|
|
27
|
+
enumerable: true
|
|
28
|
+
}) : target, mod));
|
|
9
29
|
//#endregion
|
|
10
30
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
11
32
|
let react = require("react");
|
|
12
33
|
let react_dom = require("react-dom");
|
|
13
34
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -29406,7 +29427,7 @@ window.__ModuleLoader__.load({
|
|
|
29406
29427
|
}
|
|
29407
29428
|
//#endregion
|
|
29408
29429
|
//#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/TokenTotalPanel.module.css?inline
|
|
29409
|
-
const css$2 = "/**\n * Usage statistics panel styles — a DSH-native CSS Module. Every visual\n * value rides the DSH semantic tokens (--dsw-alias-* colors, --dsw-font-*\n * typography, --ds-* motion); no static palette values, no theme selectors.\n * The heatmap's 5 levels come from the brand accent via color-mix; model\n * colours are the fixed --dsw-chart-1..5 series (rank order by token\n * volume) plus the gray --dsw-chart-other step — the reasonix token-total\n * palette, with a lifted dark variant defined below.\n */\n\n/* The panel as a standalone main panel. It gets the same 960px content column\n the Plugins page gives it — the declarations below mirror the Plugins page's\n own `.page_768838850` so both entries render identically. Without the cap the charts\n would stretch to whatever the centre column happens to be.\n\n One deliberate difference from that page: its 28px top inset is NOT part of\n this container. A main panel starts at the window's top-left corner, so an\n inset painted by this scrolling container is a strip above the content that\n no window-drag region covers; `.backRow_768838850` below owns the inset instead, which\n is the shape the shipped page plugins use for the same reason\n (`ui-plugin-manager`'s `.pageHead_768838850`/`.detailTop_768838850` and `ui-schedule`'s page\n head). The rendered position is unchanged. */\n.page_768838850 {\n display: flex;\n flex-direction: column;\n align-items: center;\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n padding: 0 clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n\n.page_768838850 > * {\n width: 100%;\n max-width: 960px;\n}\n\n/* The standalone panel's back control. Only the main panel renders this seat —\n the Plugins page entry already sits inside the page's own chrome, which\n draws its own crumb back to the bundle list. The spacing matches the gap the\n panel keeps between its own sections.\n\n The row also carries the panel's former 28px top inset: it starts at the\n window's top edge, so the whole strip above the back control drags the\n window (Electron app-regions are geometry, not stacking, so a strip owned by\n the scrolling container would subtract itself from the drag row beneath\n it). */\n.backRow_768838850 {\n display: flex;\n box-sizing: border-box;\n padding-top: 28px;\n margin-bottom: 18px;\n}\n\n.back_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.panel_768838850 {\n display: flex;\n flex-direction: column;\n gap: 18px;\n min-width: 0;\n}\n\n/* ── toolbar ───────────────────────────────────────────────────────────── */\n\n.toolbar_768838850 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\n/* Each picker (time range) lives in a framed group. */\n.group_768838850 {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n/* A range-preset chip: ghost Button inside the group, active state tinted\n with the interactive-active fill. */\n.segItem_768838850 {\n border-radius: 6px;\n}\n\n.segItem_768838850:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.segActive_768838850,\n.segActive_768838850:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n color: var(--dsw-alias-label-primary);\n}\n\n.customRange_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dateInput_768838850 {\n width: 150px;\n flex: 0 0 auto;\n}\n\n.customSep_768838850 {\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Keep the remaining toolbar action at the trailing edge. */\n.refresh_768838850 {\n flex: 0 0 auto;\n margin-left: auto;\n}\n\n/* ── states ────────────────────────────────────────────────────────────── */\n\n.errorBanner_768838850 {\n padding: 10px 14px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n color: var(--dsw-alias-state-warn-label);\n font-size: 13px;\n}\n\n.loading_768838850 {\n padding: 24px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n}\n\n.empty_768838850 {\n padding: 24px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n}\n\n/* ── numeric cards ─────────────────────────────────────────────────────── */\n\n/* The two long-valued cards bookend the row — total tokens leads it, the top\n model closes it — each on a wider track than the four short numerics\n between them. At the Plugins page's 960px content width that is six columns\n on one row. */\n.cards_768838850 {\n display: grid;\n grid-template-columns: 1.3fr repeat(4, 1fr) 1.3fr;\n gap: 10px;\n}\n\n/* Below the width where six cards hold their own, the grid folds to four\n tracks and the two long cards take two each: they keep their extra width\n instead of degrading to the numerics' size, and each row still fills. */\n@media (max-width: 720px) {\n .cards_768838850 {\n grid-template-columns: repeat(4, 1fr);\n }\n\n .cards_768838850 > :first-child,\n .cards_768838850 > :last-child {\n grid-column: span 2;\n }\n}\n\n@media (max-width: 430px) {\n .cards_768838850 {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n.card_768838850 {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n}\n\n.cardHead_768838850 {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.cardIcon_768838850 {\n color: var(--dsw-alias-label-tertiary);\n flex: 0 0 auto;\n}\n\n.cardLabel_768838850 {\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.cardValue_768838850 {\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n line-height: 1.15;\n}\n\n.cardValueSm_768838850 {\n font-size: 14px;\n}\n\n.cardValueWrap_768838850 {\n word-break: break-word;\n}\n\n/* The \"most used model\" card value: the model name over the provider, the\n same two-line anatomy as the per-model list rows (modelName/modelProvider\n classes), instead of the raw \"provider/model\" ref. */\n.cardModelLines_768838850 {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n/* A muted second line under a card value (the absolute cached-token volume\n under the hit-rate percentage). */\n.cardSub_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n line-height: 1.2;\n}\n\n/* ── sections / chart chrome ───────────────────────────────────────────── */\n\n.section_768838850 {\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n\n.sectionHead_768838850 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.sectionTitle_768838850 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.trendNote_768838850 {\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.chartWrap_768838850 {\n position: relative;\n min-width: 0;\n}\n\n/* ── heatmap ───────────────────────────────────────────────────────────── */\n\n.heatWrap_768838850 {\n position: relative;\n /* hidden, not auto: the SVG now fills the container exactly, and an\n auto scrollbar would flash when a fixed tooltip passes over the wrap's\n right edge. */\n overflow: hidden;\n min-width: 0;\n}\n\n.heatmap_768838850 {\n display: block;\n /* The 364 cells each keep their own two-layer drop-shadow chain (see\n `.heatCell_768838850`) — 728 blurs that the compositor would otherwise re-run on\n every scroll repaint of the tile, which is what made scrolling this panel\n drop frames while idle was clean (#4). Promoting the SVG onto its own\n compositing layer takes that rasterization off the scroll path and leaves\n the painted result untouched, so the cell depth stays as designed. Note\n that the promotion is what does the work: `contain: paint` on its own\n measured no better than no mitigation at all. */\n transform: translateZ(0);\n}\n\n.heatLegend_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.heatCell_768838850 {\n display: inline-block;\n border-radius: 3px;\n /* No width/height here: the SVG cells carry their size as geometry\n attributes (x/y/width/height), and a CSS width/height would override\n those on SVG2 rects — pinning every cell to a fixed px and widening the\n visual gap. The legend swatches carry the same size inline, so both\n always match the measured geom.size_768838850. */\n /* Depth at this size, WITHOUT a per-cell backdrop-filter: the cells are SVG\n geometry, where backdrop-filter is accepted by the CSSOM but changes\n nothing on paint (measured on 364 live cells: the screenshot came out\n byte-identical to no blur at all). What reads as a glass tile instead is\n the translucent fill below — it lets the panel's own glass bed through —\n carried by one hairline highlight over one soft under-shadow. */\n filter:\n drop-shadow(0 -0.5px 0 var(--dsw-heat-sheen))\n drop-shadow(0 0.5px 0.8px var(--dsw-heat-shade));\n}\n\n/* The five heat levels use DeepSeek's brand blue (#4D6BFE) as a fixed ramp,\n light and dark variants below — never the theme accent, which can read\n near-black against the base surface in dark mode. level0 (no data) is a\n neutral empty cell. The cells are SVG <rect>s, so the color rides `fill`\n (background is a no-op on SVG geometry) — but the legend swatches are HTML,\n so both declarations stay in step.\n Each level is mixed a little toward transparent so the bed shows through;\n the amount is deliberately modest, because the ramp is a DATA encoding and\n washing it out would cost readability. */\n.heatLevel0_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-0) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-0) 82%, transparent);\n}\n\n.heatLevel1_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-1) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-1) 82%, transparent);\n}\n\n.heatLevel2_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-2) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-2) 82%, transparent);\n}\n\n.heatLevel3_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-3) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-3) 82%, transparent);\n}\n\n.heatLevel4_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-4) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-4) 82%, transparent);\n}\n\n.heatLevel5_768838850 {\n fill: color-mix(in srgb, var(--dsw-heat-5) 82%, transparent);\n background: color-mix(in srgb, var(--dsw-heat-5) 82%, transparent);\n}\n\n/* ── daily trend ───────────────────────────────────────────────────────── */\n\n.chart_768838850 {\n display: block;\n}\n\n.grid_768838850 {\n stroke: var(--dsw-alias-border-l1);\n stroke-width: 1;\n}\n\n.axis_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n}\n\n.axisRate_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n}\n\n.bar_768838850 {\n transition: opacity 0.12s ease;\n /* The hover scaleX must pivot on the bar's own centre: SVG geometry\n transforms around the SVG origin unless transform-box anchors it to the\n element's fill box. Without this the scaled bar slides sideways and\n looks like it vanishes. */\n transform-box: fill-box;\n transform-origin: center;\n}\n\n.barDim_768838850 {\n opacity: 0.35;\n}\n\n.barHit_768838850 {\n fill: transparent;\n pointer-events: all;\n}\n\n/* cache hit-rate trend curve: DeepSeek's brand blue (#4D6BFE, the same hue\n family as the heatmap ramp) — a fixed brand ramp instead of the theme\n accent, which can read near-black in some themes. */\n.trend_768838850 {\n stroke: var(--dsw-trend-line);\n stroke-width: 2;\n opacity: 0.9;\n pointer-events: none;\n}\n\n.trendDot_768838850 {\n fill: var(--dsw-trend-line);\n stroke: var(--dsw-alias-bg-layer-1);\n stroke-width: 2;\n pointer-events: none;\n}\n\n.legend_768838850 {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.legendItem_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n cursor: default;\n}\n\n.legendSwatch_768838850 {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 2px;\n}\n\n.legendTrend_768838850 {\n background: var(--dsw-trend-line);\n width: 14px;\n height: 3px;\n border-radius: 2px;\n}\n\n/* ── per-model / per-provider chart + list ─────────────────────────────── */\n\n/* The ring is centred against the list beside it. The list's height is never\n tied to the chart, so expanding a row cannot stretch anything. */\n.models_768838850 {\n display: flex;\n gap: 16px;\n align-items: center;\n flex-wrap: wrap;\n min-width: 0;\n}\n\n/* The ring's own wrapper. Its diameter comes from the measured row width (see\n Donut.tsx_768838850), so it must never flex. */\n.donutWrap_768838850 {\n position: relative;\n flex: 0 0 auto;\n}\n\n.donut_768838850 {\n display: block;\n}\n\n.donutTrack_768838850 {\n stroke: var(--dsw-alias-bg-mask-1);\n}\n\n.donutSeg_768838850 {\n transition: stroke-width 0.12s ease, opacity 0.12s ease;\n}\n\n.donutDim_768838850 {\n opacity: 0.35;\n}\n\n.donutCenter_768838850 {\n fill: var(--dsw-alias-label-primary);\n font-size: 18px;\n font-weight: 600;\n}\n\n.donutLabel_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n/* Two ranking cards, side by side: MODELS left, PROVIDERS right. Both are\n always on screen (no lens/tab switch), so they share one row grid and\n stretch to a common height; a narrow pane stacks them instead. */\n.rankCards_768838850 {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n\n@media (max-width: 720px) {\n .rankCards_768838850 {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n/* Each card is the bordered surface its rows live in; the title is a recessed\n header strip, so the two rankings read as two equal panels. */\n.rankCard_768838850 {\n display: flex;\n flex-direction: column;\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n.rankCardTitle_768838850 {\n padding: 8px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n font-weight: 600;\n}\n\n.rankList_768838850 {\n list-style: none;\n margin: 0;\n padding: 0;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Every row opens a hover tip (the cross-dimension detail), so it carries the\n same hover affordance as the chart marks. */\n.modelRow_768838850:hover {\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 60%, transparent);\n}\n\n/* Position column: the row's rank inside its section. The Other bucket holds\n no single rank, so it renders an empty (but equally wide) cell. */\n.modelRank_768838850 {\n flex: 0 0 auto;\n min-width: 14px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.modelRow_768838850 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 13px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.modelRow_768838850:last-child {\n border-bottom: none;\n}\n\n/* \"Other\" aggregate row: the entire row is clickable to expand/collapse the\n detail list. The chevron button inside it stops propagation so\n double-firing is avoided. */\n.modelRowExpandable_768838850 {\n cursor: pointer;\n}\n\n/* \"Other\" row toggle (chevron) that expands the aggregated tail models. */\n.modelToggle_768838850 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n margin-right: 4px;\n border: none;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n vertical-align: middle;\n}\n\n/* Expanded detail rows: indented, slightly recessed. */\n.modelRowSub_768838850 {\n padding-left: 28px;\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent);\n font-size: 13px;\n}\n\n/* Second-level indent: a model row inside a provider that is itself folded\n into the Other bucket. Wider than .modelRowSub_768838850 so the nesting stays\n readable. Declared after it — same specificity, so this one wins. */\n.modelRowDeep_768838850 {\n padding-left: 48px;\n}\n\n/* \"Other\" details expand with an accordion: the wrapper is a grid whose\n single track animates 0fr → 1fr. */\n.modelOtherWrap_768838850 {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 0.25s ease;\n}\n\n.modelOtherOpen_768838850 {\n grid-template-rows: 1fr;\n}\n\n.modelOtherList_768838850 {\n overflow: hidden;\n min-height: 0;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/* Two-line row anatomy: [swatch] [name / provider] ……… [tokens / share].\n The name block takes the free width; the values block hugs the right. */\n.modelId_768838850 {\n display: flex;\n flex-direction: column;\n gap: 1px;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.modelValues_768838850 {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 1px;\n flex: 0 0 auto;\n}\n\n.modelName_768838850 {\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.modelProvider_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.modelTokens_768838850 {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.modelPct_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* ── tooltips ──────────────────────────────────────────────────────────── */\n\n/* Tooltips render as fixed-position portals on document.body_768838850 (see ChartTip),\n so they escape every ancestor's clipping and never overflow the settings\n panel: ChartTip clamps each tip inside the panel bounds and flips it above\n the hovered element when there is no room below. The plate follows the\n active theme: the DSH overlay token is a light gray in light mode and a\n dark gray under body[data-ds-dark-theme], so the tip reads correctly in\n both instead of staying dark in light mode. */\n.tip_768838850 {\n position: fixed;\n /* The tip is portaled to document.body_768838850, so it competes with the settings\n modal itself (z-index 1000) for the top layer: 100 would sit under the\n modal and the tip would be invisible. 1100 matches the official\n toast/portaled-menu layer, which is exactly the \"over a modal\" tier. */\n z-index: 1100;\n pointer-events: none;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-overlay);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n box-shadow: 0 2px 12px var(--dsw-alias-bg-mask-2);\n white-space: nowrap;\n}\n\n.tipTitle_768838850 {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n/* A provider row's raw route id, kept out of the row label but reachable on\n hover (the row shows the display name). */\n.tipSubtitle_768838850 {\n margin: -2px 0 4px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* Breakdown heading inside a row tip: names the cross-dimension list under it\n (\"By provider\" under a model, \"By model\" under a provider). */\n.tipLabel_768838850 {\n margin-bottom: 2px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* The breakdown line pushes its volume to the right edge of the plate. */\n.tipName_768838850 {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.tipValue_768838850 {\n margin-left: auto;\n padding-left: 12px;\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.tipRow_768838850 {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n\n/* Hover tooltip breakdown rows for the \"Other\" aggregate: indented, muted,\n same gray swatch as the Other step. */\n.tipRowOther_768838850 {\n padding-left: 8px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.tipBreakdown_768838850 {\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 4px;\n padding-top: 4px;\n}\n\n.foot_768838850 {\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── chart palette ──────────────────────────────────────────────────────── */\n\n/* Light variant (the theme's default): model colours are the reasonix\n token-total palette (rank order by token volume + gray Other) with a\n white veil mixed in (color-mix 30% white) — the raw hues read dark and\n heavy on the light card surface, and the veil lifts lightness and dulls\n saturation the way a translucent white overlay would, keeping the hues\n colourful instead of muddy. The heatmap ramp is DeepSeek's brand blue\n (#4D6BFE) stepping from a pale tint to the full blue; the hit-rate curve\n rides the same brand hue.\n Ranks 6-10 extend the reasonix five to ten (this plugin's addition): each\n is picked to keep the ten hues apart on both themes. */\n.panel_768838850 {\n --dsw-heat-0: #ebedf0;\n --dsw-heat-1: #dbe3ff;\n --dsw-heat-2: #b7c5ff;\n --dsw-heat-3: #8ea4ff;\n --dsw-heat-4: #6884ff;\n --dsw-heat-5: #4d6bfe;\n /* Heatmap cell depth: a highlight above, a shade below. Light trays need\n the stronger pair; the dark tray below lifts the shade instead. */\n --dsw-heat-sheen: rgb(255 255 255 / 0.55);\n --dsw-heat-shade: rgb(0 0 0 / 0.20);\n --dsw-trend-line: #4d6bfe;\n --dsw-chart-1: color-mix(in srgb, #0576ff 70%, white);\n --dsw-chart-2: color-mix(in srgb, #2f6f37 70%, white);\n --dsw-chart-3: color-mix(in srgb, #c46212 70%, white);\n --dsw-chart-4: color-mix(in srgb, #975bf1 70%, white);\n --dsw-chart-5: color-mix(in srgb, #d34591 70%, white);\n --dsw-chart-6: color-mix(in srgb, #0d8a8a 70%, white);\n --dsw-chart-7: color-mix(in srgb, #b3341f 70%, white);\n --dsw-chart-8: color-mix(in srgb, #7a9018 70%, white);\n --dsw-chart-9: color-mix(in srgb, #4b3fd0 70%, white);\n --dsw-chart-10: color-mix(in srgb, #d4a017 70%, white);\n --dsw-chart-other: color-mix(in srgb, #576270 70%, white);\n /* Provider series: a separate five-hue set for the provider section, so a\n provider never wears a model's colour. */\n --dsw-provider-1: color-mix(in srgb, #1f7a8c 70%, white);\n --dsw-provider-2: color-mix(in srgb, #6a8e3a 70%, white);\n --dsw-provider-3: color-mix(in srgb, #a15c2f 70%, white);\n --dsw-provider-4: color-mix(in srgb, #7b5ea7 70%, white);\n --dsw-provider-5: color-mix(in srgb, #b03a5b 70%, white);\n --dsw-provider-other: color-mix(in srgb, #576270 70%, white);\n}\n\n/* Dark variant: the same reasonix hues under a thicker white veil\n (35% — dark surfaces need the extra lift), so segments read soft and\n bright instead of sinking into the background. */\n@media (prefers-color-scheme: dark) {\n .panel_768838850 {\n --dsw-heat-0: #21262d;\n --dsw-heat-1: #2f4bd0;\n --dsw-heat-2: #4d6bfe;\n --dsw-heat-3: #6e8bff;\n --dsw-heat-4: #93aaff;\n --dsw-heat-5: #c4d0ff;\n /* Same depth pair, recalibrated: a dark tray swallows a light shade. */\n --dsw-heat-sheen: rgb(255 255 255 / 0.30);\n --dsw-heat-shade: rgb(0 0 0 / 0.50);\n --dsw-trend-line: #7d95ff;\n --dsw-chart-1: color-mix(in srgb, #0576ff 65%, white);\n --dsw-chart-2: color-mix(in srgb, #2f6f37 65%, white);\n --dsw-chart-3: color-mix(in srgb, #c46212 65%, white);\n --dsw-chart-4: color-mix(in srgb, #975bf1 65%, white);\n --dsw-chart-5: color-mix(in srgb, #d34591 65%, white);\n --dsw-chart-6: color-mix(in srgb, #0d8a8a 65%, white);\n --dsw-chart-7: color-mix(in srgb, #b3341f 65%, white);\n --dsw-chart-8: color-mix(in srgb, #7a9018 65%, white);\n --dsw-chart-9: color-mix(in srgb, #4b3fd0 65%, white);\n --dsw-chart-10: color-mix(in srgb, #d4a017 65%, white);\n --dsw-chart-other: color-mix(in srgb, #576270 65%, white);\n --dsw-provider-1: color-mix(in srgb, #1f7a8c 65%, white);\n --dsw-provider-2: color-mix(in srgb, #6a8e3a 65%, white);\n --dsw-provider-3: color-mix(in srgb, #a15c2f 65%, white);\n --dsw-provider-4: color-mix(in srgb, #7b5ea7 65%, white);\n --dsw-provider-5: color-mix(in srgb, #b03a5b 65%, white);\n --dsw-provider-other: color-mix(in srgb, #576270 65%, white);\n }\n}\n";
|
|
29430
|
+
const css$2 = "/**\n * Usage statistics panel styles — a DSH-native CSS Module. Every visual\n * value rides the DSH semantic tokens (--dsw-alias-* colors, --dsw-font-*\n * typography, --ds-* motion); no static palette values, no theme selectors.\n * The heatmap's 5 levels come from the brand accent via color-mix; model\n * colours are the fixed --dsw-chart-1..5 series (rank order by token\n * volume) plus the gray --dsw-chart-other step — the reasonix token-total\n * palette, with a lifted dark variant defined below.\n */\n\n/* The panel as a standalone main panel. It gets the same 960px content column\n the Plugins page gives it — the declarations below mirror the Plugins page's\n own `.page_768838850` so both entries render identically. Without the cap the charts\n would stretch to whatever the centre column happens to be.\n\n One deliberate difference from that page: its 28px top inset is NOT part of\n this container. A main panel starts at the window's top-left corner, so an\n inset painted by this scrolling container is a strip above the content that\n no window-drag region covers; `.backRow_768838850` below owns the inset instead, which\n is the shape the shipped page plugins use for the same reason\n (`ui-plugin-manager`'s `.pageHead_768838850`/`.detailTop_768838850` and `ui-schedule`'s page\n head). The rendered position is unchanged. */\n.page_768838850 {\n display: flex;\n flex-direction: column;\n align-items: center;\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n padding: 0 clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n\n.page_768838850 > * {\n width: 100%;\n max-width: 960px;\n}\n\n/* The standalone panel's back control. Only the main panel renders this seat —\n the Plugins page entry already sits inside the page's own chrome, which\n draws its own crumb back to the bundle list. The spacing matches the gap the\n panel keeps between its own sections.\n\n The row also carries the panel's former 28px top inset: it starts at the\n window's top edge, so the whole strip above the back control drags the\n window (Electron app-regions are geometry, not stacking, so a strip owned by\n the scrolling container would subtract itself from the drag row beneath\n it). */\n.backRow_768838850 {\n display: flex;\n box-sizing: border-box;\n padding-top: 28px;\n margin-bottom: 18px;\n}\n\n.back_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.panel_768838850 {\n display: flex;\n flex-direction: column;\n gap: 18px;\n min-width: 0;\n}\n\n/* ── toolbar ───────────────────────────────────────────────────────────── */\n\n.toolbar_768838850 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\n/* Each picker (time range) lives in a framed group. */\n.group_768838850 {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 9px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n/* A range-preset chip: ghost Button inside the group, active state tinted\n with the brand accent (same blue as the section bars and the total card). */\n.segItem_768838850 {\n border-radius: 6px;\n}\n\n.segItem_768838850:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.segActive_768838850,\n.segActive_768838850:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-tone-1) 14%, transparent);\n color: var(--dsw-tone-1);\n font-weight: 600;\n}\n\n.customRange_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.dateInput_768838850 {\n width: 150px;\n flex: 0 0 auto;\n}\n\n.customSep_768838850 {\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Trailing toolbar edge: the \"as of\" stamp sits immediately left of the\n refresh action (it stamps the very data that button refreshes). */\n.toolbarEnd_768838850 {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 10px;\n}\n\n.asOf_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n white-space: nowrap;\n}\n\n/* Keep the remaining toolbar action at the trailing edge. */\n.refresh_768838850 {\n flex: 0 0 auto;\n}\n\n/* ── states ────────────────────────────────────────────────────────────── */\n\n.errorBanner_768838850 {\n padding: 10px 14px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n color: var(--dsw-alias-state-warn-label);\n font-size: 13px;\n}\n\n.loading_768838850 {\n padding: 24px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n}\n\n.empty_768838850 {\n padding: 24px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n}\n\n/* ── numeric cards ─────────────────────────────────────────────────────── */\n\n/* Six equal tracks: the overview row is one grid of six identical cards\n (long value or not), so no card crowds its neighbours. At the Plugins\n page's 960px content width that is six columns on one row. */\n.cards_768838850 {\n display: grid;\n grid-template-columns: repeat(6, minmax(0, 1fr));\n gap: 10px;\n}\n\n/* Below the width where six cards hold their own, the grid folds to three\n columns (two rows of three), then to two (three rows of two). */\n@media (max-width: 720px) {\n .cards_768838850 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n}\n\n@media (max-width: 430px) {\n .cards_768838850 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n\n/* Each stat card wears one accent hue (tone 1..6 → --dsw-tone-1..6): a 3px\n accent bar on the left edge, the icon in a tinted badge, and a whisper of\n the hue washing the top-left corner — the overview row reads in colour\n instead of one gray register. --card-accent is set by the .cardToneN_768838850 map\n below; content sits above the wash via a z-index layer. */\n.card_768838850 {\n --card-accent: var(--dsw-alias-label-tertiary);\n position: relative;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 14px 14px 12px 18px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: 0 1px 2px var(--dsw-alias-bg-mask-2);\n min-width: 0;\n}\n\n.card_768838850::before {\n content: \"\";\n position: absolute;\n left: 0;\n top: 12px;\n bottom: 12px;\n width: 3px;\n border-radius: 0 3px 3px 0;\n background: var(--card-accent);\n}\n\n.card_768838850::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(135deg, color-mix(in srgb, var(--card-accent) 7%, transparent), transparent 60%);\n pointer-events: none;\n}\n\n/* Content must paint above the corner wash (the ::after pseudo is the last\n child, so it wins a same-layer paint order). */\n.card_768838850 > * {\n position: relative;\n z-index: 1;\n}\n\n.cardTone1_768838850 { --card-accent: var(--dsw-tone-1); }\n.cardTone2_768838850 { --card-accent: var(--dsw-tone-2); }\n.cardTone3_768838850 { --card-accent: var(--dsw-tone-3); }\n.cardTone4_768838850 { --card-accent: var(--dsw-tone-4); }\n.cardTone5_768838850 { --card-accent: var(--dsw-tone-5); }\n.cardTone6_768838850 { --card-accent: var(--dsw-tone-6); }\n\n.cardHead_768838850 {\n display: flex;\n align-items: center;\n gap: 7px;\n}\n\n/* Icon badge: the accent hue at 12% behind its own glyph. */\n.cardIcon_768838850 {\n box-sizing: border-box;\n width: 24px;\n height: 24px;\n padding: 5px;\n border-radius: 7px;\n color: var(--card-accent);\n background: color-mix(in srgb, var(--card-accent) 12%, transparent);\n flex: 0 0 auto;\n}\n\n.cardLabel_768838850 {\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Values never wrap: a wrapped number defeats FitText's measurement (the\n wrapped line is exactly as wide as its box) and reads as a layout break.\n Oversized text shrinks instead — see FitText in TokenTotalPanel.tsx_768838850. */\n.cardValue_768838850 {\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n line-height: 1.15;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.cardValueSm_768838850 {\n font-size: 14px;\n}\n\n.cardValueWrap_768838850 {\n word-break: break-word;\n}\n\n/* The \"most used model\" card value: the model name over the provider, the\n same two-line anatomy as the per-model list rows (modelName/modelProvider\n classes), instead of the raw \"provider/model\" ref. */\n.cardModelLines_768838850 {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n/* A muted second line under a card value (the absolute cached-token volume\n under the hit-rate percentage). Same no-wrap rule as the value above. */\n.cardSub_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n}\n\n/* ── sections / chart chrome ───────────────────────────────────────────── */\n\n.section_768838850 {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n padding: 14px 16px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: 0 1px 2px var(--dsw-alias-bg-mask-2);\n}\n\n.sectionHead_768838850 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n/* Section titles carry the same brand accent bar as the stat cards' tone\n language, so every card heading is anchored the same way. */\n.sectionTitle_768838850 {\n margin: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.sectionTitle_768838850::before {\n content: \"\";\n width: 3px;\n height: 13px;\n border-radius: 2px;\n background: var(--dsw-tone-1);\n flex: 0 0 auto;\n}\n\n/* Header meta cluster right of a section title (trend cap note + curve\n legend). Kept as one wrap-friendly row on a common baseline. */\n.headMeta_768838850 {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.trendNote_768838850 {\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Heatmap and the token trend share ONE row — one card each, left and right.\n Both columns stretch to a common height; the heatmap centres its SVG in the\n taller partner. Below 720px the pair folds to a single column. */\n.duo_768838850 {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 12px;\n align-items: stretch;\n}\n\n@media (max-width: 720px) {\n .duo_768838850 {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n.chartWrap_768838850 {\n position: relative;\n min-width: 0;\n}\n\n/* ── heatmap ───────────────────────────────────────────────────────────── */\n\n.heatWrap_768838850 {\n position: relative;\n /* hidden, not auto: the SVG now fills the container exactly, and an\n auto scrollbar would flash when a fixed tooltip passes over the wrap's\n right edge. */\n overflow: hidden;\n min-width: 0;\n /* In the duo row the heatmap card shares height with the trend card; the\n grid centres itself in whatever slack the partner leaves. */\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n}\n\n.heatmap_768838850 {\n display: block;\n margin: 0 auto;\n /* The ~364 day cells are flat SVG rects (no per-cell filter), so there is no\n blur chain left to re-rasterize — but promoting the SVG onto its own\n compositing layer still keeps the whole grid off the scroll repaint path,\n which is what made scrolling this panel drop frames while idle was clean\n (#4). Cheap and invisible, so it stays. Note that the promotion is what\n does the work: `contain: paint` on its own measured no better than no\n mitigation at all. */\n transform: translateZ(0);\n}\n\n.heatLegend_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Axis labels drawn inside the heatmap SVG: the weekday column on the left and\n the month strip along the bottom. SVG <text> takes `fill`, not `color`. */\n.heatAxis_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n.heatCell_768838850 {\n display: inline-block;\n border-radius: 2px;\n /* No width/height here: the SVG cells carry their size as geometry\n attributes (x/y/width/height), and a CSS width/height would override\n those on SVG2 rects — pinning every cell to a fixed px and widening the\n visual gap. The legend swatches carry the same size inline, so both\n always match the measured geom.size_768838850.\n No filter either: the reference chart paints each tile as a flat, matte\n rounded square, so a sheen/under-shadow would read as a smudge at this\n cell size rather than depth. */\n}\n\n/* The five heat levels ride a fixed emerald ramp sampled pixel-for-pixel from\n the reference chart (a GitHub-style green scale), with a darker variant for\n dark mode below — never the theme accent, which can read near-black against\n the base surface in dark mode. level0 (no data) is a neutral empty cell.\n The fills are fully opaque: the reference tiles are solid, and mixing toward\n transparent (the previous glass treatment) lightened every step and broke\n the sampled ramp. The cells are SVG <rect>s, so the color rides `fill`\n (background is a no-op on SVG geometry) — but the legend swatches are HTML,\n so both declarations stay in step. */\n.heatLevel0_768838850 {\n fill: var(--dsw-heat-0);\n background: var(--dsw-heat-0);\n}\n\n.heatLevel1_768838850 {\n fill: var(--dsw-heat-1);\n background: var(--dsw-heat-1);\n}\n\n.heatLevel2_768838850 {\n fill: var(--dsw-heat-2);\n background: var(--dsw-heat-2);\n}\n\n.heatLevel3_768838850 {\n fill: var(--dsw-heat-3);\n background: var(--dsw-heat-3);\n}\n\n.heatLevel4_768838850 {\n fill: var(--dsw-heat-4);\n background: var(--dsw-heat-4);\n}\n\n.heatLevel5_768838850 {\n fill: var(--dsw-heat-5);\n background: var(--dsw-heat-5);\n}\n\n/* ── daily trend ───────────────────────────────────────────────────────── */\n\n.chart_768838850 {\n display: block;\n}\n\n.grid_768838850 {\n stroke: var(--dsw-alias-border-l1);\n stroke-width: 1;\n}\n\n.axis_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n}\n\n.axisRate_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n}\n\n.bar_768838850 {\n transition: opacity 0.12s ease;\n /* The hover scaleX must pivot on the bar's own centre: SVG geometry\n transforms around the SVG origin unless transform-box anchors it to the\n element's fill box. Without this the scaled bar slides sideways and\n looks like it vanishes. */\n transform-box: fill-box;\n transform-origin: center;\n}\n\n.barDim_768838850 {\n opacity: 0.35;\n}\n\n.barHit_768838850 {\n fill: transparent;\n pointer-events: all;\n}\n\n/* cache hit-rate trend curve: DeepSeek's brand blue (#4D6BFE, the same hue\n family as the heatmap ramp) — a fixed brand ramp instead of the theme\n accent, which can read near-black in some themes. */\n.trend_768838850 {\n stroke: var(--dsw-trend-line);\n stroke-width: 2;\n opacity: 0.9;\n pointer-events: none;\n}\n\n.trendDot_768838850 {\n fill: var(--dsw-trend-line);\n stroke: var(--dsw-alias-bg-layer-1);\n stroke-width: 2;\n pointer-events: none;\n}\n\n.legend_768838850 {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.legendItem_768838850 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n cursor: default;\n}\n\n.legendSwatch_768838850 {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 2px;\n}\n\n.legendTrend_768838850 {\n background: var(--dsw-trend-line);\n width: 14px;\n height: 3px;\n border-radius: 2px;\n}\n\n/* ── per-model / per-provider chart + list ─────────────────────────────── */\n\n/* The ring is centred against the list beside it. The list's height is never\n tied to the chart, so expanding a row cannot stretch anything. */\n.models_768838850 {\n display: flex;\n gap: 16px;\n align-items: center;\n flex-wrap: wrap;\n min-width: 0;\n}\n\n/* The ring's own wrapper. Its diameter comes from the measured row width (see\n Donut.tsx_768838850), so it must never flex. */\n.donutWrap_768838850 {\n position: relative;\n flex: 0 0 auto;\n}\n\n.donut_768838850 {\n display: block;\n}\n\n.donutTrack_768838850 {\n stroke: var(--dsw-alias-bg-mask-1);\n}\n\n.donutSeg_768838850 {\n transition: stroke-width 0.12s ease, opacity 0.12s ease;\n}\n\n.donutDim_768838850 {\n opacity: 0.35;\n}\n\n.donutCenter_768838850 {\n fill: var(--dsw-alias-label-primary);\n font-size: 18px;\n font-weight: 600;\n}\n\n.donutLabel_768838850 {\n fill: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n/* Two ranking cards, side by side: MODELS left, PROVIDERS right. Both are\n always on screen (no lens/tab switch), so they share one row grid and\n stretch to a common height; a narrow pane stacks them instead. */\n.rankCards_768838850 {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n\n@media (max-width: 720px) {\n .rankCards_768838850 {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n/* Each card is the bordered surface its rows live in; the title is a recessed\n header strip, so the two rankings read as two equal panels. */\n.rankCard_768838850 {\n display: flex;\n flex-direction: column;\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n.rankCardTitle_768838850 {\n padding: 8px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n font-weight: 600;\n}\n\n.rankList_768838850 {\n list-style: none;\n margin: 0;\n padding: 0;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Every row opens a hover tip (the cross-dimension detail), so it carries the\n same hover affordance as the chart marks. */\n.modelRow_768838850:hover {\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 60%, transparent);\n}\n\n/* Position column: the row's rank inside its section. The Other bucket holds\n no single rank, so it renders an empty (but equally wide) cell. */\n.modelRank_768838850 {\n flex: 0 0 auto;\n min-width: 14px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.modelRow_768838850 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 13px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.modelRow_768838850:last-child {\n border-bottom: none;\n}\n\n/* Share bar under a rank row's name: the row's slice of the section total,\n drawn in the row's own colour (a 3px track, like the design mock). */\n.rowTrack_768838850 {\n height: 3px;\n margin-top: 2px;\n border-radius: 2px;\n background: var(--dsw-alias-border-l1);\n overflow: hidden;\n}\n\n.rowFill_768838850 {\n display: block;\n height: 3px;\n border-radius: 2px;\n}\n\n/* Top-3 ranks read as a podium: the ordinal takes the warm accent instead of\n the muted gray the tail ranks keep. */\n.rankList_768838850 > .modelRow_768838850:nth-child(-n + 3) .modelRank_768838850 {\n color: var(--dsw-rank-top);\n font-weight: 700;\n}\n\n/* \"Other\" aggregate row: the entire row is clickable to expand/collapse the\n detail list. The chevron button inside it stops propagation so\n double-firing is avoided. */\n.modelRowExpandable_768838850 {\n cursor: pointer;\n}\n\n/* \"Other\" row toggle (chevron) that expands the aggregated tail models. */\n.modelToggle_768838850 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n margin-right: 4px;\n border: none;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n vertical-align: middle;\n}\n\n/* Expanded detail rows: indented, slightly recessed. */\n.modelRowSub_768838850 {\n padding-left: 28px;\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent);\n font-size: 13px;\n}\n\n/* Second-level indent: a model row inside a provider that is itself folded\n into the Other bucket. Wider than .modelRowSub_768838850 so the nesting stays\n readable. Declared after it — same specificity, so this one wins. */\n.modelRowDeep_768838850 {\n padding-left: 48px;\n}\n\n/* \"Other\" details expand with an accordion: the wrapper is a grid whose\n single track animates 0fr → 1fr. */\n.modelOtherWrap_768838850 {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 0.25s ease;\n}\n\n.modelOtherOpen_768838850 {\n grid-template-rows: 1fr;\n}\n\n.modelOtherList_768838850 {\n overflow: hidden;\n min-height: 0;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/* Two-line row anatomy: [swatch] [name / provider] ……… [tokens / share].\n The name block takes the free width; the values block hugs the right. */\n.modelId_768838850 {\n display: flex;\n flex-direction: column;\n gap: 1px;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.modelValues_768838850 {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 1px;\n flex: 0 0 auto;\n}\n\n.modelName_768838850 {\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.modelProvider_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.modelTokens_768838850 {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.modelPct_768838850 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* ── tooltips ──────────────────────────────────────────────────────────── */\n\n/* Tooltips render as fixed-position portals on document.body_768838850 (see ChartTip),\n so they escape every ancestor's clipping and never overflow the settings\n panel: ChartTip clamps each tip inside the panel bounds and flips it above\n the hovered element when there is no room below. The plate follows the\n active theme: the DSH overlay token is a light gray in light mode and a\n dark gray under body[data-ds-dark-theme], so the tip reads correctly in\n both instead of staying dark in light mode. */\n.tip_768838850 {\n position: fixed;\n /* The tip is portaled to document.body_768838850, so it competes with the settings\n modal itself (z-index 1000) for the top layer: 100 would sit under the\n modal and the tip would be invisible. 1100 matches the official\n toast/portaled-menu layer, which is exactly the \"over a modal\" tier. */\n z-index: 1100;\n pointer-events: none;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-overlay);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n box-shadow: 0 2px 12px var(--dsw-alias-bg-mask-2);\n white-space: nowrap;\n}\n\n.tipTitle_768838850 {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n/* A provider row's raw route id, kept out of the row label but reachable on\n hover (the row shows the display name). */\n.tipSubtitle_768838850 {\n margin: -2px 0 4px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* Breakdown heading inside a row tip: names the cross-dimension list under it\n (\"By provider\" under a model, \"By model\" under a provider). */\n.tipLabel_768838850 {\n margin-bottom: 2px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* The breakdown line pushes its volume to the right edge of the plate. */\n.tipName_768838850 {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.tipValue_768838850 {\n margin-left: auto;\n padding-left: 12px;\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.tipRow_768838850 {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n\n/* Hover tooltip breakdown rows for the \"Other\" aggregate: indented, muted,\n same gray swatch as the Other step. */\n.tipRowOther_768838850 {\n padding-left: 8px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.tipBreakdown_768838850 {\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 4px;\n padding-top: 4px;\n}\n\n/* ── chart palette ──────────────────────────────────────────────────────── */\n\n/* Light variant (the theme's default): model colours are the reasonix\n token-total palette (rank order by token volume + gray Other) with a\n white veil mixed in (color-mix 30% white) — the raw hues read dark and\n heavy on the light card surface, and the veil lifts lightness and dulls\n saturation the way a translucent white overlay would, keeping the hues\n colourful instead of muddy. The heatmap ramp is a fixed emerald green\n scale (GitHub-style activity heatmap), stepping from a near-white tint to\n a deep green; the hit-rate curve rides the brand blue.\n Ranks 6-10 extend the reasonix five to ten (this plugin's addition): each\n is picked to keep the ten hues apart on both themes.\n The palette must ALSO live on :root: tooltips are fixed portals on\n document.body_768838850 (see ChartTip), outside the panel subtree, and the inline\n var(--dsw-chart-N) swatches they carry only resolve from :root. */\n:root, .panel_768838850 {\n --dsw-heat-0: #f4f5f5;\n --dsw-heat-1: #daf6ea;\n --dsw-heat-2: #c7f0df;\n --dsw-heat-3: #82edc0;\n --dsw-heat-4: #00b96b;\n --dsw-heat-5: #00663b;\n --dsw-trend-line: #4d6bfe;\n /* Overview-card tone accents (stat cards 1..6) and the podium ordinal.\n Six hues, each picked to sit apart from both the chart set and the heat\n ramp, at full strength so 12% badge tints stay visible on white. */\n --dsw-tone-1: #4d6bfe;\n --dsw-tone-2: #00b96b;\n --dsw-tone-3: #e07b39;\n --dsw-tone-4: #975bf1;\n --dsw-tone-5: #0d8a8a;\n --dsw-tone-6: #d34591;\n --dsw-rank-top: #c46212;\n --dsw-chart-1: color-mix(in srgb, #0576ff 70%, white);\n --dsw-chart-2: color-mix(in srgb, #2f6f37 70%, white);\n --dsw-chart-3: color-mix(in srgb, #c46212 70%, white);\n --dsw-chart-4: color-mix(in srgb, #975bf1 70%, white);\n --dsw-chart-5: color-mix(in srgb, #d34591 70%, white);\n --dsw-chart-6: color-mix(in srgb, #0d8a8a 70%, white);\n --dsw-chart-7: color-mix(in srgb, #b3341f 70%, white);\n --dsw-chart-8: color-mix(in srgb, #7a9018 70%, white);\n --dsw-chart-9: color-mix(in srgb, #4b3fd0 70%, white);\n --dsw-chart-10: color-mix(in srgb, #d4a017 70%, white);\n --dsw-chart-other: color-mix(in srgb, #576270 70%, white);\n /* Provider series: a separate five-hue set for the provider section, so a\n provider never wears a model's colour. */\n --dsw-provider-1: color-mix(in srgb, #1f7a8c 70%, white);\n --dsw-provider-2: color-mix(in srgb, #6a8e3a 70%, white);\n --dsw-provider-3: color-mix(in srgb, #a15c2f 70%, white);\n --dsw-provider-4: color-mix(in srgb, #7b5ea7 70%, white);\n --dsw-provider-5: color-mix(in srgb, #b03a5b 70%, white);\n --dsw-provider-other: color-mix(in srgb, #576270 70%, white);\n}\n\n/* Dark variant: the same reasonix hues under a thicker white veil\n (35% — dark surfaces need the extra lift), so segments read soft and\n bright instead of sinking into the background. */\n@media (prefers-color-scheme: dark) {\n :root, .panel_768838850 {\n --dsw-heat-0: #1b211e;\n --dsw-heat-1: #123a2a;\n --dsw-heat-2: #17553a;\n --dsw-heat-3: #1c7a4e;\n --dsw-heat-4: #2fbf7c;\n --dsw-heat-5: #7ef0b4;\n --dsw-trend-line: #7d95ff;\n /* Dark tones are lifted versions of the light six: same identity hue,\n brighter so 16% badge tints and 3px bars read on dark surfaces. */\n --dsw-tone-1: #7d95ff;\n --dsw-tone-2: #2fbf7c;\n --dsw-tone-3: #e89a5c;\n --dsw-tone-4: #b18cff;\n --dsw-tone-5: #3bc5c5;\n --dsw-tone-6: #f076ac;\n --dsw-rank-top: #e08543;\n --dsw-chart-1: color-mix(in srgb, #0576ff 65%, white);\n --dsw-chart-2: color-mix(in srgb, #2f6f37 65%, white);\n --dsw-chart-3: color-mix(in srgb, #c46212 65%, white);\n --dsw-chart-4: color-mix(in srgb, #975bf1 65%, white);\n --dsw-chart-5: color-mix(in srgb, #d34591 65%, white);\n --dsw-chart-6: color-mix(in srgb, #0d8a8a 65%, white);\n --dsw-chart-7: color-mix(in srgb, #b3341f 65%, white);\n --dsw-chart-8: color-mix(in srgb, #7a9018 65%, white);\n --dsw-chart-9: color-mix(in srgb, #4b3fd0 65%, white);\n --dsw-chart-10: color-mix(in srgb, #d4a017 65%, white);\n --dsw-chart-other: color-mix(in srgb, #576270 65%, white);\n --dsw-provider-1: color-mix(in srgb, #1f7a8c 65%, white);\n --dsw-provider-2: color-mix(in srgb, #6a8e3a 65%, white);\n --dsw-provider-3: color-mix(in srgb, #a15c2f 65%, white);\n --dsw-provider-4: color-mix(in srgb, #7b5ea7 65%, white);\n --dsw-provider-5: color-mix(in srgb, #b03a5b 65%, white);\n --dsw-provider-other: color-mix(in srgb, #576270 65%, white);\n }\n\n /* Dark surfaces swallow pale tints, so the card accents step up: badge\n 12% → 16%, corner wash 7% → 10%. */\n .cardIcon_768838850 {\n background: color-mix(in srgb, var(--card-accent) 16%, transparent);\n }\n\n .card_768838850::after {\n background: linear-gradient(135deg, color-mix(in srgb, var(--card-accent) 10%, transparent), transparent 60%);\n }\n}\n";
|
|
29410
29431
|
const tagId$2 = "dsh-token-total/TokenTotalPanel.module.css";
|
|
29411
29432
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
|
|
29412
29433
|
const tag = document.createElement("style");
|
|
@@ -29429,15 +29450,25 @@ window.__ModuleLoader__.load({
|
|
|
29429
29450
|
"customRange": "customRange_768838850",
|
|
29430
29451
|
"dateInput": "dateInput_768838850",
|
|
29431
29452
|
"customSep": "customSep_768838850",
|
|
29453
|
+
"toolbarEnd": "toolbarEnd_768838850",
|
|
29454
|
+
"asOf": "asOf_768838850",
|
|
29432
29455
|
"refresh": "refresh_768838850",
|
|
29433
29456
|
"errorBanner": "errorBanner_768838850",
|
|
29434
29457
|
"loading": "loading_768838850",
|
|
29435
29458
|
"empty": "empty_768838850",
|
|
29436
29459
|
"cards": "cards_768838850",
|
|
29460
|
+
"cardToneN": "cardToneN_768838850",
|
|
29437
29461
|
"card": "card_768838850",
|
|
29462
|
+
"cardTone1": "cardTone1_768838850",
|
|
29463
|
+
"cardTone2": "cardTone2_768838850",
|
|
29464
|
+
"cardTone3": "cardTone3_768838850",
|
|
29465
|
+
"cardTone4": "cardTone4_768838850",
|
|
29466
|
+
"cardTone5": "cardTone5_768838850",
|
|
29467
|
+
"cardTone6": "cardTone6_768838850",
|
|
29438
29468
|
"cardHead": "cardHead_768838850",
|
|
29439
29469
|
"cardIcon": "cardIcon_768838850",
|
|
29440
29470
|
"cardLabel": "cardLabel_768838850",
|
|
29471
|
+
"tsx": "tsx_768838850",
|
|
29441
29472
|
"cardValue": "cardValue_768838850",
|
|
29442
29473
|
"cardValueSm": "cardValueSm_768838850",
|
|
29443
29474
|
"cardValueWrap": "cardValueWrap_768838850",
|
|
@@ -29446,12 +29477,15 @@ window.__ModuleLoader__.load({
|
|
|
29446
29477
|
"section": "section_768838850",
|
|
29447
29478
|
"sectionHead": "sectionHead_768838850",
|
|
29448
29479
|
"sectionTitle": "sectionTitle_768838850",
|
|
29480
|
+
"headMeta": "headMeta_768838850",
|
|
29449
29481
|
"trendNote": "trendNote_768838850",
|
|
29482
|
+
"duo": "duo_768838850",
|
|
29450
29483
|
"chartWrap": "chartWrap_768838850",
|
|
29451
29484
|
"heatWrap": "heatWrap_768838850",
|
|
29452
29485
|
"heatmap": "heatmap_768838850",
|
|
29453
|
-
"heatCell": "heatCell_768838850",
|
|
29454
29486
|
"heatLegend": "heatLegend_768838850",
|
|
29487
|
+
"heatAxis": "heatAxis_768838850",
|
|
29488
|
+
"heatCell": "heatCell_768838850",
|
|
29455
29489
|
"size": "size_768838850",
|
|
29456
29490
|
"heatLevel0": "heatLevel0_768838850",
|
|
29457
29491
|
"heatLevel1": "heatLevel1_768838850",
|
|
@@ -29473,7 +29507,6 @@ window.__ModuleLoader__.load({
|
|
|
29473
29507
|
"legendSwatch": "legendSwatch_768838850",
|
|
29474
29508
|
"legendTrend": "legendTrend_768838850",
|
|
29475
29509
|
"models": "models_768838850",
|
|
29476
|
-
"tsx": "tsx_768838850",
|
|
29477
29510
|
"donutWrap": "donutWrap_768838850",
|
|
29478
29511
|
"donut": "donut_768838850",
|
|
29479
29512
|
"donutTrack": "donutTrack_768838850",
|
|
@@ -29487,6 +29520,8 @@ window.__ModuleLoader__.load({
|
|
|
29487
29520
|
"rankList": "rankList_768838850",
|
|
29488
29521
|
"modelRow": "modelRow_768838850",
|
|
29489
29522
|
"modelRank": "modelRank_768838850",
|
|
29523
|
+
"rowTrack": "rowTrack_768838850",
|
|
29524
|
+
"rowFill": "rowFill_768838850",
|
|
29490
29525
|
"modelRowExpandable": "modelRowExpandable_768838850",
|
|
29491
29526
|
"modelToggle": "modelToggle_768838850",
|
|
29492
29527
|
"modelRowSub": "modelRowSub_768838850",
|
|
@@ -29509,8 +29544,7 @@ window.__ModuleLoader__.load({
|
|
|
29509
29544
|
"tipValue": "tipValue_768838850",
|
|
29510
29545
|
"tipRow": "tipRow_768838850",
|
|
29511
29546
|
"tipRowOther": "tipRowOther_768838850",
|
|
29512
|
-
"tipBreakdown": "tipBreakdown_768838850"
|
|
29513
|
-
"foot": "foot_768838850"
|
|
29547
|
+
"tipBreakdown": "tipBreakdown_768838850"
|
|
29514
29548
|
};
|
|
29515
29549
|
//#endregion
|
|
29516
29550
|
//#region src/client/ChartTip.tsx
|
|
@@ -29657,13 +29691,15 @@ window.__ModuleLoader__.load({
|
|
|
29657
29691
|
const d = parts[2];
|
|
29658
29692
|
return `${Number(m)}/${Number(d)}`;
|
|
29659
29693
|
}
|
|
29660
|
-
/** X-axis label for a trend bucket key.
|
|
29661
|
-
*
|
|
29662
|
-
*
|
|
29663
|
-
|
|
29664
|
-
|
|
29665
|
-
|
|
29666
|
-
|
|
29694
|
+
/** X-axis label for a trend bucket key. `mode` is what the axis READS AS,
|
|
29695
|
+
* chosen by the caller from the window it covers:
|
|
29696
|
+
* - 'hour' — a single-day hourly span (今日): the date is constant, so the
|
|
29697
|
+
* axis shows the bare clock ("8:00");
|
|
29698
|
+
* - 'day' — anything spanning several days (3天 hourly, any daily range):
|
|
29699
|
+
* the axis shows the short date ("10/22"); on multi-day hourly spans one
|
|
29700
|
+
* label marks each day. The tooltip still carries the full date + hour. */
|
|
29701
|
+
function bucketAxisLabel(key, mode) {
|
|
29702
|
+
return mode === "hour" ? `${Number(key.slice(11, 13))}:00` : shortDay(key);
|
|
29667
29703
|
}
|
|
29668
29704
|
/** Tooltip title for a trend bucket key: the date in day mode, date + hour
|
|
29669
29705
|
* in hour mode. */
|
|
@@ -29752,6 +29788,7 @@ window.__ModuleLoader__.load({
|
|
|
29752
29788
|
const [stats, setStats] = (0, react.useState)(null);
|
|
29753
29789
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
29754
29790
|
const [error, setError] = (0, react.useState)("");
|
|
29791
|
+
const [loadedAt, setLoadedAt] = (0, react.useState)(null);
|
|
29755
29792
|
const generationRef = (0, react.useRef)(0);
|
|
29756
29793
|
const panelRef = (0, react.useRef)(null);
|
|
29757
29794
|
const heatWindow = (0, react.useMemo)(() => {
|
|
@@ -29791,6 +29828,7 @@ window.__ModuleLoader__.load({
|
|
|
29791
29828
|
const res = await fetchRange(req);
|
|
29792
29829
|
if (generationRef.current !== generation) return;
|
|
29793
29830
|
setStats(res);
|
|
29831
|
+
setLoadedAt(Date.now());
|
|
29794
29832
|
loadHeat();
|
|
29795
29833
|
} catch (e) {
|
|
29796
29834
|
if (generationRef.current !== generation) return;
|
|
@@ -29899,15 +29937,25 @@ window.__ModuleLoader__.load({
|
|
|
29899
29937
|
})
|
|
29900
29938
|
]
|
|
29901
29939
|
}),
|
|
29902
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
29903
|
-
|
|
29904
|
-
|
|
29905
|
-
|
|
29906
|
-
|
|
29907
|
-
|
|
29908
|
-
|
|
29909
|
-
|
|
29910
|
-
|
|
29940
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
29941
|
+
className: TokenTotalPanel_module_default.toolbarEnd,
|
|
29942
|
+
children: [stats && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
29943
|
+
className: TokenTotalPanel_module_default.asOf,
|
|
29944
|
+
children: [
|
|
29945
|
+
t("asOf"),
|
|
29946
|
+
" ",
|
|
29947
|
+
asOfStamp(stats.to, loadedAt)
|
|
29948
|
+
]
|
|
29949
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
29950
|
+
size: "sm",
|
|
29951
|
+
variant: "outline",
|
|
29952
|
+
className: TokenTotalPanel_module_default.refresh,
|
|
29953
|
+
onClick: () => {
|
|
29954
|
+
load();
|
|
29955
|
+
},
|
|
29956
|
+
disabled: loading,
|
|
29957
|
+
children: t("refresh")
|
|
29958
|
+
})]
|
|
29911
29959
|
})
|
|
29912
29960
|
]
|
|
29913
29961
|
}),
|
|
@@ -29924,20 +29972,22 @@ window.__ModuleLoader__.load({
|
|
|
29924
29972
|
stats,
|
|
29925
29973
|
t
|
|
29926
29974
|
}),
|
|
29927
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
29928
|
-
|
|
29929
|
-
|
|
29930
|
-
|
|
29931
|
-
|
|
29932
|
-
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
|
|
29939
|
-
|
|
29940
|
-
|
|
29975
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
29976
|
+
className: TokenTotalPanel_module_default.duo,
|
|
29977
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Heatmap, {
|
|
29978
|
+
daily: heatDaily,
|
|
29979
|
+
from: heatWindow.from,
|
|
29980
|
+
to: heatWindow.to,
|
|
29981
|
+
t,
|
|
29982
|
+
panelRef
|
|
29983
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DailyTrend, {
|
|
29984
|
+
models: trendModels,
|
|
29985
|
+
daily: trendDaily,
|
|
29986
|
+
granularity: stats?.granularity ?? "day",
|
|
29987
|
+
t,
|
|
29988
|
+
colorForModel,
|
|
29989
|
+
panelRef
|
|
29990
|
+
})]
|
|
29941
29991
|
}),
|
|
29942
29992
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBreakdown, {
|
|
29943
29993
|
models: stats.models,
|
|
@@ -29945,15 +29995,8 @@ window.__ModuleLoader__.load({
|
|
|
29945
29995
|
total: stats.tokens,
|
|
29946
29996
|
providerNames: stats.providerNames,
|
|
29947
29997
|
t,
|
|
29948
|
-
panelRef
|
|
29949
|
-
|
|
29950
|
-
stats.to && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
29951
|
-
className: TokenTotalPanel_module_default.foot,
|
|
29952
|
-
children: [
|
|
29953
|
-
t("asOf"),
|
|
29954
|
-
" ",
|
|
29955
|
-
stats.to
|
|
29956
|
-
]
|
|
29998
|
+
panelRef,
|
|
29999
|
+
colorForModel
|
|
29957
30000
|
})
|
|
29958
30001
|
] }),
|
|
29959
30002
|
!error && stats && isEmptyRange(stats) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -29963,6 +30006,14 @@ window.__ModuleLoader__.load({
|
|
|
29963
30006
|
]
|
|
29964
30007
|
});
|
|
29965
30008
|
}
|
|
30009
|
+
/** "As of" stamp for a loaded range: the fetch clock time when the range
|
|
30010
|
+
* reaches today (the data is live up to that refresh), else the day's end —
|
|
30011
|
+
* the wire carries only dates, and a past day's data is complete. */
|
|
30012
|
+
function asOfStamp(to, fetchedAt) {
|
|
30013
|
+
if (to !== localDay(0)) return `${to} 23:59`;
|
|
30014
|
+
const d = new Date(fetchedAt ?? Date.now());
|
|
30015
|
+
return `${to} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
30016
|
+
}
|
|
29966
30017
|
/** A range is empty only when NOTHING was recorded in it: a pure-cache day
|
|
29967
30018
|
* (zero input/output but real cache hits) or a failed-call day (requests
|
|
29968
30019
|
* but no tokens) both carry real usage and must not read as "no data". */
|
|
@@ -29976,44 +30027,50 @@ window.__ModuleLoader__.load({
|
|
|
29976
30027
|
label: t("tokens"),
|
|
29977
30028
|
value: formatCompact(stats.tokens),
|
|
29978
30029
|
hint: t("tokensHint"),
|
|
29979
|
-
sub: `${formatTokens(stats.tokens)} ${t("tokensUnit")}
|
|
30030
|
+
sub: `${formatTokens(stats.tokens)} ${t("tokensUnit")}`,
|
|
30031
|
+
tone: 1
|
|
29980
30032
|
},
|
|
29981
30033
|
{
|
|
29982
30034
|
icon: import_lucide_react.ArrowDownToLine,
|
|
29983
30035
|
label: t("inputTokens"),
|
|
29984
30036
|
value: formatCompact(stats.inputTokens),
|
|
29985
|
-
sub: `${formatTokens(stats.inputTokens)} ${t("tokensUnit")}
|
|
30037
|
+
sub: `${formatTokens(stats.inputTokens)} ${t("tokensUnit")}`,
|
|
30038
|
+
tone: 2
|
|
29986
30039
|
},
|
|
29987
30040
|
{
|
|
29988
30041
|
icon: import_lucide_react.ArrowUpFromLine,
|
|
29989
30042
|
label: t("outputTokens"),
|
|
29990
30043
|
value: formatCompact(stats.outputTokens),
|
|
29991
|
-
sub: `${formatTokens(stats.outputTokens)} ${t("tokensUnit")}
|
|
30044
|
+
sub: `${formatTokens(stats.outputTokens)} ${t("tokensUnit")}`,
|
|
30045
|
+
tone: 3
|
|
29992
30046
|
},
|
|
29993
30047
|
{
|
|
29994
30048
|
icon: import_lucide_react.Database,
|
|
29995
30049
|
label: t("cacheTokens"),
|
|
29996
30050
|
value: formatCompact(stats.cacheTokens),
|
|
29997
|
-
sub: `${formatTokens(stats.cacheTokens)} ${t("tokensUnit")}
|
|
30051
|
+
sub: `${formatTokens(stats.cacheTokens)} ${t("tokensUnit")}`,
|
|
30052
|
+
tone: 4
|
|
29998
30053
|
},
|
|
29999
30054
|
{
|
|
30000
30055
|
icon: import_lucide_react.Activity,
|
|
30001
30056
|
label: t("cacheRate"),
|
|
30002
30057
|
value: cacheRateText(stats.cacheHit, stats.cacheMiss),
|
|
30003
30058
|
hint: t("cacheRateHint"),
|
|
30004
|
-
sub: `${formatCompact(stats.cacheHit)} ${t("cachedTokens")}
|
|
30059
|
+
sub: `${formatCompact(stats.cacheHit)} ${t("cachedTokens")}`,
|
|
30060
|
+
tone: 5
|
|
30005
30061
|
},
|
|
30006
30062
|
{
|
|
30007
30063
|
icon: import_lucide_react.MessagesSquare,
|
|
30008
30064
|
label: t("requests"),
|
|
30009
30065
|
value: String(stats.requests),
|
|
30010
|
-
sub: stats.failures > 0 ? `${t("requestsFailed")} ${stats.failures} · ${formatPercent(stats.failures / Math.max(stats.requests, 1) * 100)}` : void 0
|
|
30066
|
+
sub: stats.failures > 0 ? `${t("requestsFailed")} ${stats.failures} · ${formatPercent(stats.failures / Math.max(stats.requests, 1) * 100)}` : void 0,
|
|
30067
|
+
tone: 6
|
|
30011
30068
|
}
|
|
30012
30069
|
];
|
|
30013
30070
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30014
30071
|
className: TokenTotalPanel_module_default.cards,
|
|
30015
30072
|
children: cards.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30016
|
-
className: TokenTotalPanel_module_default.card,
|
|
30073
|
+
className: clsx(TokenTotalPanel_module_default.card, TokenTotalPanel_module_default[`cardTone${c.tone}`]),
|
|
30017
30074
|
"data-dsh-usage-glass": "card",
|
|
30018
30075
|
title: c.hint,
|
|
30019
30076
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -30033,9 +30090,11 @@ window.__ModuleLoader__.load({
|
|
|
30033
30090
|
text: c.value,
|
|
30034
30091
|
className: TokenTotalPanel_module_default.cardValue,
|
|
30035
30092
|
maxSize: 22
|
|
30036
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
30093
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FitText, {
|
|
30094
|
+
text: c.sub,
|
|
30037
30095
|
className: TokenTotalPanel_module_default.cardSub,
|
|
30038
|
-
|
|
30096
|
+
maxSize: 11,
|
|
30097
|
+
minSize: 8
|
|
30039
30098
|
})]
|
|
30040
30099
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FitText, {
|
|
30041
30100
|
text: c.value,
|
|
@@ -30045,7 +30104,7 @@ window.__ModuleLoader__.load({
|
|
|
30045
30104
|
}, c.label))
|
|
30046
30105
|
});
|
|
30047
30106
|
}
|
|
30048
|
-
function FitText({ text, className, maxSize }) {
|
|
30107
|
+
function FitText({ text, className, maxSize, minSize = 11 }) {
|
|
30049
30108
|
const ref = (0, react.useRef)(null);
|
|
30050
30109
|
const [size, setSize] = (0, react.useState)(maxSize);
|
|
30051
30110
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -30054,7 +30113,7 @@ window.__ModuleLoader__.load({
|
|
|
30054
30113
|
const fit = () => {
|
|
30055
30114
|
let s = maxSize;
|
|
30056
30115
|
el.style.fontSize = `${s}px`;
|
|
30057
|
-
while (el.scrollWidth > el.clientWidth + 1 && s >
|
|
30116
|
+
while (el.scrollWidth > el.clientWidth + 1 && s > minSize) {
|
|
30058
30117
|
s -= .5;
|
|
30059
30118
|
el.style.fontSize = `${s}px`;
|
|
30060
30119
|
}
|
|
@@ -30064,7 +30123,11 @@ window.__ModuleLoader__.load({
|
|
|
30064
30123
|
const ro = new ResizeObserver(fit);
|
|
30065
30124
|
ro.observe(el);
|
|
30066
30125
|
return () => ro.disconnect();
|
|
30067
|
-
}, [
|
|
30126
|
+
}, [
|
|
30127
|
+
text,
|
|
30128
|
+
maxSize,
|
|
30129
|
+
minSize
|
|
30130
|
+
]);
|
|
30068
30131
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30069
30132
|
ref,
|
|
30070
30133
|
className,
|
|
@@ -30072,9 +30135,35 @@ window.__ModuleLoader__.load({
|
|
|
30072
30135
|
children: text
|
|
30073
30136
|
});
|
|
30074
30137
|
}
|
|
30075
|
-
const HEAT_BASE =
|
|
30076
|
-
const HEAT_GAP =
|
|
30077
|
-
const HEAT_RX =
|
|
30138
|
+
const HEAT_BASE = 12;
|
|
30139
|
+
const HEAT_GAP = 4;
|
|
30140
|
+
const HEAT_RX = 2;
|
|
30141
|
+
const HEAT_AXIS_LEFT = 30;
|
|
30142
|
+
const HEAT_AXIS_BOTTOM = 18;
|
|
30143
|
+
/** 0 = Monday … 6 = Sunday: row order of the heatmap, and the label keys. */
|
|
30144
|
+
const DOW_KEYS = [
|
|
30145
|
+
"dow.mon",
|
|
30146
|
+
"dow.tue",
|
|
30147
|
+
"dow.wed",
|
|
30148
|
+
"dow.thu",
|
|
30149
|
+
"dow.fri",
|
|
30150
|
+
"dow.sat",
|
|
30151
|
+
"dow.sun"
|
|
30152
|
+
];
|
|
30153
|
+
const MONTH_KEYS = [
|
|
30154
|
+
"month.jan",
|
|
30155
|
+
"month.feb",
|
|
30156
|
+
"month.mar",
|
|
30157
|
+
"month.apr",
|
|
30158
|
+
"month.may",
|
|
30159
|
+
"month.jun",
|
|
30160
|
+
"month.jul",
|
|
30161
|
+
"month.aug",
|
|
30162
|
+
"month.sep",
|
|
30163
|
+
"month.oct",
|
|
30164
|
+
"month.nov",
|
|
30165
|
+
"month.dec"
|
|
30166
|
+
];
|
|
30078
30167
|
/**
|
|
30079
30168
|
* Solve the heatmap's geometry for an available width.
|
|
30080
30169
|
*
|
|
@@ -30100,7 +30189,7 @@ window.__ModuleLoader__.load({
|
|
|
30100
30189
|
*/
|
|
30101
30190
|
function heatGeometry(avail, startOffset) {
|
|
30102
30191
|
const totalWeeks = Math.ceil((364 + startOffset) / 7);
|
|
30103
|
-
const baseCols = Math.max(1, Math.floor((avail - HEAT_GAP) /
|
|
30192
|
+
const baseCols = Math.max(1, Math.floor((avail - HEAT_GAP) / 16));
|
|
30104
30193
|
if (baseCols >= totalWeeks) {
|
|
30105
30194
|
const size = (avail - HEAT_GAP) / totalWeeks - HEAT_GAP;
|
|
30106
30195
|
if (size >= HEAT_BASE) return {
|
|
@@ -30128,12 +30217,12 @@ window.__ModuleLoader__.load({
|
|
|
30128
30217
|
}, [daily]);
|
|
30129
30218
|
const [tip, setTip] = (0, react.useState)(null);
|
|
30130
30219
|
const wrapRef = (0, react.useRef)(null);
|
|
30131
|
-
const [geom, setGeom] = (0, react.useState)(() => heatGeometry(
|
|
30220
|
+
const [geom, setGeom] = (0, react.useState)(() => heatGeometry(624, 0));
|
|
30132
30221
|
(0, react.useEffect)(() => {
|
|
30133
30222
|
const el = wrapRef.current;
|
|
30134
30223
|
if (!el) return;
|
|
30135
30224
|
const update = () => {
|
|
30136
|
-
const next = heatGeometry(Math.max(1, el.clientWidth - 2), (indexOfDay(from) + 1) % 7);
|
|
30225
|
+
const next = heatGeometry(Math.max(1, el.clientWidth - 2 - HEAT_AXIS_LEFT), (indexOfDay(from) + 1) % 7);
|
|
30137
30226
|
setGeom((prev) => prev.size === next.size && prev.cols === next.cols ? prev : next);
|
|
30138
30227
|
};
|
|
30139
30228
|
update();
|
|
@@ -30148,6 +30237,24 @@ window.__ModuleLoader__.load({
|
|
|
30148
30237
|
const rows = 7;
|
|
30149
30238
|
const startOffset = days[0] ? (indexOfDay(days[0]) + 1) % 7 : 0;
|
|
30150
30239
|
const weeks = geom.totalWeeks;
|
|
30240
|
+
const monthLabels = [];
|
|
30241
|
+
let lastMonth = -1;
|
|
30242
|
+
for (let i = 0; i < days.length; i++) {
|
|
30243
|
+
const col = Math.floor((i + startOffset) / 7);
|
|
30244
|
+
if ((i + startOffset) % 7 !== 0) continue;
|
|
30245
|
+
const m = (/* @__PURE__ */ new Date(days[i] + "T00:00:00")).getMonth();
|
|
30246
|
+
if (m !== lastMonth) {
|
|
30247
|
+
monthLabels.push({
|
|
30248
|
+
col,
|
|
30249
|
+
month: m
|
|
30250
|
+
});
|
|
30251
|
+
lastMonth = m;
|
|
30252
|
+
}
|
|
30253
|
+
}
|
|
30254
|
+
while (monthLabels.length > 1 && monthLabels[0].month === monthLabels[monthLabels.length - 1].month) monthLabels.pop();
|
|
30255
|
+
const cellW = geom.size + HEAT_GAP;
|
|
30256
|
+
const svgW = HEAT_AXIS_LEFT + weeks * cellW + HEAT_GAP;
|
|
30257
|
+
const svgH = rows * cellW + HEAT_GAP + HEAT_AXIS_BOTTOM;
|
|
30151
30258
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
30152
30259
|
className: TokenTotalPanel_module_default.section,
|
|
30153
30260
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -30159,6 +30266,13 @@ window.__ModuleLoader__.load({
|
|
|
30159
30266
|
className: TokenTotalPanel_module_default.heatLegend,
|
|
30160
30267
|
children: [
|
|
30161
30268
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("heatLess") }),
|
|
30269
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
30270
|
+
className: clsx(TokenTotalPanel_module_default.heatCell, TokenTotalPanel_module_default.heatLevel0),
|
|
30271
|
+
style: {
|
|
30272
|
+
width: geom.size,
|
|
30273
|
+
height: geom.size
|
|
30274
|
+
}
|
|
30275
|
+
}),
|
|
30162
30276
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
30163
30277
|
className: clsx(TokenTotalPanel_module_default.heatCell, TokenTotalPanel_module_default.heatLevel1),
|
|
30164
30278
|
style: {
|
|
@@ -30200,40 +30314,58 @@ window.__ModuleLoader__.load({
|
|
|
30200
30314
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30201
30315
|
className: TokenTotalPanel_module_default.heatWrap,
|
|
30202
30316
|
ref: wrapRef,
|
|
30203
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
30317
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
30204
30318
|
className: TokenTotalPanel_module_default.heatmap,
|
|
30205
|
-
width:
|
|
30206
|
-
height:
|
|
30319
|
+
width: svgW,
|
|
30320
|
+
height: svgH,
|
|
30207
30321
|
role: "img",
|
|
30208
30322
|
"aria-label": t("heatmap"),
|
|
30209
|
-
children:
|
|
30210
|
-
|
|
30211
|
-
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30323
|
+
children: [
|
|
30324
|
+
DOW_KEYS.map((key, row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
30325
|
+
className: TokenTotalPanel_module_default.heatAxis,
|
|
30326
|
+
x: 22,
|
|
30327
|
+
y: HEAT_GAP + row * cellW + geom.size / 2,
|
|
30328
|
+
textAnchor: "end",
|
|
30329
|
+
dominantBaseline: "central",
|
|
30330
|
+
children: t(key)
|
|
30331
|
+
}, key)),
|
|
30332
|
+
days.map((day, i) => {
|
|
30333
|
+
const col = Math.floor((i + startOffset) / 7);
|
|
30334
|
+
const row = (i + startOffset) % 7;
|
|
30335
|
+
const rec = byDay.get(day);
|
|
30336
|
+
const tokens = rec?.total ?? 0;
|
|
30337
|
+
const level = tokens === 0 ? 0 : 1 + Math.floor(tokens / max * 4);
|
|
30338
|
+
const x = 34 + col * cellW;
|
|
30339
|
+
const y = HEAT_GAP + row * cellW;
|
|
30340
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
30341
|
+
className: clsx(TokenTotalPanel_module_default.heatCell, level === 0 && TokenTotalPanel_module_default.heatLevel0, level === 1 && TokenTotalPanel_module_default.heatLevel1, level === 2 && TokenTotalPanel_module_default.heatLevel2, level === 3 && TokenTotalPanel_module_default.heatLevel3, level === 4 && TokenTotalPanel_module_default.heatLevel4, level === 5 && TokenTotalPanel_module_default.heatLevel5),
|
|
30342
|
+
x,
|
|
30343
|
+
y,
|
|
30344
|
+
width: geom.size,
|
|
30345
|
+
height: geom.size,
|
|
30346
|
+
rx: HEAT_RX,
|
|
30347
|
+
onMouseEnter: (e) => {
|
|
30348
|
+
setTip({
|
|
30349
|
+
day,
|
|
30350
|
+
tokens,
|
|
30351
|
+
requests: rec?.requests ?? 0,
|
|
30352
|
+
cacheHit: rec?.cacheHit ?? 0,
|
|
30353
|
+
cacheMiss: rec?.cacheMiss ?? 0,
|
|
30354
|
+
anchor: e.currentTarget
|
|
30355
|
+
});
|
|
30356
|
+
},
|
|
30357
|
+
onMouseLeave: () => setTip(null)
|
|
30358
|
+
}, day);
|
|
30359
|
+
}),
|
|
30360
|
+
monthLabels.map(({ col, month }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
30361
|
+
className: TokenTotalPanel_module_default.heatAxis,
|
|
30362
|
+
x: 34 + col * cellW,
|
|
30363
|
+
y: rows * cellW + HEAT_GAP + 3,
|
|
30364
|
+
textAnchor: "start",
|
|
30365
|
+
dominantBaseline: "hanging",
|
|
30366
|
+
children: t(MONTH_KEYS[month])
|
|
30367
|
+
}, `${col}-${month}`))
|
|
30368
|
+
]
|
|
30237
30369
|
}), tip && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartTip, {
|
|
30238
30370
|
anchor: tip.anchor,
|
|
30239
30371
|
panelRef,
|
|
@@ -30304,7 +30436,21 @@ window.__ModuleLoader__.load({
|
|
|
30304
30436
|
const plotWUsed = view.avail;
|
|
30305
30437
|
const maxTotal = Math.max(1, ...visible.map((d) => d.total));
|
|
30306
30438
|
const ticks = niceTicks(maxTotal, 4);
|
|
30307
|
-
const
|
|
30439
|
+
const dayCount = new Set(visible.map((d) => d.day.slice(0, 10))).size;
|
|
30440
|
+
const multiDayHourly = granularity === "hour" && dayCount > 1;
|
|
30441
|
+
const axisMode = multiDayHourly ? "day" : granularity;
|
|
30442
|
+
const labelCandidates = [];
|
|
30443
|
+
for (let i = 0; i < n; i++) if (!multiDayHourly || i === 0 || visible[i].day.slice(0, 10) !== visible[i - 1].day.slice(0, 10)) labelCandidates.push(i);
|
|
30444
|
+
const labelMinGap = 42;
|
|
30445
|
+
const labeledIdx = /* @__PURE__ */ new Set();
|
|
30446
|
+
let lastLabelX = Number.POSITIVE_INFINITY;
|
|
30447
|
+
for (let k = labelCandidates.length - 1; k >= 0; k--) {
|
|
30448
|
+
const x = padL + barHalf + labelCandidates[k] * step;
|
|
30449
|
+
if (lastLabelX - x >= labelMinGap) {
|
|
30450
|
+
labeledIdx.add(labelCandidates[k]);
|
|
30451
|
+
lastLabelX = x;
|
|
30452
|
+
}
|
|
30453
|
+
}
|
|
30308
30454
|
const trendPoints = [];
|
|
30309
30455
|
const trendPointByDay = /* @__PURE__ */ new Map();
|
|
30310
30456
|
visible.forEach((d, i) => {
|
|
@@ -30334,9 +30480,18 @@ window.__ModuleLoader__.load({
|
|
|
30334
30480
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
30335
30481
|
className: TokenTotalPanel_module_default.sectionTitle,
|
|
30336
30482
|
children: t(granularity === "hour" ? "hourlyTrend" : "dailyTrend")
|
|
30337
|
-
}),
|
|
30338
|
-
className: TokenTotalPanel_module_default.
|
|
30339
|
-
children:
|
|
30483
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30484
|
+
className: TokenTotalPanel_module_default.headMeta,
|
|
30485
|
+
children: [trendCapped && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30486
|
+
className: TokenTotalPanel_module_default.trendNote,
|
|
30487
|
+
children: t("trendLimited").replace("{n}", String(view.trimN))
|
|
30488
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
30489
|
+
className: TokenTotalPanel_module_default.legendItem,
|
|
30490
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
30491
|
+
className: TokenTotalPanel_module_default.legendTrend,
|
|
30492
|
+
"aria-hidden": "true"
|
|
30493
|
+
}), t("cacheHitRate")]
|
|
30494
|
+
})]
|
|
30340
30495
|
})]
|
|
30341
30496
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30342
30497
|
className: TokenTotalPanel_module_default.chartWrap,
|
|
@@ -30416,12 +30571,12 @@ window.__ModuleLoader__.load({
|
|
|
30416
30571
|
},
|
|
30417
30572
|
onMouseLeave: () => setTip(null)
|
|
30418
30573
|
}),
|
|
30419
|
-
(i
|
|
30574
|
+
labeledIdx.has(i) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
30420
30575
|
className: TokenTotalPanel_module_default.axis,
|
|
30421
30576
|
x: padL + barHalf + i * step,
|
|
30422
30577
|
y: 212,
|
|
30423
30578
|
textAnchor: "middle",
|
|
30424
|
-
children: bucketAxisLabel(d.day,
|
|
30579
|
+
children: bucketAxisLabel(d.day, axisMode)
|
|
30425
30580
|
})
|
|
30426
30581
|
] }, d.day);
|
|
30427
30582
|
}),
|
|
@@ -30501,17 +30656,17 @@ window.__ModuleLoader__.load({
|
|
|
30501
30656
|
function providerColor(index) {
|
|
30502
30657
|
return index < 5 ? `var(--dsw-provider-${index + 1})` : "var(--dsw-provider-other)";
|
|
30503
30658
|
}
|
|
30504
|
-
|
|
30505
|
-
function modelColor(index) {
|
|
30506
|
-
return index < TOP_MODELS ? `var(--dsw-chart-${index + 1})` : OTHER_COLOR;
|
|
30507
|
-
}
|
|
30508
|
-
function UsageBreakdown({ models, providers, total, providerNames, t, panelRef }) {
|
|
30659
|
+
function UsageBreakdown({ models, providers, total, providerNames, t, panelRef, colorForModel }) {
|
|
30509
30660
|
const [tip, setTip] = (0, react.useState)(null);
|
|
30510
30661
|
const providerLabel = (0, react.useCallback)((provider) => {
|
|
30511
30662
|
const named = providerNames?.[provider];
|
|
30512
30663
|
return typeof named === "string" && named.trim() !== "" ? named : provider;
|
|
30513
30664
|
}, [providerNames]);
|
|
30514
30665
|
const shareText = (n) => total > 0 ? formatPercent(n / total * 100) : "—";
|
|
30666
|
+
const providerColorOf = (0, react.useCallback)((provider) => {
|
|
30667
|
+
const slot = providers.findIndex((p) => p.provider === provider);
|
|
30668
|
+
return slot < 0 ? "var(--dsw-provider-other)" : providerColor(slot);
|
|
30669
|
+
}, [providers]);
|
|
30515
30670
|
const modelRows = (0, react.useMemo)(() => {
|
|
30516
30671
|
const byName = /* @__PURE__ */ new Map();
|
|
30517
30672
|
for (const m of models) {
|
|
@@ -30530,19 +30685,19 @@ window.__ModuleLoader__.load({
|
|
|
30530
30685
|
for (const provider of m.providers ?? (m.provider === "" ? [] : [m.provider])) acc.sources.add(provider);
|
|
30531
30686
|
byName.set(name, acc);
|
|
30532
30687
|
}
|
|
30533
|
-
return [...byName.entries()].sort((a, b) => b[1].tokens - a[1].tokens).slice(0, TOP_BREAKDOWN).map(([name, acc]
|
|
30534
|
-
const split = [...acc.split.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([provider, tokens]
|
|
30688
|
+
return [...byName.entries()].sort((a, b) => b[1].tokens - a[1].tokens).slice(0, TOP_BREAKDOWN).map(([name, acc]) => {
|
|
30689
|
+
const split = [...acc.split.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([provider, tokens]) => ({
|
|
30535
30690
|
key: provider,
|
|
30536
30691
|
name: providerLabel(provider),
|
|
30537
30692
|
tokens,
|
|
30538
|
-
color:
|
|
30693
|
+
color: providerColorOf(provider)
|
|
30539
30694
|
}));
|
|
30540
30695
|
const known = new Set(split.map((child) => child.key));
|
|
30541
|
-
const named = [...acc.sources].filter((provider) => !known.has(provider)).sort().map((provider
|
|
30696
|
+
const named = [...acc.sources].filter((provider) => !known.has(provider)).sort().map((provider) => ({
|
|
30542
30697
|
key: provider,
|
|
30543
30698
|
name: providerLabel(provider),
|
|
30544
30699
|
tokens: null,
|
|
30545
|
-
color:
|
|
30700
|
+
color: providerColorOf(provider)
|
|
30546
30701
|
}));
|
|
30547
30702
|
return {
|
|
30548
30703
|
key: name,
|
|
@@ -30550,7 +30705,7 @@ window.__ModuleLoader__.load({
|
|
|
30550
30705
|
tokens: acc.tokens,
|
|
30551
30706
|
cacheHit: acc.cacheHit,
|
|
30552
30707
|
cacheMiss: acc.cacheMiss,
|
|
30553
|
-
color:
|
|
30708
|
+
color: colorForModel(name),
|
|
30554
30709
|
breakdownLabel: t("byProvider"),
|
|
30555
30710
|
breakdown: [...split, ...named]
|
|
30556
30711
|
};
|
|
@@ -30558,10 +30713,12 @@ window.__ModuleLoader__.load({
|
|
|
30558
30713
|
}, [
|
|
30559
30714
|
models,
|
|
30560
30715
|
providerLabel,
|
|
30561
|
-
t
|
|
30716
|
+
t,
|
|
30717
|
+
colorForModel,
|
|
30718
|
+
providerColorOf
|
|
30562
30719
|
]);
|
|
30563
30720
|
const providerRows = (0, react.useMemo)(() => {
|
|
30564
|
-
return [...providers].filter((p) => p.tokens > 0).sort((a, b) => b.tokens - a.tokens).slice(0, TOP_BREAKDOWN).map((p
|
|
30721
|
+
return [...providers].filter((p) => p.tokens > 0).sort((a, b) => b.tokens - a.tokens).slice(0, TOP_BREAKDOWN).map((p) => {
|
|
30565
30722
|
const label = providerLabel(p.provider);
|
|
30566
30723
|
return {
|
|
30567
30724
|
key: p.provider,
|
|
@@ -30570,20 +30727,22 @@ window.__ModuleLoader__.load({
|
|
|
30570
30727
|
tokens: p.tokens,
|
|
30571
30728
|
cacheHit: p.cacheHit,
|
|
30572
30729
|
cacheMiss: p.cacheMiss,
|
|
30573
|
-
color:
|
|
30730
|
+
color: providerColorOf(p.provider),
|
|
30574
30731
|
breakdownLabel: t("byModel"),
|
|
30575
|
-
breakdown: [...p.modelTokens ?? []].sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model)).map((m
|
|
30732
|
+
breakdown: [...p.modelTokens ?? []].sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model)).map((m) => ({
|
|
30576
30733
|
key: m.model,
|
|
30577
30734
|
name: m.model,
|
|
30578
30735
|
tokens: m.tokens,
|
|
30579
|
-
color:
|
|
30736
|
+
color: colorForModel(m.model)
|
|
30580
30737
|
}))
|
|
30581
30738
|
};
|
|
30582
30739
|
});
|
|
30583
30740
|
}, [
|
|
30584
30741
|
providers,
|
|
30585
30742
|
providerLabel,
|
|
30586
|
-
t
|
|
30743
|
+
t,
|
|
30744
|
+
colorForModel,
|
|
30745
|
+
providerColorOf
|
|
30587
30746
|
]);
|
|
30588
30747
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
30589
30748
|
className: TokenTotalPanel_module_default.section,
|
|
@@ -30710,13 +30869,27 @@ window.__ModuleLoader__.load({
|
|
|
30710
30869
|
}),
|
|
30711
30870
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30712
30871
|
className: TokenTotalPanel_module_default.modelId,
|
|
30713
|
-
children: [
|
|
30714
|
-
|
|
30715
|
-
|
|
30716
|
-
|
|
30717
|
-
|
|
30718
|
-
|
|
30719
|
-
|
|
30872
|
+
children: [
|
|
30873
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30874
|
+
className: TokenTotalPanel_module_default.modelName,
|
|
30875
|
+
children: r.name
|
|
30876
|
+
}),
|
|
30877
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30878
|
+
className: TokenTotalPanel_module_default.modelProvider,
|
|
30879
|
+
children: share(r.tokens)
|
|
30880
|
+
}),
|
|
30881
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30882
|
+
className: TokenTotalPanel_module_default.rowTrack,
|
|
30883
|
+
"aria-hidden": "true",
|
|
30884
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
30885
|
+
className: TokenTotalPanel_module_default.rowFill,
|
|
30886
|
+
style: {
|
|
30887
|
+
width: total > 0 ? `${Math.min(100, r.tokens / total * 100)}%` : "0%",
|
|
30888
|
+
background: r.color
|
|
30889
|
+
}
|
|
30890
|
+
})
|
|
30891
|
+
})
|
|
30892
|
+
]
|
|
30720
30893
|
}),
|
|
30721
30894
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30722
30895
|
className: TokenTotalPanel_module_default.modelValues,
|
|
@@ -30783,6 +30956,80 @@ window.__ModuleLoader__.load({
|
|
|
30783
30956
|
});
|
|
30784
30957
|
}
|
|
30785
30958
|
//#endregion
|
|
30959
|
+
//#region src/client/stats-icons.tsx
|
|
30960
|
+
/**
|
|
30961
|
+
* The stat-pill glyphs.
|
|
30962
|
+
*
|
|
30963
|
+
* The shell freezes its primitives table, and that table's icon names moved
|
|
30964
|
+
* with the host line: 0.2.1-rc.2 exports `IconGaugeOutlineRegular` /
|
|
30965
|
+
* `IconDatabaseOutlineRegular` (plus the `Medium` weight) and no longer
|
|
30966
|
+
* exports the `…Outline16` names this plugin used to import. A missing named
|
|
30967
|
+
* export is `undefined` at runtime, and React then throws "Element type is
|
|
30968
|
+
* invalid" on the entry's first render — the shell's slot boundary catches it,
|
|
30969
|
+
* ABDICATES the entry, and the shadowed official pill silently returns. That
|
|
30970
|
+
* failure mode is invisible in types (the package's own declarations still
|
|
30971
|
+
* describe the old names) and looks exactly like "the plugin did nothing".
|
|
30972
|
+
*
|
|
30973
|
+
* So the glyph is resolved from the runtime table by preference order and
|
|
30974
|
+
* falls back to an inline drawing of the same shape when no name exists, which
|
|
30975
|
+
* keeps one published build rendering on every host line.
|
|
30976
|
+
*/
|
|
30977
|
+
const table = _deepseek_ai_dsh_client_ui_primitives;
|
|
30978
|
+
/** First exported function component among `names`, or null when the shell
|
|
30979
|
+
* exports none of them (an older or newer icon roster). */
|
|
30980
|
+
function resolveIcon(...names) {
|
|
30981
|
+
for (const name of names) {
|
|
30982
|
+
const candidate = table[name];
|
|
30983
|
+
if (typeof candidate === "function") return candidate;
|
|
30984
|
+
}
|
|
30985
|
+
return null;
|
|
30986
|
+
}
|
|
30987
|
+
const resolvedGauge = resolveIcon("IconGaugeOutlineRegular", "IconGaugeOutlineMedium", "IconGaugeOutline16");
|
|
30988
|
+
const resolvedDatabase = resolveIcon("IconDatabaseOutlineRegular", "IconDatabaseOutlineMedium", "IconDatabaseOutline16");
|
|
30989
|
+
/** Gauge glyph for the activity pill (counts + throughput). */
|
|
30990
|
+
function GaugeIcon() {
|
|
30991
|
+
if (resolvedGauge !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedGauge, {});
|
|
30992
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
30993
|
+
viewBox: "0 0 16 16",
|
|
30994
|
+
"aria-hidden": "true",
|
|
30995
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
30996
|
+
d: "M8 2.5a5.5 5.5 0 0 1 5.5 5.5M8 8l2.6-2.6M2.5 8A5.5 5.5 0 0 1 8 2.5",
|
|
30997
|
+
fill: "none",
|
|
30998
|
+
stroke: "currentColor",
|
|
30999
|
+
strokeWidth: "1.3",
|
|
31000
|
+
strokeLinecap: "round"
|
|
31001
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
31002
|
+
cx: "8",
|
|
31003
|
+
cy: "8",
|
|
31004
|
+
r: "1",
|
|
31005
|
+
fill: "currentColor"
|
|
31006
|
+
})]
|
|
31007
|
+
});
|
|
31008
|
+
}
|
|
31009
|
+
/** Database glyph for the token-usage pill. */
|
|
31010
|
+
function DatabaseIcon() {
|
|
31011
|
+
if (resolvedDatabase !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedDatabase, {});
|
|
31012
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
31013
|
+
viewBox: "0 0 16 16",
|
|
31014
|
+
"aria-hidden": "true",
|
|
31015
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ellipse", {
|
|
31016
|
+
cx: "8",
|
|
31017
|
+
cy: "3.6",
|
|
31018
|
+
rx: "4.6",
|
|
31019
|
+
ry: "1.9",
|
|
31020
|
+
fill: "none",
|
|
31021
|
+
stroke: "currentColor",
|
|
31022
|
+
strokeWidth: "1.3"
|
|
31023
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
31024
|
+
d: "M3.4 3.6v8.8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9V3.6M3.4 8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9",
|
|
31025
|
+
fill: "none",
|
|
31026
|
+
stroke: "currentColor",
|
|
31027
|
+
strokeWidth: "1.3",
|
|
31028
|
+
strokeLinecap: "round"
|
|
31029
|
+
})]
|
|
31030
|
+
});
|
|
31031
|
+
}
|
|
31032
|
+
//#endregion
|
|
30786
31033
|
//#region src/client/stat-dialog.ts
|
|
30787
31034
|
/**
|
|
30788
31035
|
* One trigger-anchored stat dialog seat for the composer-dock pills.
|
|
@@ -30866,29 +31113,51 @@ window.__ModuleLoader__.load({
|
|
|
30866
31113
|
function billedInputTokens(usage) {
|
|
30867
31114
|
return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
30868
31115
|
}
|
|
31116
|
+
/** Round a cache-read ratio to an integer percentage, ties rounded up (official). */
|
|
31117
|
+
function roundedIntegerPercent(cacheReadTokens, denominator) {
|
|
31118
|
+
const denominatorQuotient = Math.floor(denominator / 200);
|
|
31119
|
+
const denominatorRemainder = denominator % 200;
|
|
31120
|
+
let lower = 0;
|
|
31121
|
+
let upper = 100;
|
|
31122
|
+
while (lower < upper) {
|
|
31123
|
+
const candidate = Math.floor((lower + upper + 1) / 2);
|
|
31124
|
+
const factor = candidate * 2 - 1;
|
|
31125
|
+
if (cacheReadTokens >= factor * denominatorQuotient + Math.ceil(factor * denominatorRemainder / 200)) lower = candidate;
|
|
31126
|
+
else upper = candidate - 1;
|
|
31127
|
+
}
|
|
31128
|
+
return lower;
|
|
31129
|
+
}
|
|
30869
31130
|
/**
|
|
30870
|
-
*
|
|
30871
|
-
*
|
|
31131
|
+
* Display-ready cache-hit share of prompt-side input (official replica):
|
|
31132
|
+
* integer text when integer rounding stays below 100, otherwise the minimum
|
|
31133
|
+
* decimal precision that still rounds below 100; full hit returns 100, and
|
|
31134
|
+
* no billed input returns null.
|
|
30872
31135
|
*/
|
|
30873
|
-
function
|
|
31136
|
+
function cacheHitPercent(usage) {
|
|
30874
31137
|
const denominator = billedInputTokens(usage);
|
|
30875
31138
|
if (denominator === 0) return null;
|
|
30876
|
-
|
|
30877
|
-
|
|
30878
|
-
|
|
30879
|
-
|
|
30880
|
-
|
|
30881
|
-
|
|
30882
|
-
const
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
31139
|
+
const missedInputTokens = usage.uncachedInputTokens + usage.cacheWriteTokens;
|
|
31140
|
+
if (missedInputTokens === 0) return "100";
|
|
31141
|
+
const integerPercent = roundedIntegerPercent(usage.cacheReadTokens, denominator);
|
|
31142
|
+
if (integerPercent < 100) return String(integerPercent);
|
|
31143
|
+
let decimalPlaces = 1;
|
|
31144
|
+
let scaledDoubleGap = missedInputTokens * 200;
|
|
31145
|
+
const denominatorTens = Math.floor(denominator / 10);
|
|
31146
|
+
while (scaledDoubleGap <= denominatorTens) {
|
|
31147
|
+
scaledDoubleGap *= 10;
|
|
31148
|
+
decimalPlaces += 1;
|
|
31149
|
+
}
|
|
31150
|
+
const denominatorOnes = denominator % 10;
|
|
31151
|
+
let roundedLoss = 5;
|
|
31152
|
+
for (let loss = 1; loss < 5; loss += 1) {
|
|
31153
|
+
const factor = loss * 2 + 1;
|
|
31154
|
+
const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10);
|
|
31155
|
+
if (scaledDoubleGap <= threshold) {
|
|
31156
|
+
roundedLoss = loss;
|
|
31157
|
+
break;
|
|
31158
|
+
}
|
|
31159
|
+
}
|
|
31160
|
+
return `99.${"9".repeat(decimalPlaces - 1)}${10 - roundedLoss}`;
|
|
30892
31161
|
}
|
|
30893
31162
|
/** Compact token count: 517 / 12.2K / 517K / 1.2M (official formatTokens).
|
|
30894
31163
|
* Named formatTokensCompact (not formatTokens) — the panel's format.ts
|
|
@@ -31285,7 +31554,7 @@ window.__ModuleLoader__.load({
|
|
|
31285
31554
|
}
|
|
31286
31555
|
//#endregion
|
|
31287
31556
|
//#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/StatsLineEnhanced.module.css?inline
|
|
31288
|
-
const css$1 = "/* Session stats pills under the composer (shadow of the official StatsPills):\n a gauge pill (counts + output speed, click opens the time dialog) and a\n database pill (total tokens + cache hit, click opens the usage dialog).\n Replicated from the official StatsPills.module_348665330.css_348665330 + stat-dialog.module_348665330.css_348665330\n (13/20 tertiary tier, hover-pill affordance, shared dialog skin).\n\n Host contract: the row serves two containers at once, and picks between them\n at attach time — the component marks which one it landed in (see\n StatsLineEnhanced's `markDockContainer`):\n - through 0.1.6-alpha.1 the dock slot renders straight into InputBar's own\n column, and this row owns the content-width clamp, its side gutters and\n its 4px top pad — the official StatsPills.module_348665330.css_348665330 of that line;\n - 0.1.6-alpha.2 renders it inside InputBar's flex `.dock_348665330` beside the resident\n ContextMeter, which took all three jobs over, so the row only has to\n shrink inside it (the official stylesheet's current form).\n Both forms are kept side by side because one published build has to render\n correctly on either host line. */\n\n.root_348665330 {\n display: flex;\n justify-content: center;\n gap: 12px;\n box-sizing: border-box;\n /* <= 0.1.6-alpha.1: this row is the composer column's content-width box. */\n width: 100%;\n max-width: var(--dsh-chat-content-width);\n margin: 0 auto;\n padding: 4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n/* 0.1.6-alpha.2+: InputBar's `.dock_348665330` owns the centring, the 12px gap, the top\n pad and the side clearance. */\n.root_348665330[data-dock-row] {\n width: auto;\n min-width: 0;\n max-width: 100%;\n margin: 0;\n padding: 0;\n}\n\n/* Dialog anchor: shrink-wraps its pill so the panel clamp measures the pill. */\n.anchor_348665330 {\n display: inline-flex;\n min-width: 0;\n}\n\n/* Split-mode anchor (DSH >= 0.2.1-alpha.1). The host split the single `stats`\n dock entry into one entry per pill and let its own `.dock_348665330` flex row own the\n layout, so the anchor IS the entry root and carries the text tier `.root_348665330`\n used to set — the official StatsPills.module_348665330.css_348665330 moved exactly this pair of\n declarations from `.root_348665330` onto `.anchor_348665330`. The tier stays off the plain\n `.anchor_348665330` because the pre-split row must keep its own `.root_348665330` tier. */\n.dockAnchor_348665330 {\n font-size:
|
|
31557
|
+
const css$1 = "/* Session stats pills under the composer (shadow of the official StatsPills):\n a gauge pill (counts + output speed, click opens the time dialog) and a\n database pill (total tokens + cache hit, click opens the usage dialog).\n Replicated from the official StatsPills.module_348665330.css_348665330 + stat-dialog.module_348665330.css_348665330\n (13/20 tertiary tier, hover-pill affordance, shared dialog skin).\n\n Host contract: the row serves two containers at once, and picks between them\n at attach time — the component marks which one it landed in (see\n StatsLineEnhanced's `markDockContainer`):\n - through 0.1.6-alpha.1 the dock slot renders straight into InputBar's own\n column, and this row owns the content-width clamp, its side gutters and\n its 4px top pad — the official StatsPills.module_348665330.css_348665330 of that line;\n - 0.1.6-alpha.2 renders it inside InputBar's flex `.dock_348665330` beside the resident\n ContextMeter, which took all three jobs over, so the row only has to\n shrink inside it (the official stylesheet's current form).\n Both forms are kept side by side because one published build has to render\n correctly on either host line. */\n\n.root_348665330 {\n display: flex;\n justify-content: center;\n gap: 12px;\n box-sizing: border-box;\n /* <= 0.1.6-alpha.1: this row is the composer column's content-width box. */\n width: 100%;\n max-width: var(--dsh-chat-content-width);\n margin: 0 auto;\n padding: 4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n/* 0.1.6-alpha.2+: InputBar's `.dock_348665330` owns the centring, the 12px gap, the top\n pad and the side clearance. */\n.root_348665330[data-dock-row] {\n width: auto;\n min-width: 0;\n max-width: 100%;\n margin: 0;\n padding: 0;\n}\n\n/* Dialog anchor: shrink-wraps its pill so the panel clamp measures the pill. */\n.anchor_348665330 {\n display: inline-flex;\n min-width: 0;\n}\n\n/* Split-mode anchor (DSH >= 0.2.1-alpha.1). The host split the single `stats`\n dock entry into one entry per pill and let its own `.dock_348665330` flex row own the\n layout, so the anchor IS the entry root and carries the text tier `.root_348665330`\n used to set — the official StatsPills.module_348665330.css_348665330 moved exactly this pair of\n declarations from `.root_348665330` onto `.anchor_348665330`. The tier stays off the plain\n `.anchor_348665330` because the pre-split row must keep its own `.root_348665330` tier. */\n.dockAnchor_348665330 {\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n.pill_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n box-sizing: border-box;\n max-width: 100%;\n padding: 1px 8px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: none;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n font-variant-numeric: tabular-nums;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.pill_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n/* Only the button form (a pill with dialog rows to open) invites interaction;\n the static span form keeps the resting tertiary reading. */\nbutton.pill_348665330 {\n cursor: pointer;\n}\n\nbutton.pill_348665330:hover,\nbutton.pill_348665330[aria-expanded='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.label_348665330 {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sep_348665330 {\n color: var(--dsw-alias-separator-primary);\n margin: 0 6px;\n}\n\n/* The live throughput reading sits inline exactly like the official figure:\n no reserved width, no box of its own. The earlier `min-width: 10ch` slot kept\n the centred row from shifting while the number grew, but it also left a\n permanent gap before the separator — the official pill has no such slot, and\n native parity wins over that jitter fix. */\n.speed_348665330 {\n display: inline;\n}\n\n/* Portal dialog surface: fixed in the viewport, left/top supplied inline from\n the anchored-position clamp (12px viewport margin, layers above overlays).\n Material = the official stat-dialog PAIRING, which dsh 0.1.7's menu\n redesign made mandatory: `--dsw-specific-menu` is translucent there\n (rgba 58%/45%), so the blur token must ride along — the 0.1.5-era recipe\n this file copied was a single opaque line, and a fill-only copy reads as\n bare transparency over the page (issue #6). */\n.dialogPanel_348665330 {\n position: fixed;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 12px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n/* Dialog heading row: the section name left, the headline value right. */\n.dialogTitle_348665330 {\n display: flex;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* Rule under the heading, above its rows. */\n.dialogTitleRule_348665330 {\n margin-bottom: 8px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.dialogTitleValue_348665330 {\n font-variant-numeric: tabular-nums;\n}\n\n/* Heading glyph seated left of the section name. */\n.dialogTitleLabel_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dialogTitleLabel_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.dialogDetails_348665330 {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 4px 12px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dialogDetails_348665330 dt,\n.dialogDetails_348665330 dd {\n min-width: 0;\n margin: 0;\n}\n\n.dialogDetails_348665330 dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n";
|
|
31289
31558
|
const tagId$1 = "dsh-token-total/StatsLineEnhanced.module.css";
|
|
31290
31559
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$1 + "\"]") === null) {
|
|
31291
31560
|
const tag = document.createElement("style");
|
|
@@ -31305,7 +31574,6 @@ window.__ModuleLoader__.load({
|
|
|
31305
31574
|
"label": "label_348665330",
|
|
31306
31575
|
"sep": "sep_348665330",
|
|
31307
31576
|
"speed": "speed_348665330",
|
|
31308
|
-
"subagentMark": "subagentMark_348665330",
|
|
31309
31577
|
"dialogPanel": "dialogPanel_348665330",
|
|
31310
31578
|
"dialogTitle": "dialogTitle_348665330",
|
|
31311
31579
|
"dialogTitleRule": "dialogTitleRule_348665330",
|
|
@@ -31340,9 +31608,12 @@ window.__ModuleLoader__.load({
|
|
|
31340
31608
|
* Which pair to register is read from the dock's entry ledger in
|
|
31341
31609
|
* src/client/index.tsx.
|
|
31342
31610
|
*
|
|
31343
|
-
*
|
|
31344
|
-
*
|
|
31345
|
-
*
|
|
31611
|
+
* Streaming throughput is always enabled; no stored preference gates it. The
|
|
31612
|
+
* three columns are style-for-style the official ones: same tier, radius,
|
|
31613
|
+
* colours, same dialog rows and the same integer cache-hit figure. The one
|
|
31614
|
+
* difference is the middle figure's SCOPE — it totals the whole session tree
|
|
31615
|
+
* (this agent plus every subagent it spawned), which the native projection
|
|
31616
|
+
* cannot see.
|
|
31346
31617
|
*
|
|
31347
31618
|
* Streaming throughput replaces the gauge pill's speed
|
|
31348
31619
|
* figure with a live estimate while a step is still streaming and drops back to
|
|
@@ -31372,19 +31643,16 @@ window.__ModuleLoader__.load({
|
|
|
31372
31643
|
function anchorClass(dock) {
|
|
31373
31644
|
return dock ? `${StatsLineEnhanced_module_default.anchor} ${StatsLineEnhanced_module_default.dockAnchor}` : StatsLineEnhanced_module_default.anchor;
|
|
31374
31645
|
}
|
|
31375
|
-
/** The separator + figure shape both speed arms render into the gauge pill
|
|
31376
|
-
*
|
|
31377
|
-
*
|
|
31378
|
-
*
|
|
31379
|
-
* rendered even with no figure at all (an empty session has no official figure
|
|
31380
|
-
* to fall back to until its first step settles), so the row's layout never
|
|
31381
|
-
* depends on whether a number is available. */
|
|
31646
|
+
/** The separator + figure shape both speed arms render into the gauge pill:
|
|
31647
|
+
* exactly the official pill's inline shape — separator and figure appear only
|
|
31648
|
+
* when there is a figure, and the figure occupies no reserved width, so the
|
|
31649
|
+
* row reads and reflows like the native one. */
|
|
31382
31650
|
function SpeedFigure({ text }) {
|
|
31383
31651
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31384
31652
|
className: StatsLineEnhanced_module_default.sep,
|
|
31385
31653
|
"aria-hidden": true,
|
|
31386
31654
|
children: "·"
|
|
31387
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31655
|
+
}), text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31388
31656
|
className: StatsLineEnhanced_module_default.speed,
|
|
31389
31657
|
"data-stats-speed": true,
|
|
31390
31658
|
children: text
|
|
@@ -31521,7 +31789,7 @@ window.__ModuleLoader__.load({
|
|
|
31521
31789
|
className: anchorClass(dock),
|
|
31522
31790
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31523
31791
|
className: StatsLineEnhanced_module_default.pill,
|
|
31524
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31792
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
|
|
31525
31793
|
})
|
|
31526
31794
|
});
|
|
31527
31795
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -31537,7 +31805,7 @@ window.__ModuleLoader__.load({
|
|
|
31537
31805
|
onClick: () => {
|
|
31538
31806
|
setOpen(!open);
|
|
31539
31807
|
},
|
|
31540
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31808
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
|
|
31541
31809
|
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31542
31810
|
ref: panelRef,
|
|
31543
31811
|
className: StatsLineEnhanced_module_default.dialogPanel,
|
|
@@ -31550,7 +31818,7 @@ window.__ModuleLoader__.load({
|
|
|
31550
31818
|
className: StatsLineEnhanced_module_default.dialogTitle,
|
|
31551
31819
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31552
31820
|
className: StatsLineEnhanced_module_default.dialogTitleLabel,
|
|
31553
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31821
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), t("stats.dialog.title")]
|
|
31554
31822
|
})
|
|
31555
31823
|
}),
|
|
31556
31824
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -31573,13 +31841,10 @@ window.__ModuleLoader__.load({
|
|
|
31573
31841
|
}
|
|
31574
31842
|
function UsagePill({ usage, tree, t, dialog, dock }) {
|
|
31575
31843
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
|
|
31576
|
-
const
|
|
31577
|
-
const subagentTotals = display.subagents;
|
|
31578
|
-
const showSubagents = display.subagentSessions > 0;
|
|
31579
|
-
const billed = display.total;
|
|
31844
|
+
const billed = sessionUsageForDisplay(usage, tree?.tree ?? null).total;
|
|
31580
31845
|
const total = billedTotal(billed);
|
|
31581
31846
|
const totalText = t("message.turnUsage.count", { count: formatTokensCompact(total) });
|
|
31582
|
-
const cacheHit =
|
|
31847
|
+
const cacheHit = cacheHitPercent(billed);
|
|
31583
31848
|
const cacheHitText = cacheHit !== null ? t("stats.cacheHit", { percent: cacheHit }) : null;
|
|
31584
31849
|
const exactCount = (value) => t("message.turnUsage.count", { count: formatTokens(value) });
|
|
31585
31850
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -31595,26 +31860,13 @@ window.__ModuleLoader__.load({
|
|
|
31595
31860
|
onClick: () => {
|
|
31596
31861
|
setOpen(!open);
|
|
31597
31862
|
},
|
|
31598
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31863
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31599
31864
|
className: StatsLineEnhanced_module_default.label,
|
|
31600
|
-
children: [
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
children: "·"
|
|
31606
|
-
}),
|
|
31607
|
-
showSubagents && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31608
|
-
className: StatsLineEnhanced_module_default.subagentMark,
|
|
31609
|
-
title: t("stats.dialog.subagents"),
|
|
31610
|
-
children: ["+", tree?.tree?.subagentSessions ?? 0]
|
|
31611
|
-
}),
|
|
31612
|
-
cacheHitText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31613
|
-
className: StatsLineEnhanced_module_default.sep,
|
|
31614
|
-
"aria-hidden": true,
|
|
31615
|
-
children: "·"
|
|
31616
|
-
}), cacheHitText] })
|
|
31617
|
-
]
|
|
31865
|
+
children: [totalText, cacheHitText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31866
|
+
className: StatsLineEnhanced_module_default.sep,
|
|
31867
|
+
"aria-hidden": true,
|
|
31868
|
+
children: "·"
|
|
31869
|
+
}), cacheHitText] })]
|
|
31618
31870
|
})]
|
|
31619
31871
|
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31620
31872
|
ref: panelRef,
|
|
@@ -31628,7 +31880,7 @@ window.__ModuleLoader__.load({
|
|
|
31628
31880
|
className: StatsLineEnhanced_module_default.dialogTitle,
|
|
31629
31881
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31630
31882
|
className: StatsLineEnhanced_module_default.dialogTitleLabel,
|
|
31631
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31883
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), t("stats.dialog.usageTitle")]
|
|
31632
31884
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31633
31885
|
className: StatsLineEnhanced_module_default.dialogTitleValue,
|
|
31634
31886
|
children: exactCount(total)
|
|
@@ -31647,24 +31899,9 @@ window.__ModuleLoader__.load({
|
|
|
31647
31899
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.uncachedInputTokens) }),
|
|
31648
31900
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheRead") }),
|
|
31649
31901
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheReadTokens) }),
|
|
31650
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }),
|
|
31651
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheWriteTokens) }),
|
|
31652
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.cacheMiss") }),
|
|
31653
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(tokenBreakdown(billed).cacheMiss) }),
|
|
31902
|
+
billed.cacheWriteTokens !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheWriteTokens) })] }),
|
|
31654
31903
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.output") }),
|
|
31655
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.outputTokens) })
|
|
31656
|
-
showSubagents && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
31657
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.main") }),
|
|
31658
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billedTotal(display.self)) }),
|
|
31659
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.subagents") }),
|
|
31660
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [
|
|
31661
|
-
exactCount(billedTotal(subagentTotals)),
|
|
31662
|
-
" · ",
|
|
31663
|
-
t("stats.dialog.subagentCount", { count: tree?.tree?.subagentSessions ?? 0 })
|
|
31664
|
-
] }),
|
|
31665
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.treeTotal") }),
|
|
31666
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billedTotal(billed)) })
|
|
31667
|
-
] })
|
|
31904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.outputTokens) })
|
|
31668
31905
|
]
|
|
31669
31906
|
})
|
|
31670
31907
|
]
|
|
@@ -31803,7 +32040,7 @@ window.__ModuleLoader__.load({
|
|
|
31803
32040
|
}
|
|
31804
32041
|
//#endregion
|
|
31805
32042
|
//#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/ContextUsageEntry.module.css?inline
|
|
31806
|
-
const css = ".root_1877794043 { display: inline-flex
|
|
32043
|
+
const css = "/* Mirror the host ContextMeter, including its theme and font-size tokens. */\n.root_1877794043 {\n flex: none;\n display: inline-flex;\n}\n\n.trigger_1877794043 {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-tertiary);\n font-family: inherit;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n font-variant-numeric: tabular-nums;\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n white-space: nowrap;\n cursor: pointer;\n background: transparent;\n border: none;\n flex: none;\n align-items: center;\n gap: 6px;\n padding: 1px 8px;\n display: inline-flex;\n}\n\n.trigger_1877794043:hover,\n.trigger_1877794043[aria-expanded='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.track_1877794043 {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 2px;\n}\n\n.fill_1877794043 {\n fill: none;\n stroke: var(--dsw-alias-label-tertiary);\n stroke-width: 2px;\n stroke-linecap: round;\n}\n\n.panel_1877794043 {\n z-index: 1100;\n box-sizing: border-box;\n border-radius: var(--dsw-radius-lg);\n background: var(--dsw-specific-menu);\n width: min(264px, calc(100vw - 24px));\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n border: 0;\n padding: 12px;\n font-size: 12px;\n line-height: 20px;\n position: fixed;\n}\n\n.header_1877794043 {\n align-items: center;\n gap: 6px;\n display: flex;\n}\n\n.figures_1877794043 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n margin-left: auto;\n font-weight: 500;\n}\n\n.percent_1877794043 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.headline_1877794043 { color: var(--dsw-alias-label-tertiary); }\n.headline_1877794043:empty { display: none; }\n\n.bar_1877794043 {\n corner-shape: round;\n background: var(--dsw-alias-interactive-bg-hover);\n border-radius: 999px;\n gap: 1px;\n height: 4px;\n margin: 10px 0 12px;\n display: flex;\n overflow: hidden;\n}\n\n.segment_1877794043 {\n background: var(--meter-tint, var(--dsw-alias-label-tertiary));\n border-radius: 1px;\n flex: none;\n min-width: 2px;\n height: 100%;\n}\n\n.swatch_1877794043 {\n background: var(--meter-tint);\n vertical-align: baseline;\n border-radius: 2px;\n width: 8px;\n height: 8px;\n margin-right: 6px;\n display: inline-block;\n}\n\n.colorSystem_1877794043 { --meter-tint: var(--dsw-static-neutral-bluish-400); }\n.colorTools_1877794043 { --meter-tint: #a78bfa; }\n.colorMessages_1877794043 { --meter-tint: var(--dsw-static-blue-450); }\n.rows_1877794043 { margin: 6px 0 0; }\n\n.row_1877794043 {\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n padding: 2px 0;\n display: flex;\n}\n\n.row_1877794043 dt { color: var(--dsw-alias-label-secondary); }\n.row_1877794043 dd {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n margin: 0;\n}\n";
|
|
31807
32044
|
const tagId = "dsh-token-total/ContextUsageEntry.module.css";
|
|
31808
32045
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
|
|
31809
32046
|
const tag = document.createElement("style");
|
|
@@ -31819,16 +32056,17 @@ window.__ModuleLoader__.load({
|
|
|
31819
32056
|
"fill": "fill_1877794043",
|
|
31820
32057
|
"panel": "panel_1877794043",
|
|
31821
32058
|
"header": "header_1877794043",
|
|
31822
|
-
"percent": "percent_1877794043",
|
|
31823
32059
|
"figures": "figures_1877794043",
|
|
32060
|
+
"percent": "percent_1877794043",
|
|
32061
|
+
"headline": "headline_1877794043",
|
|
31824
32062
|
"bar": "bar_1877794043",
|
|
31825
32063
|
"segment": "segment_1877794043",
|
|
31826
|
-
"
|
|
31827
|
-
"
|
|
31828
|
-
"
|
|
32064
|
+
"swatch": "swatch_1877794043",
|
|
32065
|
+
"colorSystem": "colorSystem_1877794043",
|
|
32066
|
+
"colorTools": "colorTools_1877794043",
|
|
32067
|
+
"colorMessages": "colorMessages_1877794043",
|
|
31829
32068
|
"rows": "rows_1877794043",
|
|
31830
|
-
"row": "row_1877794043"
|
|
31831
|
-
"swatch": "swatch_1877794043"
|
|
32069
|
+
"row": "row_1877794043"
|
|
31832
32070
|
};
|
|
31833
32071
|
//#endregion
|
|
31834
32072
|
//#region src/client/ContextUsageEntry.tsx
|
|
@@ -31836,13 +32074,13 @@ window.__ModuleLoader__.load({
|
|
|
31836
32074
|
const CONTEXT_LOCALES = {
|
|
31837
32075
|
zh: {
|
|
31838
32076
|
label: "上下文",
|
|
31839
|
-
used: "
|
|
32077
|
+
used: "上下文已用",
|
|
31840
32078
|
system: "系统提示词",
|
|
31841
|
-
tools: "
|
|
31842
|
-
messages: "
|
|
31843
|
-
aria: "
|
|
31844
|
-
thousand: "{value}
|
|
31845
|
-
million: "{value}
|
|
32079
|
+
tools: "工具定义",
|
|
32080
|
+
messages: "对话消息",
|
|
32081
|
+
aria: "上下文已用 {percent}",
|
|
32082
|
+
thousand: "{value}K",
|
|
32083
|
+
million: "{value}M"
|
|
31846
32084
|
},
|
|
31847
32085
|
en: {
|
|
31848
32086
|
label: "Context",
|
|
@@ -31900,13 +32138,27 @@ window.__ModuleLoader__.load({
|
|
|
31900
32138
|
if (context === null) return null;
|
|
31901
32139
|
const percent = context.percent;
|
|
31902
32140
|
const reading = `${percent}%`;
|
|
31903
|
-
const total = breakdown === void 0 ? 0 : breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens;
|
|
31904
32141
|
const segments = contextBreakdownSegments(percent, breakdown);
|
|
31905
32142
|
const format = (value) => formatContextTokens(value, {
|
|
31906
32143
|
thousand: t("thousand"),
|
|
31907
32144
|
million: t("million")
|
|
31908
32145
|
});
|
|
31909
|
-
const
|
|
32146
|
+
const [headBefore = "", headAfter = ""] = t("aria", { percent: "\0" }).split("\0").map((part) => part.trim());
|
|
32147
|
+
const rowLabels = [
|
|
32148
|
+
"system",
|
|
32149
|
+
"tools",
|
|
32150
|
+
"messages"
|
|
32151
|
+
];
|
|
32152
|
+
const rowColors = [
|
|
32153
|
+
"colorSystem",
|
|
32154
|
+
"colorTools",
|
|
32155
|
+
"colorMessages"
|
|
32156
|
+
];
|
|
32157
|
+
const segmentColors = {
|
|
32158
|
+
systemTokens: ContextUsageEntry_module_default.colorSystem,
|
|
32159
|
+
toolsTokens: ContextUsageEntry_module_default.colorTools,
|
|
32160
|
+
messageTokens: ContextUsageEntry_module_default.colorMessages
|
|
32161
|
+
};
|
|
31910
32162
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31911
32163
|
ref: rootRef,
|
|
31912
32164
|
className: ContextUsageEntry_module_default.root,
|
|
@@ -31947,11 +32199,18 @@ window.__ModuleLoader__.load({
|
|
|
31947
32199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31948
32200
|
className: ContextUsageEntry_module_default.header,
|
|
31949
32201
|
children: [
|
|
31950
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32202
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32203
|
+
className: ContextUsageEntry_module_default.headline,
|
|
32204
|
+
children: headBefore
|
|
32205
|
+
}),
|
|
31951
32206
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31952
32207
|
className: ContextUsageEntry_module_default.percent,
|
|
31953
32208
|
children: reading
|
|
31954
32209
|
}),
|
|
32210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32211
|
+
className: ContextUsageEntry_module_default.headline,
|
|
32212
|
+
children: headAfter
|
|
32213
|
+
}),
|
|
31955
32214
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31956
32215
|
className: ContextUsageEntry_module_default.figures,
|
|
31957
32216
|
children: `~${format(context.usedTokens)} / ${format(context.contextWindow)}`
|
|
@@ -31961,11 +32220,11 @@ window.__ModuleLoader__.load({
|
|
|
31961
32220
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31962
32221
|
className: ContextUsageEntry_module_default.bar,
|
|
31963
32222
|
children: segments.map((segment) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31964
|
-
className: `${ContextUsageEntry_module_default.segment} ${
|
|
32223
|
+
className: segmentColors[segment.key] === void 0 ? ContextUsageEntry_module_default.segment : `${ContextUsageEntry_module_default.segment} ${segmentColors[segment.key]}`,
|
|
31965
32224
|
style: { width: `${segment.width}%` }
|
|
31966
32225
|
}, segment.key))
|
|
31967
32226
|
}),
|
|
31968
|
-
breakdown !== void 0 &&
|
|
32227
|
+
breakdown !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
|
|
31969
32228
|
className: ContextUsageEntry_module_default.rows,
|
|
31970
32229
|
children: [
|
|
31971
32230
|
"systemTokens",
|
|
@@ -31973,11 +32232,7 @@ window.__ModuleLoader__.load({
|
|
|
31973
32232
|
"messageTokens"
|
|
31974
32233
|
].map((key, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31975
32234
|
className: ContextUsageEntry_module_default.row,
|
|
31976
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dt", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${ContextUsageEntry_module_default.swatch} ${ContextUsageEntry_module_default[
|
|
31977
|
-
"system",
|
|
31978
|
-
"tools",
|
|
31979
|
-
"messages"
|
|
31980
|
-
][index])] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: `~${format(breakdown[key])}` })]
|
|
32235
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dt", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${ContextUsageEntry_module_default.swatch} ${ContextUsageEntry_module_default[rowColors[index]]}` }), t(rowLabels[index])] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: `~${format(breakdown[key])}` })]
|
|
31981
32236
|
}, key))
|
|
31982
32237
|
})
|
|
31983
32238
|
]
|
|
@@ -32066,6 +32321,25 @@ window.__ModuleLoader__.load({
|
|
|
32066
32321
|
heatmap: "Activity heatmap",
|
|
32067
32322
|
heatLess: "Less",
|
|
32068
32323
|
heatMore: "More",
|
|
32324
|
+
"dow.mon": "Mon",
|
|
32325
|
+
"dow.tue": "Tue",
|
|
32326
|
+
"dow.wed": "Wed",
|
|
32327
|
+
"dow.thu": "Thu",
|
|
32328
|
+
"dow.fri": "Fri",
|
|
32329
|
+
"dow.sat": "Sat",
|
|
32330
|
+
"dow.sun": "Sun",
|
|
32331
|
+
"month.jan": "Jan",
|
|
32332
|
+
"month.feb": "Feb",
|
|
32333
|
+
"month.mar": "Mar",
|
|
32334
|
+
"month.apr": "Apr",
|
|
32335
|
+
"month.may": "May",
|
|
32336
|
+
"month.jun": "Jun",
|
|
32337
|
+
"month.jul": "Jul",
|
|
32338
|
+
"month.aug": "Aug",
|
|
32339
|
+
"month.sep": "Sep",
|
|
32340
|
+
"month.oct": "Oct",
|
|
32341
|
+
"month.nov": "Nov",
|
|
32342
|
+
"month.dec": "Dec",
|
|
32069
32343
|
dailyTrend: "Daily token trend",
|
|
32070
32344
|
hourlyTrend: "Hourly token trend",
|
|
32071
32345
|
trendLimited: "Showing the latest {n} days",
|
|
@@ -32135,6 +32409,25 @@ window.__ModuleLoader__.load({
|
|
|
32135
32409
|
heatmap: "活跃热力图",
|
|
32136
32410
|
heatLess: "较少",
|
|
32137
32411
|
heatMore: "较多",
|
|
32412
|
+
"dow.mon": "周一",
|
|
32413
|
+
"dow.tue": "周二",
|
|
32414
|
+
"dow.wed": "周三",
|
|
32415
|
+
"dow.thu": "周四",
|
|
32416
|
+
"dow.fri": "周五",
|
|
32417
|
+
"dow.sat": "周六",
|
|
32418
|
+
"dow.sun": "周日",
|
|
32419
|
+
"month.jan": "1月",
|
|
32420
|
+
"month.feb": "2月",
|
|
32421
|
+
"month.mar": "3月",
|
|
32422
|
+
"month.apr": "4月",
|
|
32423
|
+
"month.may": "5月",
|
|
32424
|
+
"month.jun": "6月",
|
|
32425
|
+
"month.jul": "7月",
|
|
32426
|
+
"month.aug": "8月",
|
|
32427
|
+
"month.sep": "9月",
|
|
32428
|
+
"month.oct": "10月",
|
|
32429
|
+
"month.nov": "11月",
|
|
32430
|
+
"month.dec": "12月",
|
|
32138
32431
|
dailyTrend: "按天 Token 趋势",
|
|
32139
32432
|
hourlyTrend: "按小时 Token 趋势",
|
|
32140
32433
|
trendLimited: "仅显示最近 {n} 天",
|
|
@@ -32204,6 +32497,25 @@ window.__ModuleLoader__.load({
|
|
|
32204
32497
|
heatmap: "活躍熱力圖",
|
|
32205
32498
|
heatLess: "較少",
|
|
32206
32499
|
heatMore: "較多",
|
|
32500
|
+
"dow.mon": "週一",
|
|
32501
|
+
"dow.tue": "週二",
|
|
32502
|
+
"dow.wed": "週三",
|
|
32503
|
+
"dow.thu": "週四",
|
|
32504
|
+
"dow.fri": "週五",
|
|
32505
|
+
"dow.sat": "週六",
|
|
32506
|
+
"dow.sun": "週日",
|
|
32507
|
+
"month.jan": "1月",
|
|
32508
|
+
"month.feb": "2月",
|
|
32509
|
+
"month.mar": "3月",
|
|
32510
|
+
"month.apr": "4月",
|
|
32511
|
+
"month.may": "5月",
|
|
32512
|
+
"month.jun": "6月",
|
|
32513
|
+
"month.jul": "7月",
|
|
32514
|
+
"month.aug": "8月",
|
|
32515
|
+
"month.sep": "9月",
|
|
32516
|
+
"month.oct": "10月",
|
|
32517
|
+
"month.nov": "11月",
|
|
32518
|
+
"month.dec": "12月",
|
|
32207
32519
|
dailyTrend: "每日 Token 趨勢",
|
|
32208
32520
|
hourlyTrend: "每小時 Token 趨勢",
|
|
32209
32521
|
trendLimited: "僅顯示最近 {n} 天",
|