dsh-token-total 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -31281,16 +31281,25 @@ window.__ModuleLoader__.load({
31281
31281
  const CJK_TOKENS_PER_CHAR = .6;
31282
31282
  const ASCII_TOKENS_PER_CHAR = .3;
31283
31283
  /**
31284
- * Shortest observable decode window before a live rate is published, in ms
31285
- * (the same floor MiMo-Code's sidebar tps uses). Below it the first chunk's
31286
- * burst would read as hundreds of tok/s; the sampler also publishes its FIRST
31287
- * reading at exactly this mark rather than at the next steady cadence.
31284
+ * Shortest observable decode window before a live rate is published, in ms.
31285
+ * Below it the first chunk's burst would read as hundreds of tok/s; the
31286
+ * sampler also publishes its FIRST reading at exactly this mark rather than at
31287
+ * the next steady cadence.
31288
+ *
31289
+ * The floor is deliberately short — 200 ms, down from the 500 ms MiMo-Code's
31290
+ * sidebar tps holds — because every step opens a window of its own, and at
31291
+ * 500 ms a step that finishes inside the floor never published at all: a run
31292
+ * of short steps (one burst of tool-call arguments, then settle) kept showing
31293
+ * a figure measured somewhere else while the screen was full of live output.
31294
+ * At this length the opening burst is judged by the plausibility check below
31295
+ * rather than by waiting it out, and the smoothing constant still carries the
31296
+ * first reading only part of the way into an established figure.
31288
31297
  *
31289
31298
  * The floor is a publishing gate, NOT a rejection gate: it is applied after the
31290
31299
  * plausibility check below, so a backlog frame can never dodge that check by
31291
31300
  * arriving early (see {@link StreamingRateSampler.observe}).
31292
31301
  */
31293
- const MIN_STREAM_WINDOW_MS = 500;
31302
+ const MIN_STREAM_WINDOW_MS = 200;
31294
31303
  /**
31295
31304
  * Longest window a live rate may span, in ms. This is the bound the previous
31296
31305
  * cumulative quotient lacked: any artifact inside the window — a frame the UI
@@ -31652,8 +31661,13 @@ window.__ModuleLoader__.load({
31652
31661
  * cannot see.
31653
31662
  *
31654
31663
  * Streaming throughput replaces the gauge pill's speed
31655
- * figure with a live estimate while a step is still streaming and drops back to
31656
- * the official session figure the moment it settles. That subscription lives in
31664
+ * figure with a live estimate of what is happening now. The figure is the
31665
+ * streaming step's measured rate while one streams and the LAST measured rate
31666
+ * once it settles — a session average describes the whole session's past, and
31667
+ * it cannot match the pace of the answer on screen, which is what users read
31668
+ * as the pill disagreeing with the stream in front of them. The official
31669
+ * session figure only shows on a session that has never streamed. That
31670
+ * subscription lives in
31657
31671
  * its own child component so stream deltas never re-render the pills. The live
31658
31672
  * figure itself is measured by a bounded sliding window over observed token
31659
31673
  * growth (stats-line-core's StreamingRateSampler): the earlier cumulative
@@ -31740,17 +31754,19 @@ window.__ModuleLoader__.load({
31740
31754
  * The gauge pill's always-on streaming speed slot.
31741
31755
  *
31742
31756
  * It subscribes to `legacy.partial` itself so per-frame deltas re-render only
31743
- * this child, not the surrounding pills. Until the streaming step has a
31744
- * measurable window the official session
31745
- * figure stays on screen, so the slot is never empty and the handover at
31746
- * settle is a plain value change.
31757
+ * this child, not the surrounding pills. The figure it carries is the CURRENT
31758
+ * stream's measured rate, and it stays that rate once the step settles:
31759
+ * tool execution and the gap between steps happen inside the same answer, and
31760
+ * the session average this slot used to fall back to there is a figure from
31761
+ * the whole session's past — a number that cannot match the pace of the answer
31762
+ * on screen, which is what reads as "not the real-time speed". The official
31763
+ * session figure is only a session's opening state, shown until the first
31764
+ * window measures anything.
31747
31765
  *
31748
31766
  * One window covers a whole step, across every output kind the accumulator
31749
31767
  * carries — reasoning, answer text, and Tool-call arguments — because the
31750
31768
  * provider bills all three as completion tokens and a step emits them in
31751
- * sequence. Between steps the partial is gone (the step's assistant/message
31752
- * settles it and the next step has not begun), so Tool execution shows the
31753
- * official figure until the next step opens its own window.
31769
+ * sequence.
31754
31770
  *
31755
31771
  * Sampling happens in a layout effect and the figure it yields is published as
31756
31772
  * state, so no render reads a clock or mutates the window: a render React starts
@@ -31768,10 +31784,7 @@ window.__ModuleLoader__.load({
31768
31784
  rate: new StreamingRateSampler()
31769
31785
  };
31770
31786
  const rate = sampler.current.rate;
31771
- const [published, setPublished] = (0, react.useState)({
31772
- key: null,
31773
- rate: null
31774
- });
31787
+ const [reading, setReading] = (0, react.useState)(null);
31775
31788
  const scaleRef = (0, react.useRef)(scale);
31776
31789
  const [beat, setBeat] = (0, react.useState)(0);
31777
31790
  (0, react.useLayoutEffect)(() => {
@@ -31781,10 +31794,7 @@ window.__ModuleLoader__.load({
31781
31794
  }
31782
31795
  rate.observe(tokens, Date.now());
31783
31796
  const next = rate.ratePerSecond();
31784
- setPublished((previous) => previous.key === key && previous.rate === next ? previous : {
31785
- key,
31786
- rate: next
31787
- });
31797
+ if (next !== null) setReading((previous) => previous === next ? previous : next);
31788
31798
  }, [
31789
31799
  tokens,
31790
31800
  key,
@@ -31802,7 +31812,6 @@ window.__ModuleLoader__.load({
31802
31812
  clearInterval(handle);
31803
31813
  };
31804
31814
  }, [streaming]);
31805
- const reading = published.key === key ? published.rate : null;
31806
31815
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeedFigure, { text: reading === null ? fallback : t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(reading) }) });
31807
31816
  }
31808
31817
  function TimePill({ stats, t, dialog, useChat, scale, dock }) {
@@ -32707,24 +32716,55 @@ window.__ModuleLoader__.load({
32707
32716
  };
32708
32717
  }
32709
32718
  //#endregion
32710
- //#region src/client/context-window-core.ts
32711
- /** Legend order, also the color assignment order (see the CSS module). */
32712
- const CONTEXT_CATEGORY_ORDER = [
32713
- "system",
32714
- "tools",
32715
- "memory",
32716
- "files",
32717
- "results",
32718
- "conversation"
32719
+ //#region src/usage-buckets.ts
32720
+ const USAGE_BUCKET_ORDER = [
32721
+ "systemPrompt",
32722
+ "user",
32723
+ "skill",
32724
+ "toolResult",
32725
+ "toolDef",
32726
+ "injected",
32727
+ "assistant",
32728
+ "output"
32719
32729
  ];
32720
- /** Per-category token totals in legend order, empty categories dropped. */
32721
- function contextCategoryTotals(items) {
32730
+ /** The buckets that describe the live window (output never occupies it). */
32731
+ const CONTEXT_BUCKET_ORDER = USAGE_BUCKET_ORDER.filter((key) => key !== "output");
32732
+ //#endregion
32733
+ //#region src/client/context-window-core.ts
32734
+ /** Which legend bucket one window row belongs to.
32735
+ *
32736
+ * The row categories are the treemap's (they group files separately); the
32737
+ * cards speak the eight-bucket legend instead. A folded `rest` row lost its
32738
+ * kind, so a conversation tail lands with the assistant — the larger half of
32739
+ * what a turn replays.
32740
+ */
32741
+ function contextBucketOf(item) {
32742
+ switch (item.category) {
32743
+ case "system": return "systemPrompt";
32744
+ case "tools": return "toolDef";
32745
+ case "memory": return item.kind === "skill-catalog" ? "skill" : "injected";
32746
+ case "files":
32747
+ case "results": return "toolResult";
32748
+ case "conversation": return item.kind === "user" ? "user" : "assistant";
32749
+ default: return "injected";
32750
+ }
32751
+ }
32752
+ /** The live window folded into the same legend the spend card prints, in
32753
+ * legend order and keeping zero buckets so both cards stay aligned. */
32754
+ function contextBucketTotals(items) {
32722
32755
  const totals = /* @__PURE__ */ new Map();
32723
- for (const item of items) totals.set(item.category, (totals.get(item.category) ?? 0) + item.tokens);
32724
- return CONTEXT_CATEGORY_ORDER.map((key) => ({
32756
+ for (const item of items) {
32757
+ const key = contextBucketOf(item);
32758
+ totals.set(key, (totals.get(key) ?? 0) + item.tokens);
32759
+ }
32760
+ return CONTEXT_BUCKET_ORDER.map((key) => ({
32725
32761
  key,
32726
32762
  tokens: totals.get(key) ?? 0
32727
- })).filter((row) => row.tokens > 0);
32763
+ }));
32764
+ }
32765
+ /** What a share of the whole is, for the legend column. */
32766
+ function bucketPercent(tokens, total) {
32767
+ return total > 0 ? tokens / total * 100 : 0;
32728
32768
  }
32729
32769
  /** Window occupancy in whole percent, or null when the host never reported the
32730
32770
  * model's window (the panel then says "unknown" instead of guessing one). */
@@ -32743,98 +32783,10 @@ window.__ModuleLoader__.load({
32743
32783
  estimateDiffers: context.promptTokens !== null && gap > .15
32744
32784
  };
32745
32785
  }
32746
- /** Aspect ratio of the worst tile in a row of `sum` area, given the strip's
32747
- * short side — the squarified-treemap cost function. */
32748
- function worstRatio(sum, max, min, side) {
32749
- const squared = sum * sum;
32750
- const sideSquared = side * side;
32751
- return Math.max(sideSquared * max / squared, squared / (sideSquared * min));
32752
- }
32753
- const round = (value) => Math.round(value * 100) / 100;
32754
- /**
32755
- * Squarified treemap (Bruls, Huizing & van Wijk): tile `width`×`height` with
32756
- * one block per positive weight, proportional to its tokens and as close to
32757
- * square as the greedy row packing allows. Blocks come back in the input's
32758
- * descending-weight order, covering the whole area.
32759
- */
32760
- function treemapLayout(rows, width, height) {
32761
- const ordered = rows.filter((row) => row.tokens > 0 && Number.isFinite(row.tokens)).sort((a, b) => b.tokens - a.tokens);
32762
- if (ordered.length === 0 || !(width > 0) || !(height > 0)) return [];
32763
- const total = ordered.reduce((sum, row) => sum + row.tokens, 0);
32764
- const scale = width * height / total;
32765
- const areas = ordered.map((row) => ({
32766
- key: row.key,
32767
- area: row.tokens * scale
32768
- }));
32769
- const boxes = [];
32770
- let x = 0;
32771
- let y = 0;
32772
- let w = width;
32773
- let h = height;
32774
- let index = 0;
32775
- while (index < areas.length) {
32776
- const side = Math.min(w, h);
32777
- if (!(side > 0)) break;
32778
- let end = index + 1;
32779
- let sum = areas[index].area;
32780
- let max = sum;
32781
- let min = sum;
32782
- let worst = worstRatio(sum, max, min, side);
32783
- while (end < areas.length) {
32784
- const area = areas[end].area;
32785
- const nextSum = sum + area;
32786
- const nextMax = Math.max(max, area);
32787
- const nextMin = Math.min(min, area);
32788
- const nextWorst = worstRatio(nextSum, nextMax, nextMin, side);
32789
- if (nextWorst > worst) break;
32790
- sum = nextSum;
32791
- max = nextMax;
32792
- min = nextMin;
32793
- worst = nextWorst;
32794
- end++;
32795
- }
32796
- const thickness = sum / side;
32797
- if (!(thickness > 0)) break;
32798
- let offset = 0;
32799
- for (let k = index; k < end; k++) {
32800
- const length = areas[k].area / thickness;
32801
- const box = w >= h ? {
32802
- key: areas[k].key,
32803
- x,
32804
- y: y + offset,
32805
- width: thickness,
32806
- height: length
32807
- } : {
32808
- key: areas[k].key,
32809
- x: x + offset,
32810
- y,
32811
- width: length,
32812
- height: thickness
32813
- };
32814
- boxes.push({
32815
- key: box.key,
32816
- x: round(box.x),
32817
- y: round(box.y),
32818
- width: round(box.width),
32819
- height: round(box.height)
32820
- });
32821
- offset += length;
32822
- }
32823
- if (w >= h) {
32824
- x += thickness;
32825
- w -= thickness;
32826
- } else {
32827
- y += thickness;
32828
- h -= thickness;
32829
- }
32830
- index = end;
32831
- }
32832
- return boxes;
32833
- }
32834
32786
  //#endregion
32835
- //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/ContextWindowPanel.module.css?inline
32836
- const css$1 = "/* The context-window panel: magpie's shape on this page's surfaces — a header\n * with the model and a live pill, the used total beside the newest answer's\n * prompt-cache split, a treemap of what occupies the window, and one legend row\n * naming the six buckets. Category colors live as custom properties on the tone\n * classes so the tile and the legend dot share one palette. */\n.panel_1441117854 { background:#fff; border:1px solid #e4e7ee; border-radius:12px; margin:0 0 16px; overflow:hidden; container-type:inline-size; }\n.head_1441117854 { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:12px 16px 0; }\n.headCollapsed_1441117854 { padding-bottom:12px; }\n.toggle_1441117854 { display:flex; align-items:center; gap:6px; min-width:0; flex:none; background:none; border:0; padding:0; font:inherit; color:#394354; cursor:pointer; }\n.toggle_1441117854 h2 { font-size:13px; font-weight:600; margin:0; white-space:nowrap; }\n.chevron_1441117854 { flex:none; color:#99a1af; transition:transform .2s; }\n.chevronOpen_1441117854 { transform:rotate(90deg); }\n.headLead_1441117854 { display:flex; align-items:center; gap:8px; min-width:0; flex:1 1 auto; }\n/* Folded digest: the used/window figures plus a strip whose whole length is the\n * model's window — the tinted head is what the six buckets occupy, and the grey\n * track behind it is the room still free. A collapsed panel answers \"how full,\n * with what, and how much is left\" while it refreshes. */\n.digest_1441117854 { display:flex; align-items:center; gap:8px; min-width:0; flex:1 1 auto; }\n/* The figures never shrink — a truncated count is worse than no strip — so the\n * strip absorbs the shortfall and steps aside once it can no longer be read. */\n.digestTotal_1441117854 { flex:none; white-space:nowrap; font-size:11px; color:#6f7889; font-variant-numeric:tabular-nums; }\n/* The track is the free room: `#eef0f4` shows through after the used share. */\n.digestBar_1441117854 { display:flex; flex:1 1 auto; min-width:0; max-width:320px; height:8px; border-radius:999px; overflow:hidden; background:#e8ebf1; }\n.digestSeg_1441117854 { height:100%; flex:none; background:var(--ctx); transition:width .35s; }\n.headRight_1441117854 { display:flex; align-items:center; gap:8px; min-width:0; flex:none; }\n.model_1441117854 { min-width:0; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; color:#8b93a4; }\n.livePill_1441117854 { display:inline-flex; align-items:center; gap:4px; flex:none; padding:1px 8px 1px 6px; border-radius:999px; background:#e9f8f1; color:#128a63; font-size:10px; font-weight:500; }\n.liveDot_1441117854 { width:5px; height:5px; border-radius:50%; background:#18a97b; box-shadow:0 0 0 2px #d6f2e5; }\n.liveTime_1441117854 { font-variant-numeric:tabular-nums; opacity:.85; }\n.toggle_1441117854:focus-visible { outline:2px solid #2ab1c6; outline-offset:2px; }\n.body_1441117854 { padding:10px 16px 14px; }\n.body_1441117854[hidden] { display:none; }\n.summary_1441117854 { display:grid; grid-template-columns:minmax(0,1fr) minmax(180px,260px); align-items:end; gap:18px; padding-bottom:12px; }\n.used_1441117854 { display:flex; flex-direction:column; gap:1px; min-width:0; }\n.label_1441117854 { font-size:11px; color:#8790a3; }\n.total_1441117854 { font-size:28px; font-weight:650; line-height:1.15; letter-spacing:-.6px; color:#1b2130; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.total_1441117854 small { margin-left:4px; font-size:11px; font-weight:500; letter-spacing:0; color:#98a0ae; }\n.window_1441117854 { font-size:11px; color:#939aab; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.cache_1441117854 { display:flex; flex-direction:column; gap:6px; min-width:0; }\n.cacheBar_1441117854 { display:flex; width:100%; height:5px; border-radius:999px; overflow:hidden; background:#eef0f4; }\n.cacheHit_1441117854 { height:100%; background:#2ec4a0; }\n.cacheMiss_1441117854 { height:100%; background:#dfe3ea; }\n.cacheEmpty_1441117854 { display:block; width:100%; height:100%; background:repeating-linear-gradient(90deg,#e6e9ef 0 3px,transparent 3px 7px); }\n.cacheLegend_1441117854 { display:flex; flex-wrap:wrap; gap:2px 10px; font-size:10px; color:#8b93a4; font-variant-numeric:tabular-nums; }\n.cacheLegend_1441117854 i { display:inline-block; width:6px; height:6px; margin-right:4px; border-radius:50%; }\n.cacheHitDot_1441117854 { background:#2ec4a0; }\n.cacheMissDot_1441117854 { background:#dfe3ea; }\n.cachePending_1441117854 { color:#9aa3b3; }\n.note_1441117854 { margin:0 0 10px; padding:5px 8px; border:1px solid #f2e4c6; border-radius:6px; background:#fdf7ea; font-size:10.5px; line-height:1.5; color:#8a6a2a; font-variant-numeric:tabular-nums; }\n.map_1441117854 { position:relative; height:268px; border-radius:9px; overflow:hidden; }\n/* Slots glide into their new geometry when a refresh re-writes the estimate,\n * so a change in the data reads as motion instead of a silent repaint. */\n.slot_1441117854 { position:absolute; box-sizing:border-box; padding:1.5px; transition:left .35s, top .35s, width .35s, height .35s; }\n.tile_1441117854 { display:flex; flex-direction:column; gap:1px; box-sizing:border-box; width:100%; height:100%; padding:5px 6px; border:1px solid var(--ctx-line); border-radius:7px; background:var(--ctx-soft); color:var(--ctx-ink); overflow:hidden; }\n.tileLabel_1441117854 { font-size:11px; font-weight:600; line-height:1.25; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\n.tileSize_1441117854 { font-size:10px; opacity:.72; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-variant-numeric:tabular-nums; }\n/* The hover card: exact figures a tile has no room to print. */\n.tip_1441117854 { position:absolute; z-index:2; box-sizing:border-box; width:220px; padding:8px 10px; border:1px solid #e0e4ec; border-radius:8px; background:#fff; box-shadow:0 8px 20px rgba(23,32,54,.12); font-size:11px; line-height:1.55; pointer-events:none; }\n.tipName_1441117854 { font-weight:600; color:#1b2130; overflow-wrap:anywhere; }\n.tipMeta_1441117854 { color:#6f7889; font-variant-numeric:tabular-nums; }\n.tipShare_1441117854 { color:#4a5567; font-variant-numeric:tabular-nums; }\n.legend_1441117854 { display:flex; flex-wrap:wrap; gap:5px 14px; list-style:none; margin:0; padding:10px 0 0; border-top:1px solid #f2f4f8; margin-top:10px; font-size:11px; color:#6f7889; }\n.legend_1441117854 li { display:flex; align-items:center; gap:5px; }\n.legendName_1441117854 { white-space:nowrap; }\n.legendValue_1441117854 { color:#98a0ae; font-variant-numeric:tabular-nums; }\n.swatch_1441117854 { display:inline-block; flex:none; box-sizing:border-box; width:9px; height:9px; border-radius:3px; background:var(--ctx-soft); background:color-mix(in srgb, var(--ctx) 50%, var(--ctx-soft)); border:1px solid var(--ctx-line); }\n.empty_1441117854 { margin:0; padding:24px 0; text-align:center; font-size:12px; color:#9aa2b1; }\n/* Category palette: `--ctx` is the accent tint, `--ctx-soft` the tile fill (and\n * the legend swatch, so the dots match the tiles exactly), `--ctx-ink` the tile\n * text, `--ctx-line` its hairline border. */\n.toneSystem_1441117854 { --ctx:#a346e6; --ctx-soft:#f6ecfd; --ctx-ink:#6c2e9e; --ctx-line:#ecdafa; }\n.toneTools_1441117854 { --ctx:#4f79f6; --ctx-soft:#eaf0fe; --ctx-ink:#3a5aa8; --ctx-line:#d9e3fb; }\n.toneMemory_1441117854 { --ctx:#8fb21e; --ctx-soft:#f1f5d8; --ctx-ink:#5a7012; --ctx-line:#dfe8bd; }\n.toneFiles_1441117854 { --ctx:#18a97b; --ctx-soft:#e6f6ee; --ctx-ink:#1c7a56; --ctx-line:#d2ecdf; }\n.toneResults_1441117854 { --ctx:#e8853f; --ctx-soft:#fdeee3; --ctx-ink:#a35d24; --ctx-line:#f7ddc8; }\n.toneConversation_1441117854 { --ctx:#c2327f; --ctx-soft:#fbe9f3; --ctx-ink:#8c2259; --ctx-line:#f2d3e4; }\n@media (max-width:700px) {\n .head_1441117854 { padding:11px 12px 0; }\n .headCollapsed_1441117854 { padding-bottom:11px; }\n .body_1441117854 { padding:9px 12px 12px; }\n .summary_1441117854 { grid-template-columns:minmax(0,1fr); gap:10px; }\n .total_1441117854 { font-size:24px; }\n .map_1441117854 { height:220px; }\n .model_1441117854 { display:none; }\n}\n/* Container queries measure this card, not the page — the panel sits in a\n * column, so below ~620px the strip is no longer readable and steps aside; the\n * figures keep every number, and the tooltip still holds the split. */\n@container (max-width:620px) {\n .digestBar_1441117854 { display:none; }\n .model_1441117854 { display:none; }\n}\n@media (prefers-reduced-motion:reduce) {\n .chevron_1441117854 { transition:none; }\n .slot_1441117854 { transition:none; }\n .digestSeg_1441117854 { transition:none; }\n}\n";
32837
- const tagId$1 = "dsh-token-total/ContextWindowPanel.module.css";
32787
+ //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/UsageCards.module.css?inline
32788
+ const css$1 = "/* The two live cards that replaced the context-window panel: the window's\n * current composition on the left, the session's spend composition on the\n * right. Surfaces ride the host semantic tokens (`--dsw-alias-*`) like every\n * other card on this page, so light/dark flip for free; only the eight bucket\n * hues are this plugin's own (they match the reference design and the\n * dashboard's chart accents). Each bucket sets `--bucket`, and the swatch, ring\n * segment, bar segment and value text all read that one variable. */\n.grid_673061119 { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:18px; margin:0 0 18px; align-items:stretch; }\n.card_673061119 { min-width:0; display:flex; flex-direction:column; background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l2); border-radius:12px; overflow:hidden; }\n.head_673061119 { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:42px; padding:12px 16px 0; }\n.title_673061119 { margin:0; font-size:13px; font-weight:600; color:var(--dsw-alias-label-primary); white-space:nowrap; }\n.model_673061119 { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; color:var(--dsw-alias-label-tertiary); }\n.cacheBadge_673061119 { flex:none; font-size:11px; color:var(--dsw-alias-label-tertiary); white-space:nowrap; }\n.cacheBadge_673061119 b { margin-left:2px; font-weight:600; color:var(--dsw-alias-state-success-primary); font-variant-numeric:tabular-nums; }\n.body_673061119 { flex:1 1 auto; padding:12px 16px 16px; }\n/* ── Left card: the live window ─────────────────────────────────────────── */\n.totalRow_673061119 { display:flex; align-items:baseline; justify-content:space-between; gap:12px; }\n.total_673061119 { font-size:20px; font-weight:650; letter-spacing:-.3px; color:var(--dsw-alias-label-primary); font-variant-numeric:tabular-nums; white-space:nowrap; }\n.totalShare_673061119 { flex:none; font-size:11px; color:var(--dsw-alias-label-tertiary); white-space:nowrap; }\n.totalShare_673061119 b { color:var(--dsw-alias-label-primary-dimmed); font-weight:600; font-variant-numeric:tabular-nums; }\n/* A stacked share bar over the drawn content; the empty state is the same\n * diagonal hatch the old panel used for \"no data yet\". */\n.bar_673061119 { display:flex; width:100%; height:10px; margin:12px 0 14px; border-radius:999px; overflow:hidden; background:var(--dsw-alias-border-l1); }\n.barSeg_673061119 { height:100%; flex:none; background:var(--bucket); transition:width .35s ease; }\n.barEmpty_673061119 { display:block; width:100%; height:100%; background:repeating-linear-gradient(90deg,var(--dsw-alias-border-l2) 0 4px,transparent 4px 8px); }\n/* ── Right card: the spend composition ──────────────────────────────────── */\n.statsMain_673061119 { display:flex; align-items:center; gap:16px; }\n.ringWrap_673061119 { position:relative; flex:none; width:116px; height:116px; }\n.ring_673061119 { display:block; }\n.ringTrack_673061119 { stroke:var(--dsw-alias-border-l1); }\n.ringCenter_673061119 { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px; text-align:center; pointer-events:none; }\n.ringTotal_673061119 { font-size:17px; font-weight:650; letter-spacing:-.3px; color:var(--dsw-alias-label-primary); font-variant-numeric:tabular-nums; }\n.ringCaption_673061119 { font-size:10px; color:var(--dsw-alias-label-tertiary); }\n/* Legend: two columns, column-major (left column is the first four buckets,\n * the right column the rest), so the reading order matches the ring. */\n.legend_673061119 { flex:1 1 auto; min-width:0; display:grid; grid-template-rows:repeat(4,auto); grid-auto-flow:column; gap:7px 16px; margin:0; padding:0; list-style:none; }\n.legendRow_673061119 { display:grid; grid-template-columns:7px minmax(0,1fr) auto auto; align-items:center; gap:6px; min-width:0; font-size:11px; }\n.swatch_673061119 { width:7px; height:7px; border-radius:50%; background:var(--bucket); }\n.legendName_673061119 { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-secondary); }\n.legendPercent_673061119 { color:var(--dsw-alias-label-tertiary); font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap; }\n.legendValue_673061119 { color:var(--bucket); font-variant-numeric:tabular-nums; font-weight:500; text-align:right; white-space:nowrap; }\n.legendSuffix_673061119 { color:var(--dsw-alias-label-tertiary); font-weight:400; }\n.empty_673061119 { margin:16px 0 18px; text-align:center; font-size:12px; color:var(--dsw-alias-label-tertiary); }\n/* ── Bucket palette ─────────────────────────────────────────────────────── */\n.toneSystemPrompt_673061119 { --bucket:#6f6bf5; }\n.toneUser_673061119 { --bucket:#2bb673; }\n.toneSkill_673061119 { --bucket:#16b8a6; }\n.toneToolResult_673061119 { --bucket:#4d6bfe; }\n.toneToolDef_673061119 { --bucket:#f0a53a; }\n.toneInjected_673061119 { --bucket:#a75ff0; }\n.toneAssistant_673061119 { --bucket:#7c8cff; }\n.toneOutput_673061119 { --bucket:#ef5da8; }\n/* Narrow surfaces: one card per row, and the legends go back to wrapping rows\n * (a two-column legend cannot keep 11 px labels readable under ~340 px). */\n@container (max-width:880px) {\n .grid_673061119 { grid-template-columns:minmax(0,1fr); }\n}\n@container (max-width:520px) {\n .statsMain_673061119 { flex-wrap:wrap; justify-content:center; }\n .legend_673061119 { grid-template-rows:none; grid-auto-flow:row; grid-template-columns:minmax(0,1fr); width:100%; }\n}\n@media (prefers-reduced-motion:reduce) {\n .barSeg_673061119 { transition:none; }\n}\n/* Dark theme: the host re-resolves its aliases; only the bucket hues need a\n * brighter variant so chips and ring segments hold their weight. */\nbody[data-ds-dark-theme] .toneSystemPrompt_673061119 { --bucket:#9b96ff; }\nbody[data-ds-dark-theme] .toneUser_673061119 { --bucket:#3ed08a; }\nbody[data-ds-dark-theme] .toneSkill_673061119 { --bucket:#2fd6c2; }\nbody[data-ds-dark-theme] .toneToolResult_673061119 { --bucket:#7d95ff; }\nbody[data-ds-dark-theme] .toneToolDef_673061119 { --bucket:#f5b861; }\nbody[data-ds-dark-theme] .toneInjected_673061119 { --bucket:#c184ff; }\nbody[data-ds-dark-theme] .toneAssistant_673061119 { --bucket:#9aabff; }\nbody[data-ds-dark-theme] .toneOutput_673061119 { --bucket:#ff85c0; }\n";
32789
+ const tagId$1 = "dsh-token-total/UsageCards.module.css";
32838
32790
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$1 + "\"]") === null) {
32839
32791
  const tag = document.createElement("style");
32840
32792
  tag.dataset.plugin = "dsh-token-total";
@@ -32842,423 +32794,305 @@ window.__ModuleLoader__.load({
32842
32794
  tag.textContent = css$1;
32843
32795
  document.head.appendChild(tag);
32844
32796
  }
32845
- var ContextWindowPanel_module_default = {
32846
- "panel": "panel_1441117854",
32847
- "head": "head_1441117854",
32848
- "headCollapsed": "headCollapsed_1441117854",
32849
- "toggle": "toggle_1441117854",
32850
- "chevron": "chevron_1441117854",
32851
- "chevronOpen": "chevronOpen_1441117854",
32852
- "headLead": "headLead_1441117854",
32853
- "digest": "digest_1441117854",
32854
- "digestTotal": "digestTotal_1441117854",
32855
- "digestBar": "digestBar_1441117854",
32856
- "digestSeg": "digestSeg_1441117854",
32857
- "headRight": "headRight_1441117854",
32858
- "model": "model_1441117854",
32859
- "livePill": "livePill_1441117854",
32860
- "liveDot": "liveDot_1441117854",
32861
- "liveTime": "liveTime_1441117854",
32862
- "body": "body_1441117854",
32863
- "summary": "summary_1441117854",
32864
- "used": "used_1441117854",
32865
- "label": "label_1441117854",
32866
- "total": "total_1441117854",
32867
- "window": "window_1441117854",
32868
- "cache": "cache_1441117854",
32869
- "cacheBar": "cacheBar_1441117854",
32870
- "cacheHit": "cacheHit_1441117854",
32871
- "cacheMiss": "cacheMiss_1441117854",
32872
- "cacheEmpty": "cacheEmpty_1441117854",
32873
- "cacheLegend": "cacheLegend_1441117854",
32874
- "cacheHitDot": "cacheHitDot_1441117854",
32875
- "cacheMissDot": "cacheMissDot_1441117854",
32876
- "cachePending": "cachePending_1441117854",
32877
- "note": "note_1441117854",
32878
- "map": "map_1441117854",
32879
- "slot": "slot_1441117854",
32880
- "tile": "tile_1441117854",
32881
- "tileLabel": "tileLabel_1441117854",
32882
- "tileSize": "tileSize_1441117854",
32883
- "tip": "tip_1441117854",
32884
- "tipName": "tipName_1441117854",
32885
- "tipMeta": "tipMeta_1441117854",
32886
- "tipShare": "tipShare_1441117854",
32887
- "legend": "legend_1441117854",
32888
- "legendName": "legendName_1441117854",
32889
- "legendValue": "legendValue_1441117854",
32890
- "swatch": "swatch_1441117854",
32891
- "empty": "empty_1441117854",
32892
- "toneSystem": "toneSystem_1441117854",
32893
- "toneTools": "toneTools_1441117854",
32894
- "toneMemory": "toneMemory_1441117854",
32895
- "toneFiles": "toneFiles_1441117854",
32896
- "toneResults": "toneResults_1441117854",
32897
- "toneConversation": "toneConversation_1441117854"
32797
+ var UsageCards_module_default = {
32798
+ "grid": "grid_673061119",
32799
+ "card": "card_673061119",
32800
+ "head": "head_673061119",
32801
+ "title": "title_673061119",
32802
+ "model": "model_673061119",
32803
+ "cacheBadge": "cacheBadge_673061119",
32804
+ "body": "body_673061119",
32805
+ "totalRow": "totalRow_673061119",
32806
+ "total": "total_673061119",
32807
+ "totalShare": "totalShare_673061119",
32808
+ "bar": "bar_673061119",
32809
+ "barSeg": "barSeg_673061119",
32810
+ "barEmpty": "barEmpty_673061119",
32811
+ "statsMain": "statsMain_673061119",
32812
+ "ringWrap": "ringWrap_673061119",
32813
+ "ring": "ring_673061119",
32814
+ "ringTrack": "ringTrack_673061119",
32815
+ "ringCenter": "ringCenter_673061119",
32816
+ "ringTotal": "ringTotal_673061119",
32817
+ "ringCaption": "ringCaption_673061119",
32818
+ "legend": "legend_673061119",
32819
+ "legendRow": "legendRow_673061119",
32820
+ "swatch": "swatch_673061119",
32821
+ "legendName": "legendName_673061119",
32822
+ "legendPercent": "legendPercent_673061119",
32823
+ "legendValue": "legendValue_673061119",
32824
+ "legendSuffix": "legendSuffix_673061119",
32825
+ "empty": "empty_673061119",
32826
+ "toneSystemPrompt": "toneSystemPrompt_673061119",
32827
+ "toneUser": "toneUser_673061119",
32828
+ "toneSkill": "toneSkill_673061119",
32829
+ "toneToolResult": "toneToolResult_673061119",
32830
+ "toneToolDef": "toneToolDef_673061119",
32831
+ "toneInjected": "toneInjected_673061119",
32832
+ "toneAssistant": "toneAssistant_673061119",
32833
+ "toneOutput": "toneOutput_673061119"
32898
32834
  };
32899
32835
  //#endregion
32900
- //#region src/client/ContextWindowPanel.tsx
32901
- const CATEGORY_LABEL = {
32902
- system: "contextSystem",
32903
- tools: "contextTools",
32904
- memory: "contextMemory",
32905
- files: "contextFiles",
32906
- results: "contextResults",
32907
- conversation: "contextConversation"
32836
+ //#region src/client/UsageCards.tsx
32837
+ /** Legend names, one per bucket (the context-window panel's category names are
32838
+ * a different split — files live inside tool results here). */
32839
+ const BUCKET_LABEL = {
32840
+ systemPrompt: "bucketSystemPrompt",
32841
+ user: "bucketUser",
32842
+ skill: "bucketSkill",
32843
+ toolResult: "bucketToolResult",
32844
+ toolDef: "bucketToolDef",
32845
+ injected: "bucketInjected",
32846
+ assistant: "bucketAssistant",
32847
+ output: "bucketOutput"
32908
32848
  };
32909
- const CATEGORY_TONE = {
32910
- system: ContextWindowPanel_module_default.toneSystem,
32911
- tools: ContextWindowPanel_module_default.toneTools,
32912
- memory: ContextWindowPanel_module_default.toneMemory,
32913
- files: ContextWindowPanel_module_default.toneFiles,
32914
- results: ContextWindowPanel_module_default.toneResults,
32915
- conversation: ContextWindowPanel_module_default.toneConversation
32849
+ /** Each bucket brings its own accent as a CSS custom property, so the swatch,
32850
+ * the ring segment, the bar segment and the value text all read one variable. */
32851
+ const BUCKET_TONE = {
32852
+ systemPrompt: UsageCards_module_default.toneSystemPrompt,
32853
+ user: UsageCards_module_default.toneUser,
32854
+ skill: UsageCards_module_default.toneSkill,
32855
+ toolResult: UsageCards_module_default.toneToolResult,
32856
+ toolDef: UsageCards_module_default.toneToolDef,
32857
+ injected: UsageCards_module_default.toneInjected,
32858
+ assistant: UsageCards_module_default.toneAssistant,
32859
+ output: UsageCards_module_default.toneOutput
32916
32860
  };
32917
- /** Which locale string names an injected/system row, keyed by its log kind. */
32918
- const KIND_LABEL = {
32919
- "system-prompt": "contextKindSystemPrompt",
32920
- "runtime-context": "contextKindRuntimeContext",
32921
- "skill-catalog": "contextKindSkillCatalog",
32922
- "time-context": "contextKindTimeContext",
32923
- "compaction-summary": "contextKindCompaction",
32924
- user: "contextKindUser",
32925
- assistant: "contextKindAssistant"
32926
- };
32927
- /** What one treemap tile is called: a tool name or path as-is, an
32928
- * injected kind through the dictionary, and a folded tail as "N more". */
32929
- function itemText(item, t) {
32930
- if (item.kind === "rest") return t("contextKindRest", { count: String(item.count) });
32931
- const label = item.kind === void 0 ? void 0 : KIND_LABEL[item.kind];
32932
- const base = label !== void 0 ? t(label) : item.label || item.kind || "";
32933
- return item.count > 1 ? `${base} ${t("contextTimes", { count: String(item.count) })}` : base;
32934
- }
32935
- const sumOf = (items) => items.reduce((sum, item) => sum + item.tokens, 0);
32936
- /** Wall-clock stamp of the last successful read, same shape as the page's. */
32937
- const clock$1 = (ms) => new Date(ms).toLocaleTimeString([], {
32938
- hour: "2-digit",
32939
- minute: "2-digit",
32940
- second: "2-digit"
32941
- });
32942
- /** Hover-card footprint estimate, so the tip can be kept inside the map
32943
- * without measuring itself first. */
32944
- const TIP_W = 220;
32945
- const TIP_H = 70;
32946
- /** The hover card for one tile: name and call count, the exact token count
32947
- * with its category, and the block's share of the estimate and of the
32948
- * model's window — the figures a tile has no room to print. */
32949
- function Tip({ item, box, map, t, total, contextWindow }) {
32950
- const share = total > 0 ? formatPercent(item.tokens / total * 100) : null;
32951
- const windowShare = contextWindow !== null && contextWindow > 0 ? formatPercent(item.tokens / contextWindow * 100) : null;
32952
- const left = Math.min(Math.max(box.x + box.width / 2 - TIP_W / 2, 4), Math.max(4, map.width - TIP_W - 4));
32953
- const top = box.y + box.height + 6 + TIP_H < map.height - 4 ? box.y + box.height + 6 : Math.max(4, box.y - TIP_H - 6);
32861
+ /** Approximate value as the card prints it: "≈299.1k". */
32862
+ const approx = (n) => `≈${formatCompact(n)}`;
32863
+ /** Ring geometry: a 116 px donut with a 15 px stroke. */
32864
+ const RING_SIZE = 116;
32865
+ const RING_STROKE = 15;
32866
+ const RING_RADIUS = 101 / 2 - 2;
32867
+ const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
32868
+ /** Donut of bucket shares with the grand total in the middle. Segments are
32869
+ * stroked along one circle and offset by the running length, so the ring is
32870
+ * DPI-independent and needs no chart library. `total` sizes the segments (the
32871
+ * replay estimate); `center` is the exact billed figure the card headlines. */
32872
+ function Ring({ rows, total, center, centerTitle, caption }) {
32873
+ const drawn = rows.filter((row) => row.tokens > 0);
32874
+ let offset = 0;
32954
32875
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32955
- className: ContextWindowPanel_module_default.tip,
32956
- role: "tooltip",
32957
- style: {
32958
- left,
32959
- top
32960
- },
32876
+ className: UsageCards_module_default.ringWrap,
32877
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
32878
+ className: UsageCards_module_default.ring,
32879
+ width: RING_SIZE,
32880
+ height: RING_SIZE,
32881
+ viewBox: `0 0 ${RING_SIZE} ${RING_SIZE}`,
32882
+ role: "img",
32883
+ "aria-label": caption,
32884
+ children: [
32885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: caption }),
32886
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
32887
+ className: UsageCards_module_default.ringTrack,
32888
+ cx: RING_SIZE / 2,
32889
+ cy: RING_SIZE / 2,
32890
+ r: RING_RADIUS,
32891
+ fill: "none",
32892
+ strokeWidth: RING_STROKE
32893
+ }),
32894
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
32895
+ transform: `rotate(-90 ${RING_SIZE / 2} ${RING_SIZE / 2})`,
32896
+ children: drawn.map((row) => {
32897
+ const length = total > 0 ? row.tokens / total * RING_CIRCUMFERENCE : 0;
32898
+ const visible = Math.max(length - 1.2, .6);
32899
+ const dash = `${visible} ${RING_CIRCUMFERENCE - visible}`;
32900
+ const circle = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
32901
+ className: BUCKET_TONE[row.key],
32902
+ cx: RING_SIZE / 2,
32903
+ cy: RING_SIZE / 2,
32904
+ r: RING_RADIUS,
32905
+ fill: "none",
32906
+ stroke: "var(--bucket)",
32907
+ strokeWidth: RING_STROKE,
32908
+ strokeDasharray: dash,
32909
+ strokeDashoffset: -offset
32910
+ }, row.key);
32911
+ offset += length;
32912
+ return circle;
32913
+ })
32914
+ })
32915
+ ]
32916
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32917
+ className: UsageCards_module_default.ringCenter,
32918
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
32919
+ className: UsageCards_module_default.ringTotal,
32920
+ title: centerTitle,
32921
+ children: center
32922
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32923
+ className: UsageCards_module_default.ringCaption,
32924
+ children: caption
32925
+ })]
32926
+ })]
32927
+ });
32928
+ }
32929
+ /** One legend entry: swatch, name, share, approximate value. */
32930
+ function LegendRow({ row, percent, suffix, t }) {
32931
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
32932
+ className: `${UsageCards_module_default.legendRow} ${BUCKET_TONE[row.key]}`,
32961
32933
  children: [
32962
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32963
- className: ContextWindowPanel_module_default.tipName,
32964
- children: itemText(item, t)
32934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
32935
+ className: UsageCards_module_default.swatch,
32936
+ "aria-hidden": "true"
32965
32937
  }),
