dsh-token-total 1.0.2 → 1.0.3
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 +203 -100
- 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/stats-icons.tsx +83 -0
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");
|
|
@@ -30783,6 +30804,80 @@ window.__ModuleLoader__.load({
|
|
|
30783
30804
|
});
|
|
30784
30805
|
}
|
|
30785
30806
|
//#endregion
|
|
30807
|
+
//#region src/client/stats-icons.tsx
|
|
30808
|
+
/**
|
|
30809
|
+
* The stat-pill glyphs.
|
|
30810
|
+
*
|
|
30811
|
+
* The shell freezes its primitives table, and that table's icon names moved
|
|
30812
|
+
* with the host line: 0.2.1-rc.2 exports `IconGaugeOutlineRegular` /
|
|
30813
|
+
* `IconDatabaseOutlineRegular` (plus the `Medium` weight) and no longer
|
|
30814
|
+
* exports the `…Outline16` names this plugin used to import. A missing named
|
|
30815
|
+
* export is `undefined` at runtime, and React then throws "Element type is
|
|
30816
|
+
* invalid" on the entry's first render — the shell's slot boundary catches it,
|
|
30817
|
+
* ABDICATES the entry, and the shadowed official pill silently returns. That
|
|
30818
|
+
* failure mode is invisible in types (the package's own declarations still
|
|
30819
|
+
* describe the old names) and looks exactly like "the plugin did nothing".
|
|
30820
|
+
*
|
|
30821
|
+
* So the glyph is resolved from the runtime table by preference order and
|
|
30822
|
+
* falls back to an inline drawing of the same shape when no name exists, which
|
|
30823
|
+
* keeps one published build rendering on every host line.
|
|
30824
|
+
*/
|
|
30825
|
+
const table = _deepseek_ai_dsh_client_ui_primitives;
|
|
30826
|
+
/** First exported function component among `names`, or null when the shell
|
|
30827
|
+
* exports none of them (an older or newer icon roster). */
|
|
30828
|
+
function resolveIcon(...names) {
|
|
30829
|
+
for (const name of names) {
|
|
30830
|
+
const candidate = table[name];
|
|
30831
|
+
if (typeof candidate === "function") return candidate;
|
|
30832
|
+
}
|
|
30833
|
+
return null;
|
|
30834
|
+
}
|
|
30835
|
+
const resolvedGauge = resolveIcon("IconGaugeOutlineRegular", "IconGaugeOutlineMedium", "IconGaugeOutline16");
|
|
30836
|
+
const resolvedDatabase = resolveIcon("IconDatabaseOutlineRegular", "IconDatabaseOutlineMedium", "IconDatabaseOutline16");
|
|
30837
|
+
/** Gauge glyph for the activity pill (counts + throughput). */
|
|
30838
|
+
function GaugeIcon() {
|
|
30839
|
+
if (resolvedGauge !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedGauge, {});
|
|
30840
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
30841
|
+
viewBox: "0 0 16 16",
|
|
30842
|
+
"aria-hidden": "true",
|
|
30843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
30844
|
+
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",
|
|
30845
|
+
fill: "none",
|
|
30846
|
+
stroke: "currentColor",
|
|
30847
|
+
strokeWidth: "1.3",
|
|
30848
|
+
strokeLinecap: "round"
|
|
30849
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
30850
|
+
cx: "8",
|
|
30851
|
+
cy: "8",
|
|
30852
|
+
r: "1",
|
|
30853
|
+
fill: "currentColor"
|
|
30854
|
+
})]
|
|
30855
|
+
});
|
|
30856
|
+
}
|
|
30857
|
+
/** Database glyph for the token-usage pill. */
|
|
30858
|
+
function DatabaseIcon() {
|
|
30859
|
+
if (resolvedDatabase !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedDatabase, {});
|
|
30860
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
30861
|
+
viewBox: "0 0 16 16",
|
|
30862
|
+
"aria-hidden": "true",
|
|
30863
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ellipse", {
|
|
30864
|
+
cx: "8",
|
|
30865
|
+
cy: "3.6",
|
|
30866
|
+
rx: "4.6",
|
|
30867
|
+
ry: "1.9",
|
|
30868
|
+
fill: "none",
|
|
30869
|
+
stroke: "currentColor",
|
|
30870
|
+
strokeWidth: "1.3"
|
|
30871
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
30872
|
+
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",
|
|
30873
|
+
fill: "none",
|
|
30874
|
+
stroke: "currentColor",
|
|
30875
|
+
strokeWidth: "1.3",
|
|
30876
|
+
strokeLinecap: "round"
|
|
30877
|
+
})]
|
|
30878
|
+
});
|
|
30879
|
+
}
|
|
30880
|
+
//#endregion
|
|
30786
30881
|
//#region src/client/stat-dialog.ts
|
|
30787
30882
|
/**
|
|
30788
30883
|
* One trigger-anchored stat dialog seat for the composer-dock pills.
|
|
@@ -30866,29 +30961,51 @@ window.__ModuleLoader__.load({
|
|
|
30866
30961
|
function billedInputTokens(usage) {
|
|
30867
30962
|
return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
30868
30963
|
}
|
|
30964
|
+
/** Round a cache-read ratio to an integer percentage, ties rounded up (official). */
|
|
30965
|
+
function roundedIntegerPercent(cacheReadTokens, denominator) {
|
|
30966
|
+
const denominatorQuotient = Math.floor(denominator / 200);
|
|
30967
|
+
const denominatorRemainder = denominator % 200;
|
|
30968
|
+
let lower = 0;
|
|
30969
|
+
let upper = 100;
|
|
30970
|
+
while (lower < upper) {
|
|
30971
|
+
const candidate = Math.floor((lower + upper + 1) / 2);
|
|
30972
|
+
const factor = candidate * 2 - 1;
|
|
30973
|
+
if (cacheReadTokens >= factor * denominatorQuotient + Math.ceil(factor * denominatorRemainder / 200)) lower = candidate;
|
|
30974
|
+
else upper = candidate - 1;
|
|
30975
|
+
}
|
|
30976
|
+
return lower;
|
|
30977
|
+
}
|
|
30869
30978
|
/**
|
|
30870
|
-
*
|
|
30871
|
-
*
|
|
30979
|
+
* Display-ready cache-hit share of prompt-side input (official replica):
|
|
30980
|
+
* integer text when integer rounding stays below 100, otherwise the minimum
|
|
30981
|
+
* decimal precision that still rounds below 100; full hit returns 100, and
|
|
30982
|
+
* no billed input returns null.
|
|
30872
30983
|
*/
|
|
30873
|
-
function
|
|
30984
|
+
function cacheHitPercent(usage) {
|
|
30874
30985
|
const denominator = billedInputTokens(usage);
|
|
30875
30986
|
if (denominator === 0) return null;
|
|
30876
|
-
|
|
30877
|
-
|
|
30878
|
-
|
|
30879
|
-
|
|
30880
|
-
|
|
30881
|
-
|
|
30882
|
-
const
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
30987
|
+
const missedInputTokens = usage.uncachedInputTokens + usage.cacheWriteTokens;
|
|
30988
|
+
if (missedInputTokens === 0) return "100";
|
|
30989
|
+
const integerPercent = roundedIntegerPercent(usage.cacheReadTokens, denominator);
|
|
30990
|
+
if (integerPercent < 100) return String(integerPercent);
|
|
30991
|
+
let decimalPlaces = 1;
|
|
30992
|
+
let scaledDoubleGap = missedInputTokens * 200;
|
|
30993
|
+
const denominatorTens = Math.floor(denominator / 10);
|
|
30994
|
+
while (scaledDoubleGap <= denominatorTens) {
|
|
30995
|
+
scaledDoubleGap *= 10;
|
|
30996
|
+
decimalPlaces += 1;
|
|
30997
|
+
}
|
|
30998
|
+
const denominatorOnes = denominator % 10;
|
|
30999
|
+
let roundedLoss = 5;
|
|
31000
|
+
for (let loss = 1; loss < 5; loss += 1) {
|
|
31001
|
+
const factor = loss * 2 + 1;
|
|
31002
|
+
const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10);
|
|
31003
|
+
if (scaledDoubleGap <= threshold) {
|
|
31004
|
+
roundedLoss = loss;
|
|
31005
|
+
break;
|
|
31006
|
+
}
|
|
31007
|
+
}
|
|
31008
|
+
return `99.${"9".repeat(decimalPlaces - 1)}${10 - roundedLoss}`;
|
|
30892
31009
|
}
|
|
30893
31010
|
/** Compact token count: 517 / 12.2K / 517K / 1.2M (official formatTokens).
|
|
30894
31011
|
* Named formatTokensCompact (not formatTokens) — the panel's format.ts
|
|
@@ -31285,7 +31402,7 @@ window.__ModuleLoader__.load({
|
|
|
31285
31402
|
}
|
|
31286
31403
|
//#endregion
|
|
31287
31404
|
//#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:
|
|
31405
|
+
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
31406
|
const tagId$1 = "dsh-token-total/StatsLineEnhanced.module.css";
|
|
31290
31407
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$1 + "\"]") === null) {
|
|
31291
31408
|
const tag = document.createElement("style");
|
|
@@ -31305,7 +31422,6 @@ window.__ModuleLoader__.load({
|
|
|
31305
31422
|
"label": "label_348665330",
|
|
31306
31423
|
"sep": "sep_348665330",
|
|
31307
31424
|
"speed": "speed_348665330",
|
|
31308
|
-
"subagentMark": "subagentMark_348665330",
|
|
31309
31425
|
"dialogPanel": "dialogPanel_348665330",
|
|
31310
31426
|
"dialogTitle": "dialogTitle_348665330",
|
|
31311
31427
|
"dialogTitleRule": "dialogTitleRule_348665330",
|
|
@@ -31340,9 +31456,12 @@ window.__ModuleLoader__.load({
|
|
|
31340
31456
|
* Which pair to register is read from the dock's entry ledger in
|
|
31341
31457
|
* src/client/index.tsx.
|
|
31342
31458
|
*
|
|
31343
|
-
*
|
|
31344
|
-
*
|
|
31345
|
-
*
|
|
31459
|
+
* Streaming throughput is always enabled; no stored preference gates it. The
|
|
31460
|
+
* three columns are style-for-style the official ones: same tier, radius,
|
|
31461
|
+
* colours, same dialog rows and the same integer cache-hit figure. The one
|
|
31462
|
+
* difference is the middle figure's SCOPE — it totals the whole session tree
|
|
31463
|
+
* (this agent plus every subagent it spawned), which the native projection
|
|
31464
|
+
* cannot see.
|
|
31346
31465
|
*
|
|
31347
31466
|
* Streaming throughput replaces the gauge pill's speed
|
|
31348
31467
|
* figure with a live estimate while a step is still streaming and drops back to
|
|
@@ -31372,19 +31491,16 @@ window.__ModuleLoader__.load({
|
|
|
31372
31491
|
function anchorClass(dock) {
|
|
31373
31492
|
return dock ? `${StatsLineEnhanced_module_default.anchor} ${StatsLineEnhanced_module_default.dockAnchor}` : StatsLineEnhanced_module_default.anchor;
|
|
31374
31493
|
}
|
|
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. */
|
|
31494
|
+
/** The separator + figure shape both speed arms render into the gauge pill:
|
|
31495
|
+
* exactly the official pill's inline shape — separator and figure appear only
|
|
31496
|
+
* when there is a figure, and the figure occupies no reserved width, so the
|
|
31497
|
+
* row reads and reflows like the native one. */
|
|
31382
31498
|
function SpeedFigure({ text }) {
|
|
31383
31499
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31384
31500
|
className: StatsLineEnhanced_module_default.sep,
|
|
31385
31501
|
"aria-hidden": true,
|
|
31386
31502
|
children: "·"
|
|
31387
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31503
|
+
}), text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31388
31504
|
className: StatsLineEnhanced_module_default.speed,
|
|
31389
31505
|
"data-stats-speed": true,
|
|
31390
31506
|
children: text
|
|
@@ -31521,7 +31637,7 @@ window.__ModuleLoader__.load({
|
|
|
31521
31637
|
className: anchorClass(dock),
|
|
31522
31638
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31523
31639
|
className: StatsLineEnhanced_module_default.pill,
|
|
31524
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31640
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
|
|
31525
31641
|
})
|
|
31526
31642
|
});
|
|
31527
31643
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -31537,7 +31653,7 @@ window.__ModuleLoader__.load({
|
|
|
31537
31653
|
onClick: () => {
|
|
31538
31654
|
setOpen(!open);
|
|
31539
31655
|
},
|
|
31540
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31656
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
|
|
31541
31657
|
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31542
31658
|
ref: panelRef,
|
|
31543
31659
|
className: StatsLineEnhanced_module_default.dialogPanel,
|
|
@@ -31550,7 +31666,7 @@ window.__ModuleLoader__.load({
|
|
|
31550
31666
|
className: StatsLineEnhanced_module_default.dialogTitle,
|
|
31551
31667
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31552
31668
|
className: StatsLineEnhanced_module_default.dialogTitleLabel,
|
|
31553
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31669
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), t("stats.dialog.title")]
|
|
31554
31670
|
})
|
|
31555
31671
|
}),
|
|
31556
31672
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -31573,13 +31689,10 @@ window.__ModuleLoader__.load({
|
|
|
31573
31689
|
}
|
|
31574
31690
|
function UsagePill({ usage, tree, t, dialog, dock }) {
|
|
31575
31691
|
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;
|
|
31692
|
+
const billed = sessionUsageForDisplay(usage, tree?.tree ?? null).total;
|
|
31580
31693
|
const total = billedTotal(billed);
|
|
31581
31694
|
const totalText = t("message.turnUsage.count", { count: formatTokensCompact(total) });
|
|
31582
|
-
const cacheHit =
|
|
31695
|
+
const cacheHit = cacheHitPercent(billed);
|
|
31583
31696
|
const cacheHitText = cacheHit !== null ? t("stats.cacheHit", { percent: cacheHit }) : null;
|
|
31584
31697
|
const exactCount = (value) => t("message.turnUsage.count", { count: formatTokens(value) });
|
|
31585
31698
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -31595,26 +31708,13 @@ window.__ModuleLoader__.load({
|
|
|
31595
31708
|
onClick: () => {
|
|
31596
31709
|
setOpen(!open);
|
|
31597
31710
|
},
|
|
31598
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31711
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31599
31712
|
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
|
-
]
|
|
31713
|
+
children: [totalText, cacheHitText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31714
|
+
className: StatsLineEnhanced_module_default.sep,
|
|
31715
|
+
"aria-hidden": true,
|
|
31716
|
+
children: "·"
|
|
31717
|
+
}), cacheHitText] })]
|
|
31618
31718
|
})]
|
|
31619
31719
|
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31620
31720
|
ref: panelRef,
|
|
@@ -31628,7 +31728,7 @@ window.__ModuleLoader__.load({
|
|
|
31628
31728
|
className: StatsLineEnhanced_module_default.dialogTitle,
|
|
31629
31729
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31630
31730
|
className: StatsLineEnhanced_module_default.dialogTitleLabel,
|
|
31631
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
31731
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), t("stats.dialog.usageTitle")]
|
|
31632
31732
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31633
31733
|
className: StatsLineEnhanced_module_default.dialogTitleValue,
|
|
31634
31734
|
children: exactCount(total)
|
|
@@ -31647,24 +31747,9 @@ window.__ModuleLoader__.load({
|
|
|
31647
31747
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.uncachedInputTokens) }),
|
|
31648
31748
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheRead") }),
|
|
31649
31749
|
/* @__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) }),
|
|
31750
|
+
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
31751
|
/* @__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
|
-
] })
|
|
31752
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.outputTokens) })
|
|
31668
31753
|
]
|
|
31669
31754
|
})
|
|
31670
31755
|
]
|
|
@@ -31803,7 +31888,7 @@ window.__ModuleLoader__.load({
|
|
|
31803
31888
|
}
|
|
31804
31889
|
//#endregion
|
|
31805
31890
|
//#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
|
|
31891
|
+
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
31892
|
const tagId = "dsh-token-total/ContextUsageEntry.module.css";
|
|
31808
31893
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
|
|
31809
31894
|
const tag = document.createElement("style");
|
|
@@ -31819,16 +31904,17 @@ window.__ModuleLoader__.load({
|
|
|
31819
31904
|
"fill": "fill_1877794043",
|
|
31820
31905
|
"panel": "panel_1877794043",
|
|
31821
31906
|
"header": "header_1877794043",
|
|
31822
|
-
"percent": "percent_1877794043",
|
|
31823
31907
|
"figures": "figures_1877794043",
|
|
31908
|
+
"percent": "percent_1877794043",
|
|
31909
|
+
"headline": "headline_1877794043",
|
|
31824
31910
|
"bar": "bar_1877794043",
|
|
31825
31911
|
"segment": "segment_1877794043",
|
|
31826
|
-
"
|
|
31827
|
-
"
|
|
31828
|
-
"
|
|
31912
|
+
"swatch": "swatch_1877794043",
|
|
31913
|
+
"colorSystem": "colorSystem_1877794043",
|
|
31914
|
+
"colorTools": "colorTools_1877794043",
|
|
31915
|
+
"colorMessages": "colorMessages_1877794043",
|
|
31829
31916
|
"rows": "rows_1877794043",
|
|
31830
|
-
"row": "row_1877794043"
|
|
31831
|
-
"swatch": "swatch_1877794043"
|
|
31917
|
+
"row": "row_1877794043"
|
|
31832
31918
|
};
|
|
31833
31919
|
//#endregion
|
|
31834
31920
|
//#region src/client/ContextUsageEntry.tsx
|
|
@@ -31836,13 +31922,13 @@ window.__ModuleLoader__.load({
|
|
|
31836
31922
|
const CONTEXT_LOCALES = {
|
|
31837
31923
|
zh: {
|
|
31838
31924
|
label: "上下文",
|
|
31839
|
-
used: "
|
|
31925
|
+
used: "上下文已用",
|
|
31840
31926
|
system: "系统提示词",
|
|
31841
|
-
tools: "
|
|
31842
|
-
messages: "
|
|
31843
|
-
aria: "
|
|
31844
|
-
thousand: "{value}
|
|
31845
|
-
million: "{value}
|
|
31927
|
+
tools: "工具定义",
|
|
31928
|
+
messages: "对话消息",
|
|
31929
|
+
aria: "上下文已用 {percent}",
|
|
31930
|
+
thousand: "{value}K",
|
|
31931
|
+
million: "{value}M"
|
|
31846
31932
|
},
|
|
31847
31933
|
en: {
|
|
31848
31934
|
label: "Context",
|
|
@@ -31900,13 +31986,27 @@ window.__ModuleLoader__.load({
|
|
|
31900
31986
|
if (context === null) return null;
|
|
31901
31987
|
const percent = context.percent;
|
|
31902
31988
|
const reading = `${percent}%`;
|
|
31903
|
-
const total = breakdown === void 0 ? 0 : breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens;
|
|
31904
31989
|
const segments = contextBreakdownSegments(percent, breakdown);
|
|
31905
31990
|
const format = (value) => formatContextTokens(value, {
|
|
31906
31991
|
thousand: t("thousand"),
|
|
31907
31992
|
million: t("million")
|
|
31908
31993
|
});
|
|
31909
|
-
const
|
|
31994
|
+
const [headBefore = "", headAfter = ""] = t("aria", { percent: "\0" }).split("\0").map((part) => part.trim());
|
|
31995
|
+
const rowLabels = [
|
|
31996
|
+
"system",
|
|
31997
|
+
"tools",
|
|
31998
|
+
"messages"
|
|
31999
|
+
];
|
|
32000
|
+
const rowColors = [
|
|
32001
|
+
"colorSystem",
|
|
32002
|
+
"colorTools",
|
|
32003
|
+
"colorMessages"
|
|
32004
|
+
];
|
|
32005
|
+
const segmentColors = {
|
|
32006
|
+
systemTokens: ContextUsageEntry_module_default.colorSystem,
|
|
32007
|
+
toolsTokens: ContextUsageEntry_module_default.colorTools,
|
|
32008
|
+
messageTokens: ContextUsageEntry_module_default.colorMessages
|
|
32009
|
+
};
|
|
31910
32010
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
31911
32011
|
ref: rootRef,
|
|
31912
32012
|
className: ContextUsageEntry_module_default.root,
|
|
@@ -31947,11 +32047,18 @@ window.__ModuleLoader__.load({
|
|
|
31947
32047
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31948
32048
|
className: ContextUsageEntry_module_default.header,
|
|
31949
32049
|
children: [
|
|
31950
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32050
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32051
|
+
className: ContextUsageEntry_module_default.headline,
|
|
32052
|
+
children: headBefore
|
|
32053
|
+
}),
|
|
31951
32054
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31952
32055
|
className: ContextUsageEntry_module_default.percent,
|
|
31953
32056
|
children: reading
|
|
31954
32057
|
}),
|
|
32058
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32059
|
+
className: ContextUsageEntry_module_default.headline,
|
|
32060
|
+
children: headAfter
|
|
32061
|
+
}),
|
|
31955
32062
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31956
32063
|
className: ContextUsageEntry_module_default.figures,
|
|
31957
32064
|
children: `~${format(context.usedTokens)} / ${format(context.contextWindow)}`
|
|
@@ -31961,11 +32068,11 @@ window.__ModuleLoader__.load({
|
|
|
31961
32068
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31962
32069
|
className: ContextUsageEntry_module_default.bar,
|
|
31963
32070
|
children: segments.map((segment) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31964
|
-
className: `${ContextUsageEntry_module_default.segment} ${
|
|
32071
|
+
className: segmentColors[segment.key] === void 0 ? ContextUsageEntry_module_default.segment : `${ContextUsageEntry_module_default.segment} ${segmentColors[segment.key]}`,
|
|
31965
32072
|
style: { width: `${segment.width}%` }
|
|
31966
32073
|
}, segment.key))
|
|
31967
32074
|
}),
|
|
31968
|
-
breakdown !== void 0 &&
|
|
32075
|
+
breakdown !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
|
|
31969
32076
|
className: ContextUsageEntry_module_default.rows,
|
|
31970
32077
|
children: [
|
|
31971
32078
|
"systemTokens",
|
|
@@ -31973,11 +32080,7 @@ window.__ModuleLoader__.load({
|
|
|
31973
32080
|
"messageTokens"
|
|
31974
32081
|
].map((key, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31975
32082
|
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])}` })]
|
|
32083
|
+
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
32084
|
}, key))
|
|
31982
32085
|
})
|
|
31983
32086
|
]
|
package/package.json
CHANGED
|
@@ -1,20 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
/* Mirror the host ContextMeter, including its theme and font-size tokens. */
|
|
2
|
+
.root {
|
|
3
|
+
flex: none;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.trigger {
|
|
8
|
+
border-radius: var(--dsw-radius-sm);
|
|
9
|
+
color: var(--dsw-alias-label-tertiary);
|
|
10
|
+
font-family: inherit;
|
|
11
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
12
|
+
font-variant-numeric: tabular-nums;
|
|
13
|
+
line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
background: transparent;
|
|
17
|
+
border: none;
|
|
18
|
+
flex: none;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 6px;
|
|
21
|
+
padding: 1px 8px;
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.trigger:hover,
|
|
26
|
+
.trigger[aria-expanded='true'] {
|
|
27
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
28
|
+
color: var(--dsw-alias-label-secondary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.track {
|
|
32
|
+
fill: none;
|
|
33
|
+
stroke: var(--dsw-alias-border-l3);
|
|
34
|
+
stroke-width: 2px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.fill {
|
|
38
|
+
fill: none;
|
|
39
|
+
stroke: var(--dsw-alias-label-tertiary);
|
|
40
|
+
stroke-width: 2px;
|
|
41
|
+
stroke-linecap: round;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.panel {
|
|
45
|
+
z-index: 1100;
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
border-radius: var(--dsw-radius-lg);
|
|
48
|
+
background: var(--dsw-specific-menu);
|
|
49
|
+
width: min(264px, calc(100vw - 24px));
|
|
50
|
+
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
51
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
52
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
53
|
+
color: var(--dsw-alias-label-secondary);
|
|
54
|
+
cursor: default;
|
|
55
|
+
border: 0;
|
|
56
|
+
padding: 12px;
|
|
57
|
+
font-size: 12px;
|
|
58
|
+
line-height: 20px;
|
|
59
|
+
position: fixed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.header {
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 6px;
|
|
65
|
+
display: flex;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.figures {
|
|
69
|
+
font-variant-numeric: tabular-nums;
|
|
70
|
+
color: var(--dsw-alias-label-primary);
|
|
71
|
+
margin-left: auto;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.percent {
|
|
76
|
+
color: var(--dsw-alias-label-primary);
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.headline { color: var(--dsw-alias-label-tertiary); }
|
|
81
|
+
.headline:empty { display: none; }
|
|
82
|
+
|
|
83
|
+
.bar {
|
|
84
|
+
corner-shape: round;
|
|
85
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
86
|
+
border-radius: 999px;
|
|
87
|
+
gap: 1px;
|
|
88
|
+
height: 4px;
|
|
89
|
+
margin: 10px 0 12px;
|
|
90
|
+
display: flex;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.segment {
|
|
95
|
+
background: var(--meter-tint, var(--dsw-alias-label-tertiary));
|
|
96
|
+
border-radius: 1px;
|
|
97
|
+
flex: none;
|
|
98
|
+
min-width: 2px;
|
|
99
|
+
height: 100%;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.swatch {
|
|
103
|
+
background: var(--meter-tint);
|
|
104
|
+
vertical-align: baseline;
|
|
105
|
+
border-radius: 2px;
|
|
106
|
+
width: 8px;
|
|
107
|
+
height: 8px;
|
|
108
|
+
margin-right: 6px;
|
|
109
|
+
display: inline-block;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.colorSystem { --meter-tint: var(--dsw-static-neutral-bluish-400); }
|
|
113
|
+
.colorTools { --meter-tint: #a78bfa; }
|
|
114
|
+
.colorMessages { --meter-tint: var(--dsw-static-blue-450); }
|
|
115
|
+
.rows { margin: 6px 0 0; }
|
|
116
|
+
|
|
117
|
+
.row {
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 12px;
|
|
121
|
+
padding: 2px 0;
|
|
122
|
+
display: flex;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.row dt { color: var(--dsw-alias-label-secondary); }
|
|
126
|
+
.row dd {
|
|
127
|
+
font-variant-numeric: tabular-nums;
|
|
128
|
+
color: var(--dsw-alias-label-primary);
|
|
129
|
+
margin: 0;
|
|
130
|
+
}
|
|
@@ -10,7 +10,7 @@ export const CONTEXT_LOCALE_NS = 'tokenTotalContext'
|
|
|
10
10
|
const keys = ['label', 'used', 'system', 'tools', 'messages', 'aria', 'thousand', 'million'] as const
|
|
11
11
|
export type ContextLocaleKey = typeof keys[number]
|
|
12
12
|
export const CONTEXT_LOCALES: Record<string, Record<ContextLocaleKey, string>> = {
|
|
13
|
-
zh: { label: '上下文', used: '
|
|
13
|
+
zh: { label: '上下文', used: '上下文已用', system: '系统提示词', tools: '工具定义', messages: '对话消息', aria: '上下文已用 {percent}', thousand: '{value}K', million: '{value}M' },
|
|
14
14
|
en: { label: 'Context', used: 'Context used', system: 'System', tools: 'Tools', messages: 'Conversation', aria: 'Context usage {percent}', thousand: '{value}K', million: '{value}M' },
|
|
15
15
|
'zh-TW': { label: '上下文', used: '已用上下文', system: '系統提示詞', tools: '工具', messages: '對話', aria: '上下文使用量 {percent}', thousand: '{value}千', million: '{value}百萬' },
|
|
16
16
|
}
|
|
@@ -50,12 +50,14 @@ export function ContextUsageEntry({ useProjection, t, installContextTakeover }:
|
|
|
50
50
|
if (context === null) return null
|
|
51
51
|
const percent = context.percent
|
|
52
52
|
const reading = `${percent}%`
|
|
53
|
-
const total = breakdown === undefined ? 0 : breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens
|
|
54
53
|
const segments = contextBreakdownSegments(percent, breakdown)
|
|
55
54
|
const format = (value: number): string => formatContextTokens(value, {
|
|
56
55
|
thousand: t('thousand'), million: t('million'),
|
|
57
56
|
})
|
|
58
|
-
const
|
|
57
|
+
const [headBefore = '', headAfter = ''] = t('aria', { percent: '\0' }).split('\0').map(part => part.trim())
|
|
58
|
+
const rowLabels = ['system', 'tools', 'messages'] as const
|
|
59
|
+
const rowColors = ['colorSystem', 'colorTools', 'colorMessages'] as const
|
|
60
|
+
const segmentColors: Record<string, string> = { systemTokens: css.colorSystem, toolsTokens: css.colorTools, messageTokens: css.colorMessages }
|
|
59
61
|
return (
|
|
60
62
|
<span ref={rootRef} className={css.root} data-dsh-token-total-stat="context">
|
|
61
63
|
<button type="button" className={css.trigger} aria-label={t('aria', { percent: reading })} aria-haspopup="dialog" aria-expanded={open} onClick={() => setOpen(!open)}>
|
|
@@ -68,13 +70,15 @@ export function ContextUsageEntry({ useProjection, t, installContextTakeover }:
|
|
|
68
70
|
{open && createPortal(
|
|
69
71
|
<div ref={panelRef} className={css.panel} style={position ?? MEASURE_STYLE} role="dialog" aria-label={t('used')}>
|
|
70
72
|
<div className={css.header}>
|
|
71
|
-
<span
|
|
73
|
+
<span className={css.headline}>{headBefore}</span>
|
|
74
|
+
<span className={css.percent}>{reading}</span>
|
|
75
|
+
<span className={css.headline}>{headAfter}</span>
|
|
72
76
|
<span className={css.figures}>{`~${format(context.usedTokens)} / ${format(context.contextWindow)}`}</span>
|
|
73
77
|
</div>
|
|
74
|
-
<div className={css.bar}>{segments.map(segment => <div key={segment.key} className={`${css.segment} ${
|
|
75
|
-
{breakdown !== undefined &&
|
|
78
|
+
<div className={css.bar}>{segments.map(segment => <div key={segment.key} className={segmentColors[segment.key] === undefined ? css.segment : `${css.segment} ${segmentColors[segment.key]}`} style={{ width: `${segment.width}%` }} />)}</div>
|
|
79
|
+
{breakdown !== undefined && <dl className={css.rows}>
|
|
76
80
|
{(['systemTokens', 'toolsTokens', 'messageTokens'] as const).map((key, index) => <div className={css.row} key={key}>
|
|
77
|
-
<dt><span className={`${css.swatch} ${css[
|
|
81
|
+
<dt><span className={`${css.swatch} ${css[rowColors[index]]}`} />{t(rowLabels[index])}</dt>
|
|
78
82
|
<dd>{`~${format(breakdown[key])}`}</dd>
|
|
79
83
|
</div>)}
|
|
80
84
|
</dl>}
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
declarations from `.root` onto `.anchor`. The tier stays off the plain
|
|
54
54
|
`.anchor` because the pre-split row must keep its own `.root` tier. */
|
|
55
55
|
.dockAnchor {
|
|
56
|
-
font-size:
|
|
56
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
57
57
|
line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
max-width: 100%;
|
|
66
66
|
padding: 1px 8px;
|
|
67
67
|
border: none;
|
|
68
|
-
border-radius:
|
|
69
|
-
background:
|
|
68
|
+
border-radius: var(--dsw-radius-sm);
|
|
69
|
+
background: none;
|
|
70
70
|
color: var(--dsw-alias-label-tertiary);
|
|
71
71
|
font: inherit;
|
|
72
72
|
font-variant-numeric: tabular-nums;
|
|
@@ -103,22 +103,13 @@ button.pill[aria-expanded='true'] {
|
|
|
103
103
|
margin: 0 6px;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
the
|
|
110
|
-
|
|
106
|
+
/* The live throughput reading sits inline exactly like the official figure:
|
|
107
|
+
no reserved width, no box of its own. The earlier `min-width: 10ch` slot kept
|
|
108
|
+
the centred row from shifting while the number grew, but it also left a
|
|
109
|
+
permanent gap before the separator — the official pill has no such slot, and
|
|
110
|
+
native parity wins over that jitter fix. */
|
|
111
111
|
.speed {
|
|
112
|
-
display: inline
|
|
113
|
-
min-width: 10ch;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* Marks a usage figure that now includes subagent spend. Deliberately quiet:
|
|
117
|
-
it rides inside the official pill's label and must not read as a badge or
|
|
118
|
-
compete with the token count beside it. */
|
|
119
|
-
.subagentMark {
|
|
120
|
-
color: var(--dsw-alias-label-tertiary);
|
|
121
|
-
font-variant-numeric: tabular-nums;
|
|
112
|
+
display: inline;
|
|
122
113
|
}
|
|
123
114
|
|
|
124
115
|
/* Portal dialog surface: fixed in the viewport, left/top supplied inline from
|
|
@@ -135,15 +126,15 @@ button.pill[aria-expanded='true'] {
|
|
|
135
126
|
width: max-content;
|
|
136
127
|
min-width: min(300px, calc(100vw - 24px));
|
|
137
128
|
max-width: min(440px, calc(100vw - 24px));
|
|
138
|
-
padding:
|
|
129
|
+
padding: 12px;
|
|
139
130
|
border: 0;
|
|
140
|
-
border-radius:
|
|
131
|
+
border-radius: var(--dsw-radius-lg);
|
|
141
132
|
background: var(--dsw-specific-menu);
|
|
142
133
|
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
143
134
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
144
135
|
box-shadow: var(--dsw-elevation-prominent);
|
|
145
|
-
font-size:
|
|
146
|
-
line-height:
|
|
136
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
137
|
+
line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
|
|
147
138
|
color: var(--dsw-alias-label-secondary);
|
|
148
139
|
cursor: default;
|
|
149
140
|
}
|
|
@@ -160,7 +151,7 @@ button.pill[aria-expanded='true'] {
|
|
|
160
151
|
|
|
161
152
|
/* Rule under the heading, above its rows. */
|
|
162
153
|
.dialogTitleRule {
|
|
163
|
-
margin-bottom:
|
|
154
|
+
margin-bottom: 8px;
|
|
164
155
|
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
165
156
|
}
|
|
166
157
|
|
|
@@ -185,7 +176,7 @@ button.pill[aria-expanded='true'] {
|
|
|
185
176
|
.dialogDetails {
|
|
186
177
|
display: grid;
|
|
187
178
|
grid-template-columns: minmax(76px, auto) minmax(0, 1fr);
|
|
188
|
-
gap:
|
|
179
|
+
gap: 4px 12px;
|
|
189
180
|
margin: 0;
|
|
190
181
|
color: var(--dsw-alias-label-tertiary);
|
|
191
182
|
}
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
* Which pair to register is read from the dock's entry ledger in
|
|
24
24
|
* src/client/index.tsx.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* Streaming throughput is always enabled; no stored preference gates it. The
|
|
27
|
+
* three columns are style-for-style the official ones: same tier, radius,
|
|
28
|
+
* colours, same dialog rows and the same integer cache-hit figure. The one
|
|
29
|
+
* difference is the middle figure's SCOPE — it totals the whole session tree
|
|
30
|
+
* (this agent plus every subagent it spawned), which the native projection
|
|
31
|
+
* cannot see.
|
|
29
32
|
*
|
|
30
33
|
* Streaming throughput replaces the gauge pill's speed
|
|
31
34
|
* figure with a live estimate while a step is still streaming and drops back to
|
|
@@ -44,14 +47,14 @@
|
|
|
44
47
|
*/
|
|
45
48
|
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
46
49
|
import { createPortal } from 'react-dom'
|
|
47
|
-
import { IconDatabaseOutline16, IconGaugeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
48
50
|
import type { PropsLocale, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
|
|
51
|
+
import { DatabaseIcon, GaugeIcon } from './stats-icons.tsx'
|
|
49
52
|
import { LOCALE_NS } from './locales.ts'
|
|
50
53
|
import { MEASURE_STYLE, useStatDialog, type StatDialogSeat } from './stat-dialog.ts'
|
|
51
54
|
import {
|
|
52
|
-
billedInputTokens,
|
|
55
|
+
billedInputTokens, cacheHitPercent, deriveStats,
|
|
53
56
|
estimateOutputTokens, formatDuration, formatTokensCompact, formatTokensPerSecond,
|
|
54
|
-
measuredTokenScale, StreamingRateSampler,
|
|
57
|
+
measuredTokenScale, StreamingRateSampler,
|
|
55
58
|
type ConversationNodeLike, type PartialAssistantLike, type UseProjection, type WindowStats,
|
|
56
59
|
} from './stats-line-core.ts'
|
|
57
60
|
import { formatTokens } from './format.ts'
|
|
@@ -112,18 +115,15 @@ function anchorClass(dock: boolean): string | undefined {
|
|
|
112
115
|
return dock ? `${css.anchor} ${css.dockAnchor}` : css.anchor
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
/** The separator + figure shape both speed arms render into the gauge pill
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* rendered even with no figure at all (an empty session has no official figure
|
|
120
|
-
* to fall back to until its first step settles), so the row's layout never
|
|
121
|
-
* depends on whether a number is available. */
|
|
118
|
+
/** The separator + figure shape both speed arms render into the gauge pill:
|
|
119
|
+
* exactly the official pill's inline shape — separator and figure appear only
|
|
120
|
+
* when there is a figure, and the figure occupies no reserved width, so the
|
|
121
|
+
* row reads and reflows like the native one. */
|
|
122
122
|
function SpeedFigure({ text }: { text: string | null }) {
|
|
123
123
|
return (
|
|
124
124
|
<>
|
|
125
125
|
{text !== null && <span className={css.sep} aria-hidden>·</span>}
|
|
126
|
-
<span className={css.speed} data-stats-speed>{text}</span>
|
|
126
|
+
{text !== null && <span className={css.speed} data-stats-speed>{text}</span>}
|
|
127
127
|
</>
|
|
128
128
|
)
|
|
129
129
|
}
|
|
@@ -290,7 +290,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
|
|
|
290
290
|
return (
|
|
291
291
|
<span className={anchorClass(dock)}>
|
|
292
292
|
<span className={css.pill}>
|
|
293
|
-
<
|
|
293
|
+
<GaugeIcon />
|
|
294
294
|
{label}
|
|
295
295
|
</span>
|
|
296
296
|
</span>
|
|
@@ -306,7 +306,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
|
|
|
306
306
|
aria-label={officialTps === null ? counts : counts + ' · ' + officialTps}
|
|
307
307
|
onClick={() => { setOpen(!open) }}
|
|
308
308
|
>
|
|
309
|
-
<
|
|
309
|
+
<GaugeIcon />
|
|
310
310
|
{label}
|
|
311
311
|
</button>
|
|
312
312
|
{open && createPortal(
|
|
@@ -320,7 +320,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
|
|
|
320
320
|
>
|
|
321
321
|
<div className={css.dialogTitle}>
|
|
322
322
|
<span className={css.dialogTitleLabel}>
|
|
323
|
-
<
|
|
323
|
+
<GaugeIcon />
|
|
324
324
|
{t('stats.dialog.title')}
|
|
325
325
|
</span>
|
|
326
326
|
</div>
|
|
@@ -372,13 +372,15 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
|
|
|
372
372
|
}) {
|
|
373
373
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog)
|
|
374
374
|
const display = sessionUsageForDisplay(usage, tree?.tree ?? null)
|
|
375
|
-
const subagentTotals = display.subagents
|
|
376
|
-
const showSubagents = display.subagentSessions > 0
|
|
377
375
|
const billed = display.total
|
|
378
376
|
// Same aggregate as the official pill total: every prompt-side billing bucket plus output.
|
|
379
377
|
const total = billedTotal(billed)
|
|
380
378
|
const totalText = t('message.turnUsage.count', { count: formatTokensCompact(total) })
|
|
381
|
-
|
|
379
|
+
// Native formatting, one consistent scope: the ratio comes from the same
|
|
380
|
+
// aggregate the headline uses, so "33M tok · 87%" can never contradict the
|
|
381
|
+
// number printed beside it. The integer shape and its >99% guard are the
|
|
382
|
+
// official ones, untouched.
|
|
383
|
+
const cacheHit = cacheHitPercent(billed)
|
|
382
384
|
const cacheHitText = cacheHit !== null ? t('stats.cacheHit', { percent: cacheHit }) : null
|
|
383
385
|
const exactCount = (value: number): string =>
|
|
384
386
|
t('message.turnUsage.count', { count: formatTokens(value) })
|
|
@@ -392,19 +394,13 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
|
|
|
392
394
|
aria-label={cacheHitText === null ? totalText : totalText + ' · ' + cacheHitText}
|
|
393
395
|
onClick={() => { setOpen(!open) }}
|
|
394
396
|
>
|
|
395
|
-
<
|
|
397
|
+
<DatabaseIcon />
|
|
396
398
|
<span className={css.label}>
|
|
397
399
|
{totalText}
|
|
398
|
-
{showSubagents && (
|
|
399
|
-
<span className={css.sep} aria-hidden>·</span>
|
|
400
|
-
)}
|
|
401
|
-
{showSubagents && (
|
|
402
|
-
<span className={css.subagentMark} title={t('stats.dialog.subagents')}>
|
|
403
|
-
+{tree?.tree?.subagentSessions ?? 0}
|
|
404
|
-
</span>
|
|
405
|
-
)}
|
|
406
400
|
{cacheHitText !== null && (
|
|
407
401
|
<>
|
|
402
|
+
{/* Separator only when the second figure exists: an empty span
|
|
403
|
+
would still take the label's gap and read as a trailing dot. */}
|
|
408
404
|
<span className={css.sep} aria-hidden>·</span>
|
|
409
405
|
{cacheHitText}
|
|
410
406
|
</>
|
|
@@ -422,12 +418,14 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
|
|
|
422
418
|
>
|
|
423
419
|
<div className={css.dialogTitle}>
|
|
424
420
|
<span className={css.dialogTitleLabel}>
|
|
425
|
-
<
|
|
421
|
+
<DatabaseIcon />
|
|
426
422
|
{t('stats.dialog.usageTitle')}
|
|
427
423
|
</span>
|
|
428
424
|
<span className={css.dialogTitleValue}>{exactCount(total)}</span>
|
|
429
425
|
</div>
|
|
430
426
|
<div className={css.dialogTitleRule} aria-hidden />
|
|
427
|
+
{/* Exactly the official dialog's rows, in the official order: the
|
|
428
|
+
takeover adds the tree to the TOTAL, not to the breakdown. */}
|
|
431
429
|
<dl className={css.dialogDetails} data-session-stats-usage>
|
|
432
430
|
{cacheHit !== null && (
|
|
433
431
|
<>
|
|
@@ -439,28 +437,14 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
|
|
|
439
437
|
<dd>{exactCount(billed.uncachedInputTokens)}</dd>
|
|
440
438
|
<dt>{t('message.turnUsage.cacheRead')}</dt>
|
|
441
439
|
<dd>{exactCount(billed.cacheReadTokens)}</dd>
|
|
442
|
-
|
|
443
|
-
<dd>{exactCount(billed.cacheWriteTokens)}</dd>
|
|
444
|
-
<dt>{t('stats.cacheMiss')}</dt>
|
|
445
|
-
<dd>{exactCount(tokenBreakdown(billed).cacheMiss)}</dd>
|
|
446
|
-
<dt>{t('message.turnUsage.output')}</dt>
|
|
447
|
-
<dd>{exactCount(billed.outputTokens)}</dd>
|
|
448
|
-
{/* The split that the native figure cannot show: this session's own
|
|
449
|
-
main agent against every subagent it delegated to. */}
|
|
450
|
-
{showSubagents && (
|
|
440
|
+
{billed.cacheWriteTokens !== 0 && (
|
|
451
441
|
<>
|
|
452
|
-
<dt>{t('
|
|
453
|
-
<dd>{exactCount(
|
|
454
|
-
<dt>{t('stats.dialog.subagents')}</dt>
|
|
455
|
-
<dd>
|
|
456
|
-
{exactCount(billedTotal(subagentTotals))}
|
|
457
|
-
{' · '}
|
|
458
|
-
{t('stats.dialog.subagentCount', { count: tree?.tree?.subagentSessions ?? 0 })}
|
|
459
|
-
</dd>
|
|
460
|
-
<dt>{t('stats.dialog.treeTotal')}</dt>
|
|
461
|
-
<dd>{exactCount(billedTotal(billed))}</dd>
|
|
442
|
+
<dt>{t('message.turnUsage.cacheWrite')}</dt>
|
|
443
|
+
<dd>{exactCount(billed.cacheWriteTokens)}</dd>
|
|
462
444
|
</>
|
|
463
445
|
)}
|
|
446
|
+
<dt>{t('message.turnUsage.output')}</dt>
|
|
447
|
+
<dd>{exactCount(billed.outputTokens)}</dd>
|
|
464
448
|
</dl>
|
|
465
449
|
</div>,
|
|
466
450
|
document.body,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stat-pill glyphs.
|
|
3
|
+
*
|
|
4
|
+
* The shell freezes its primitives table, and that table's icon names moved
|
|
5
|
+
* with the host line: 0.2.1-rc.2 exports `IconGaugeOutlineRegular` /
|
|
6
|
+
* `IconDatabaseOutlineRegular` (plus the `Medium` weight) and no longer
|
|
7
|
+
* exports the `…Outline16` names this plugin used to import. A missing named
|
|
8
|
+
* export is `undefined` at runtime, and React then throws "Element type is
|
|
9
|
+
* invalid" on the entry's first render — the shell's slot boundary catches it,
|
|
10
|
+
* ABDICATES the entry, and the shadowed official pill silently returns. That
|
|
11
|
+
* failure mode is invisible in types (the package's own declarations still
|
|
12
|
+
* describe the old names) and looks exactly like "the plugin did nothing".
|
|
13
|
+
*
|
|
14
|
+
* So the glyph is resolved from the runtime table by preference order and
|
|
15
|
+
* falls back to an inline drawing of the same shape when no name exists, which
|
|
16
|
+
* keeps one published build rendering on every host line.
|
|
17
|
+
*/
|
|
18
|
+
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
|
|
19
|
+
|
|
20
|
+
type IconComponent = () => JSX.Element
|
|
21
|
+
|
|
22
|
+
const table = primitives as unknown as Record<string, unknown>
|
|
23
|
+
|
|
24
|
+
/** First exported function component among `names`, or null when the shell
|
|
25
|
+
* exports none of them (an older or newer icon roster). */
|
|
26
|
+
function resolveIcon(...names: readonly string[]): IconComponent | null {
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
const candidate = table[name]
|
|
29
|
+
if (typeof candidate === 'function') return candidate as IconComponent
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const resolvedGauge = resolveIcon(
|
|
35
|
+
'IconGaugeOutlineRegular',
|
|
36
|
+
'IconGaugeOutlineMedium',
|
|
37
|
+
'IconGaugeOutline16',
|
|
38
|
+
)
|
|
39
|
+
const resolvedDatabase = resolveIcon(
|
|
40
|
+
'IconDatabaseOutlineRegular',
|
|
41
|
+
'IconDatabaseOutlineMedium',
|
|
42
|
+
'IconDatabaseOutline16',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
/** Gauge glyph for the activity pill (counts + throughput). */
|
|
46
|
+
export function GaugeIcon(): JSX.Element {
|
|
47
|
+
if (resolvedGauge !== null) {
|
|
48
|
+
const Icon = resolvedGauge
|
|
49
|
+
return <Icon />
|
|
50
|
+
}
|
|
51
|
+
return (
|
|
52
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
53
|
+
<path
|
|
54
|
+
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"
|
|
55
|
+
fill="none"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
strokeWidth="1.3"
|
|
58
|
+
strokeLinecap="round"
|
|
59
|
+
/>
|
|
60
|
+
<circle cx="8" cy="8" r="1" fill="currentColor" />
|
|
61
|
+
</svg>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Database glyph for the token-usage pill. */
|
|
66
|
+
export function DatabaseIcon(): JSX.Element {
|
|
67
|
+
if (resolvedDatabase !== null) {
|
|
68
|
+
const Icon = resolvedDatabase
|
|
69
|
+
return <Icon />
|
|
70
|
+
}
|
|
71
|
+
return (
|
|
72
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
73
|
+
<ellipse cx="8" cy="3.6" rx="4.6" ry="1.9" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
74
|
+
<path
|
|
75
|
+
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"
|
|
76
|
+
fill="none"
|
|
77
|
+
stroke="currentColor"
|
|
78
|
+
strokeWidth="1.3"
|
|
79
|
+
strokeLinecap="round"
|
|
80
|
+
/>
|
|
81
|
+
</svg>
|
|
82
|
+
)
|
|
83
|
+
}
|