32966
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32967
- className: ContextWindowPanel_module_default.tipMeta,
32968
- children: [
32969
- formatTokens(item.tokens),
32970
- " token · ",
32971
- t(CATEGORY_LABEL[item.category])
32972
- ]
32938
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32939
+ className: UsageCards_module_default.legendName,
32940
+ children: t(BUCKET_LABEL[row.key])
32941
+ }),
32942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32943
+ className: UsageCards_module_default.legendPercent,
32944
+ children: [percent.toFixed(1), "%"]
32973
32945
  }),
32974
- share !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32975
- className: ContextWindowPanel_module_default.tipShare,
32976
- children: [t("contextTipShare", { percent: share }), windowShare === null ? "" : ` · ${t("contextTipWindow", { percent: windowShare })}`]
32946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32947
+ className: UsageCards_module_default.legendValue,
32948
+ title: formatTokens(row.tokens),
32949
+ children: [approx(row.tokens), suffix === void 0 ? "" : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32950
+ className: UsageCards_module_default.legendSuffix,
32951
+ children: [" · ", suffix]
32952
+ })]
32977
32953
  })
32978
32954
  ]
32979
32955
  });
32980
32956
  }
32981
- function Treemap({ items, t, contextWindow }) {
32982
- const hostRef = (0, react.useRef)(null);
32983
- const [size, setSize] = (0, react.useState)({
32984
- width: 0,
32985
- height: 0
32986
- });
32987
- const [hoverKey, setHoverKey] = (0, react.useState)(null);
32988
- (0, react.useLayoutEffect)(() => {
32989
- const host = hostRef.current;
32990
- if (host === null) return;
32991
- const measure = () => setSize((previous) => {
32992
- const width = host.clientWidth;
32993
- const height = host.clientHeight;
32994
- return previous.width === width && previous.height === height ? previous : {
32995
- width,
32996
- height
32997
- };
32998
- });
32999
- measure();
33000
- if (typeof ResizeObserver === "undefined") {
33001
- window.addEventListener("resize", measure);
33002
- return () => window.removeEventListener("resize", measure);
33003
- }
33004
- const observer = new ResizeObserver(measure);
33005
- observer.observe(host);
33006
- return () => observer.disconnect();
33007
- }, []);
33008
- const boxes = (0, react.useMemo)(() => treemapLayout(items.map((item) => ({
33009
- key: item.key,
33010
- tokens: item.tokens
33011
- })), size.width, size.height), [
33012
- items,
33013
- size.width,
33014
- size.height
33015
- ]);
33016
- const byKey = (0, react.useMemo)(() => new Map(items.map((item) => [item.key, item])), [items]);
33017
- const hoverBox = hoverKey === null ? void 0 : boxes.find((box) => box.key === hoverKey);
33018
- const hoverItem = hoverBox === void 0 ? void 0 : byKey.get(hoverBox.key);
33019
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33020
- ref: hostRef,
33021
- className: ContextWindowPanel_module_default.map,
33022
- onMouseLeave: () => setHoverKey(null),
33023
- children: [boxes.map((box) => {
33024
- const item = byKey.get(box.key);
33025
- if (item === void 0 || box.width <= 0 || box.height <= 0) return null;
33026
- const text = itemText(item, t);
33027
- const showLabel = box.width >= 46 && box.height >= 22;
33028
- const showSize = box.width >= 62 && box.height >= 40;
33029
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33030
- className: ContextWindowPanel_module_default.slot,
33031
- style: {
33032
- left: box.x,
33033
- top: box.y,
33034
- width: box.width,
33035
- height: box.height
33036
- },
33037
- onMouseEnter: () => setHoverKey(box.key),
33038
- "aria-label": `${text} · ${formatTokens(item.tokens)} token · ${t(CATEGORY_LABEL[item.category])}`,
33039
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33040
- className: `${ContextWindowPanel_module_default.tile} ${CATEGORY_TONE[item.category]}`,
33041
- children: [showLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33042
- className: ContextWindowPanel_module_default.tileLabel,
33043
- children: text
33044
- }), showSize && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33045
- className: ContextWindowPanel_module_default.tileSize,
33046
- children: [
33047
- formatCompact(item.tokens),
33048
- " · ",
33049
- t(CATEGORY_LABEL[item.category])
33050
- ]
33051
- })]
33052
- })
33053
- }, box.key);
33054
- }), hoverBox !== void 0 && hoverItem !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tip, {
33055
- item: hoverItem,
33056
- box: hoverBox,
33057
- map: size,
33058
- t,
33059
- total: sumOf(items),
33060
- contextWindow
33061
- })]
33062
- });
33063
- }
33064
- /** The context-window panel above the Usage cards, in magpie's shape: the used
33065
- * total and the model's window on the left, the newest answer's prompt-cache
33066
- * split on the right, a treemap of what occupies the window below (with a
33067
- * hover card per tile), and one legend row naming the six buckets with their
33068
- * sizes. Every figure is an estimate, so the panel never claims an exact
33069
- * count — and the live pill stamps when the estimate was last refreshed. */
33070
- function ContextWindowPanel({ context, t, updatedAt }) {
33071
- const [open, setOpen] = (0, react.useState)(true);
33072
- const totals = contextCategoryTotals(context.items);
32957
+ /** Left card: the live window's composition, its occupancy and the hard cap. */
32958
+ function ContextCard({ context, t }) {
33073
32959
  const reading = contextReading(context);
33074
- const percent = reading.percent;
33075
- const hitRate = context.cacheKnown ? cacheRate(context.cacheHitTokens, context.cacheMissTokens) : null;
33076
- const model = context.model !== "" && !context.model.includes("/") && context.provider !== "" ? `${context.provider}/${context.model}` : context.model;
33077
- const windowText = context.contextWindow === null ? t("contextWindowUnknown") : t("contextWindowKnown", {
33078
- value: formatCompact(context.contextWindow),
33079
- percent: String(percent ?? 0)
33080
- });
33081
- const categoryTotal = totals.reduce((sum, row) => sum + row.tokens, 0);
33082
- const windowKnown = context.contextWindow !== null && context.contextWindow > 0;
33083
- const usedShare = windowKnown ? Math.min(100, Math.max(0, reading.usedTokens / context.contextWindow * 100)) : 100;
33084
- const freeTokens = windowKnown ? Math.max(0, context.contextWindow - reading.usedTokens) : 0;
33085
- const digestLabel = [
33086
- `${t("contextUsed")} ${formatTokens(reading.usedTokens)} token`,
33087
- ...totals.map((row) => `${t(CATEGORY_LABEL[row.key])} ${formatCompact(row.tokens)}`),
33088
- windowKnown ? `${t("contextFree")} ${formatTokens(freeTokens)} token` : windowText
33089
- ].join(" · ");
33090
- const digestText = context.contextWindow === null ? t("contextDigestUnknown", { used: formatCompact(reading.usedTokens) }) : t("contextDigest", {
32960
+ const rows = contextBucketTotals(context.items);
32961
+ const total = rows.reduce((sum, row) => sum + row.tokens, 0);
32962
+ const capacity = context.contextWindow !== null && context.contextWindow > 0 ? context.contextWindow : total;
32963
+ const occupied = Math.min(100, bucketPercent(reading.usedTokens, capacity));
32964
+ const windowText = context.contextWindow !== null && context.contextWindow > 0 ? t("contextWindowTotal", {
33091
32965
  used: formatCompact(reading.usedTokens),
33092
- window: formatCompact(context.contextWindow),
33093
- percent: String(percent ?? 0)
33094
- });
32966
+ window: formatCompact(context.contextWindow)
32967
+ }) : t("contextWindowTotalUnknown", { used: formatCompact(reading.usedTokens) });
33095
32968
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33096
- className: ContextWindowPanel_module_default.panel,
33097
- "data-dsh-context-window": true,
32969
+ className: `${UsageCards_module_default.card} ${UsageCards_module_default.contextCard}`,
33098
32970
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
33099
- className: open ? ContextWindowPanel_module_default.head : `${ContextWindowPanel_module_default.head} ${ContextWindowPanel_module_default.headCollapsed}`,
33100
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33101
- className: ContextWindowPanel_module_default.headLead,
33102
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
33103
- type: "button",
33104
- className: ContextWindowPanel_module_default.toggle,
33105
- onClick: () => setOpen(!open),
33106
- "aria-expanded": open,
33107
- "aria-controls": "dsh-context-window-body",
33108
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
33109
- className: `${ContextWindowPanel_module_default.chevron} ${open ? ContextWindowPanel_module_default.chevronOpen : ""}`,
33110
- width: "12",
33111
- height: "12",
33112
- viewBox: "0 0 12 12",
33113
- "aria-hidden": "true",
33114
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
33115
- d: "m4 2 4 4-4 4",
33116
- stroke: "currentColor",
33117
- strokeWidth: "1.5",
33118
- fill: "none"
33119
- })
33120
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("contextTitle") })]
33121
- }), !open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33122
- className: ContextWindowPanel_module_default.digest,
33123
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33124
- className: ContextWindowPanel_module_default.digestTotal,
33125
- title: `${t("contextUsed")} ${formatTokens(reading.usedTokens)} token · ${windowText}`,
33126
- children: digestText
33127
- }), categoryTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33128
- className: ContextWindowPanel_module_default.digestBar,
33129
- role: "img",
33130
- "aria-label": digestLabel,
33131
- title: digestLabel,
33132
- children: totals.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33133
- className: `${ContextWindowPanel_module_default.digestSeg} ${CATEGORY_TONE[row.key]}`,
33134
- style: { width: `${row.tokens / categoryTotal * usedShare}%` }
33135
- }, row.key))
33136
- })]
33137
- })]
33138
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33139
- className: ContextWindowPanel_module_default.headRight,
33140
- children: [model !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33141
- className: ContextWindowPanel_module_default.model,
33142
- title: `${context.provider} / ${context.model}`,
33143
- children: model
33144
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33145
- className: ContextWindowPanel_module_default.livePill,
33146
- children: [
33147
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33148
- className: ContextWindowPanel_module_default.liveDot,
33149
- "aria-hidden": "true"
33150
- }),
33151
- t("contextLive"),
33152
- typeof updatedAt === "number" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33153
- className: ContextWindowPanel_module_default.liveTime,
33154
- children: [" · ", clock$1(updatedAt)]
33155
- })
33156
- ]
33157
- })]
32971
+ className: UsageCards_module_default.head,
32972
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
32973
+ className: UsageCards_module_default.title,
32974
+ children: t("currentContextTitle")
32975
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32976
+ className: UsageCards_module_default.model,
32977
+ title: `${context.provider} / ${context.model}`,
32978
+ children: [context.provider, context.model].filter(Boolean).join(" · ") || "—"
33158
32979
  })]
33159
32980
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33160
- id: "dsh-context-window-body",
33161
- className: ContextWindowPanel_module_default.body,
33162
- hidden: !open,
32981
+ className: UsageCards_module_default.body,
33163
32982
  children: [
33164
32983
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33165
- className: ContextWindowPanel_module_default.summary,
33166
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33167
- className: ContextWindowPanel_module_default.used,
32984
+ className: UsageCards_module_default.totalRow,
32985
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
32986
+ className: UsageCards_module_default.total,
32987
+ title: formatTokens(reading.usedTokens),
32988
+ children: windowText
32989
+ }), reading.percent !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32990
+ className: UsageCards_module_default.totalShare,
33168
32991
  children: [
33169
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33170
- className: ContextWindowPanel_module_default.label,
33171
- children: t("contextUsed")
33172
- }),
33173
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", {
33174
- className: ContextWindowPanel_module_default.total,
33175
- title: formatTokens(reading.usedTokens),
33176
- children: [formatCompact(reading.usedTokens), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: "token" })]
33177
- }),
33178
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33179
- className: ContextWindowPanel_module_default.window,
33180
- children: windowText
33181
- })
33182
- ]
33183
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33184
- className: ContextWindowPanel_module_default.cache,
33185
- children: [
33186
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33187
- className: ContextWindowPanel_module_default.label,
33188
- children: t("contextCache")
33189
- }),
33190
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33191
- className: ContextWindowPanel_module_default.cacheBar,
33192
- children: hitRate === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: ContextWindowPanel_module_default.cacheEmpty }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33193
- className: ContextWindowPanel_module_default.cacheHit,
33194
- style: { width: `${hitRate}%` }
33195
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33196
- className: ContextWindowPanel_module_default.cacheMiss,
33197
- style: { width: `${100 - hitRate}%` }
33198
- })] })
33199
- }),
33200
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33201
- className: ContextWindowPanel_module_default.cacheLegend,
33202
- children: hitRate === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33203
- className: ContextWindowPanel_module_default.cachePending,
33204
- children: t("contextCachePending")
33205
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
33206
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
33207
- className: ContextWindowPanel_module_default.cacheHitDot,
33208
- "aria-hidden": "true"
33209
- }),
33210
- t("contextCacheHit"),
33211
- " ",
33212
- formatPercent(hitRate)
33213
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
33214
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
33215
- className: ContextWindowPanel_module_default.cacheMissDot,
33216
- "aria-hidden": "true"
33217
- }),
33218
- t("contextCacheMiss"),
33219
- " ",
33220
- formatPercent(100 - hitRate)
33221
- ] })] })
33222
- })
32992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: formatPercent(reading.percent) }),
32993
+ " ",
32994
+ t("contextUsedShort")
33223
32995
  ]
33224
32996
  })]
33225
32997
  }),
33226
- reading.estimateDiffers && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33227
- className: ContextWindowPanel_module_default.note,
33228
- children: t("contextEstimateNote", {
33229
- estimate: formatCompact(reading.estimateTokens),
33230
- billed: formatCompact(reading.usedTokens)
33231
- })
33232
- }),
33233
- context.items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33234
- className: ContextWindowPanel_module_default.empty,
33235
- children: t("contextEmpty")
33236
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Treemap, {
33237
- items: context.items,
33238
- t,
33239
- contextWindow: context.contextWindow
32998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32999
+ className: UsageCards_module_default.bar,
33000
+ role: "img",
33001
+ "aria-label": t("currentContextTitle"),
33002
+ children: [rows.map((row) => row.tokens > 0 && total > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33003
+ className: `${UsageCards_module_default.barSeg} ${BUCKET_TONE[row.key]}`,
33004
+ style: { width: `${bucketPercent(row.tokens, total) * occupied / 100}%` }
33005
+ }, row.key) : null), total <= 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageCards_module_default.barEmpty })]
33240
33006
  }),
33241
- totals.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
33242
- className: ContextWindowPanel_module_default.legend,
33243
- children: totals.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
33244
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33245
- className: `${ContextWindowPanel_module_default.swatch} ${CATEGORY_TONE[row.key]}`,
33246
- "aria-hidden": "true"
33247
- }),
33248
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33249
- className: ContextWindowPanel_module_default.legendName,
33250
- children: t(CATEGORY_LABEL[row.key])
33251
- }),
33252
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33253
- className: ContextWindowPanel_module_default.legendValue,
33254
- children: formatCompact(row.tokens)
33255
- })
33256
- ] }, row.key))
33007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
33008
+ className: UsageCards_module_default.legend,
33009
+ children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LegendRow, {
33010
+ row,
33011
+ percent: bucketPercent(row.tokens, total),
33012
+ t
33013
+ }, row.key))
33257
33014
  })
33258
33015
  ]
33259
33016
  })]
33260
33017
  });
33261
33018
  }
33019
+ /** Right card: every request's replayed content plus the billed output. */
33020
+ function StatsCard({ detail, t }) {
33021
+ const composition = detail.composition ?? null;
33022
+ const rows = composition?.buckets ?? [];
33023
+ const total = rows.reduce((sum, row) => sum + row.tokens, 0);
33024
+ const billed = composition?.billedTokens ?? 0;
33025
+ const hit = composition !== null ? cacheHitPercentOf(composition.totals) : detail.context?.cacheKnown ? cacheRate(detail.context.cacheHitTokens, detail.context.cacheMissTokens) : null;
33026
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33027
+ className: `${UsageCards_module_default.card} ${UsageCards_module_default.statsCard}`,
33028
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
33029
+ className: UsageCards_module_default.head,
33030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
33031
+ className: UsageCards_module_default.title,
33032
+ children: t("statsTitle")
33033
+ }), hit !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33034
+ className: UsageCards_module_default.cacheBadge,
33035
+ children: [
33036
+ t("statsCacheHit"),
33037
+ " ",
33038
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", { children: [hit.toFixed(2), "%"] })
33039
+ ]
33040
+ })]
33041
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33042
+ className: UsageCards_module_default.body,
33043
+ children: composition === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33044
+ className: UsageCards_module_default.empty,
33045
+ children: t("statsEmpty")
33046
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33047
+ className: UsageCards_module_default.statsMain,
33048
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Ring, {
33049
+ rows,
33050
+ total,
33051
+ center: formatCompact(billed),
33052
+ centerTitle: formatTokens(billed),
33053
+ caption: t("statsTotal")
33054
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
33055
+ className: UsageCards_module_default.legend,
33056
+ children: USAGE_BUCKET_ORDER.map((key) => {
33057
+ const row = rows.find((item) => item.key === key) ?? {
33058
+ key,
33059
+ tokens: 0
33060
+ };
33061
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LegendRow, {
33062
+ row,
33063
+ percent: bucketPercent(row.tokens, total),
33064
+ suffix: key === "output" ? t("statsIncludesThinking") : void 0,
33065
+ t
33066
+ }, key);
33067
+ })
33068
+ })]
33069
+ }) })
33070
+ })]
33071
+ });
33072
+ }
33073
+ /** The pair, rendered where the context-window panel used to sit. */
33074
+ function SessionUsageCards({ detail, t }) {
33075
+ if (detail.context === null || detail.context === void 0) {
33076
+ if (detail.composition === null || detail.composition === void 0) return null;
33077
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33078
+ className: UsageCards_module_default.grid,
33079
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsCard, {
33080
+ detail,
33081
+ t
33082
+ })
33083
+ });
33084
+ }
33085
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33086
+ className: UsageCards_module_default.grid,
33087
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextCard, {
33088
+ context: detail.context,
33089
+ t
33090
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsCard, {
33091
+ detail,
33092
+ t
33093
+ })]
33094
+ });
33095
+ }
33262
33096
  //#endregion
33263
33097
  //#region src/client/model-logos-data.ts
33264
33098
  /**
@@ -34003,11 +33837,10 @@ window.__ModuleLoader__.load({
34003
33837
  detail,
34004
33838
  t
34005
33839
  }),
34006
- detail.context ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextWindowPanel, {
34007
- context: detail.context,
34008
- t,
34009
- updatedAt
34010
- }) : null,
33840
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionUsageCards, {
33841
+ detail,
33842
+ t
33843
+ }),
34011
33844
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Records, {
34012
33845
  detail,
34013
33846
  t
@@ -34152,6 +33985,7 @@ window.__ModuleLoader__.load({
34152
33985
  asOf: "统计截至",
34153
33986
  refresh: "刷新",
34154
33987
  contextTitle: "上下文窗口",
33988
+ currentContextTitle: "当前上下文",
34155
33989
  contextUsed: "已用上下文",
34156
33990
  contextLive: "实时",
34157
33991
  contextWindowKnown: "模型窗口 {value} · 已用 {percent}%",
@@ -34181,7 +34015,23 @@ window.__ModuleLoader__.load({
34181
34015
  contextEmpty: "本会话还没有可统计的上下文",
34182
34016
  contextTipShare: "占上下文 {percent}",
34183
34017
  contextTipWindow: "占窗口 {percent}",
34184
- contextEstimateNote: "色块按历史重放估算 {estimate};供应商实测提示词 {billed}(上游已压缩或裁剪)"
34018
+ contextEstimateNote: "色块按历史重放估算 {estimate};供应商实测提示词 {billed}(上游已压缩或裁剪)",
34019
+ statsTitle: "Token 统计",
34020
+ statsTotal: "总用量",
34021
+ statsCacheHit: "缓存命中",
34022
+ statsIncludesThinking: "含思考",
34023
+ statsEmpty: "本会话还没有可统计的 token",
34024
+ contextWindowTotal: "{used} / {window} tokens",
34025
+ contextWindowTotalUnknown: "{used} tokens",
34026
+ contextUsedShort: "上下文已用",
34027
+ bucketSystemPrompt: "系统提示词",
34028
+ bucketUser: "用户消息",
34029
+ bucketSkill: "技能注入",
34030
+ bucketToolResult: "工具结果",
34031
+ bucketToolDef: "工具定义",
34032
+ bucketInjected: "注入内容",
34033
+ bucketAssistant: "助手消息",
34034
+ bucketOutput: "输出"
34185
34035
  };
34186
34036
  const usageEn = {
34187
34037
  tab: "Usage",
@@ -34243,6 +34093,7 @@ window.__ModuleLoader__.load({
34243
34093
  asOf: "as of",
34244
34094
  refresh: "Refresh",
34245
34095
  contextTitle: "Context window",
34096
+ currentContextTitle: "Current context",
34246
34097
  contextUsed: "Context used",
34247
34098
  contextLive: "Live",
34248
34099
  contextWindowKnown: "Window {value} · {percent}% used",
@@ -34272,7 +34123,23 @@ window.__ModuleLoader__.load({
34272
34123
  contextEmpty: "Nothing in this session’s context yet",
34273
34124
  contextTipShare: "{percent} of context",
34274
34125
  contextTipWindow: "{percent} of window",
34275
- contextEstimateNote: "Blocks estimate {estimate} by replay; the provider billed {billed} after upstream compression"
34126
+ contextEstimateNote: "Blocks estimate {estimate} by replay; the provider billed {billed} after upstream compression",
34127
+ statsTitle: "Token stats",
34128
+ statsTotal: "Total usage",
34129
+ statsCacheHit: "Cache hit",
34130
+ statsIncludesThinking: "incl. thinking",
34131
+ statsEmpty: "No token spend recorded for this session yet",
34132
+ contextWindowTotal: "{used} / {window} tokens",
34133
+ contextWindowTotalUnknown: "{used} tokens",
34134
+ contextUsedShort: "context used",
34135
+ bucketSystemPrompt: "System prompt",
34136
+ bucketUser: "User messages",
34137
+ bucketSkill: "Skill injection",
34138
+ bucketToolResult: "Tool results",
34139
+ bucketToolDef: "Tool definitions",
34140
+ bucketInjected: "Injected content",
34141
+ bucketAssistant: "Assistant messages",
34142
+ bucketOutput: "Output"
34276
34143
  };
34277
34144
  const usageZhTW = {
34278
34145
  ...usageZh,
@@ -34334,6 +34201,7 @@ window.__ModuleLoader__.load({
34334
34201
  asOf: "統計截至",
34335
34202
  refresh: "重新整理",
34336
34203
  contextTitle: "上下文視窗",
34204
+ currentContextTitle: "當前上下文",
34337
34205
  contextUsed: "已用上下文",
34338
34206
  contextLive: "即時",
34339
34207
  contextWindowKnown: "模型視窗 {value} · 已用 {percent}%",
@@ -34363,7 +34231,23 @@ window.__ModuleLoader__.load({
34363
34231
  contextEmpty: "本會話還沒有可統計的上下文",
34364
34232
  contextTipShare: "佔上下文 {percent}",
34365
34233
  contextTipWindow: "佔視窗 {percent}",
34366
- contextEstimateNote: "色塊按歷史重放估算 {estimate};供應商實測提示詞 {billed}(上游已壓縮或裁剪)"
34234
+ contextEstimateNote: "色塊按歷史重放估算 {estimate};供應商實測提示詞 {billed}(上游已壓縮或裁剪)",
34235
+ statsTitle: "Token 統計",
34236
+ statsTotal: "總用量",
34237
+ statsCacheHit: "快取命中",
34238
+ statsIncludesThinking: "含思考",
34239
+ statsEmpty: "本會話還沒有可統計的 token",
34240
+ contextWindowTotal: "{used} / {window} tokens",
34241
+ contextWindowTotalUnknown: "{used} tokens",
34242
+ contextUsedShort: "上下文已用",
34243
+ bucketSystemPrompt: "系統提示詞",
34244
+ bucketUser: "使用者訊息",
34245
+ bucketSkill: "技能注入",
34246
+ bucketToolResult: "工具結果",
34247
+ bucketToolDef: "工具定義",
34248
+ bucketInjected: "注入內容",
34249
+ bucketAssistant: "助手訊息",
34250
+ bucketOutput: "輸出"
34367
34251
  };
34368
34252
  //#endregion
34369
34253
  //#region src/client/index.tsx