dsh-token-total 1.1.0 → 1.1.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 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,9 +31661,14 @@ 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
31657
- * its own child component so stream deltas never re-render the pills. The live
31664
+ * figure with a live estimate of what is happening now, and hands the slot back
31665
+ * to the session's decode average when nothing is streaming. While a step
31666
+ * streams the live rate is the only reading that can match the pace of the
31667
+ * answer on screen; once the step settles it becomes a figure from a window
31668
+ * nobody is watching any more, so the pill returns to the same quotient the
31669
+ * usage view quotes (ΣoutputTokens / ΣdecodeMs) instead of freezing on the last
31670
+ * measurement. That subscription lives in its own child component, so stream
31671
+ * 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
31660
31674
  * quotient charged a whole step's text to a window that had just opened, so a
@@ -31740,23 +31754,35 @@ 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. Two figures share the slot, chosen by
31758
+ * whether a step is streaming right now:
31759
+ *
31760
+ * - while one is (`partial != null`), the measured rate of THAT stream — a
31761
+ * bounded sliding window over observed token growth (stats-line-core's
31762
+ * StreamingRateSampler), the only reading that can track the pace of the
31763
+ * answer currently on screen;
31764
+ * - otherwise the session's own decode average, ΣoutputTokens / ΣdecodeMs —
31765
+ * the same quotient `decodeRateOf` quotes in the usage view. An idle composer
31766
+ * must not freeze on whatever the last window happened to measure: a settled
31767
+ * thirty-step turn kept showing its final second-long reading (57 tok/s)
31768
+ * beside the usage view's 156 tok/s for the same work, and the two read as a
31769
+ * contradiction rather than as two scopes of one number.
31770
+ *
31771
+ * The measured reading carries the step it was taken in, so a new step cannot
31772
+ * inherit the previous one's figure before its own window publishes; nothing is
31773
+ * held across the gap — the gap is exactly when the average applies.
31747
31774
  *
31748
31775
  * One window covers a whole step, across every output kind the accumulator
31749
31776
  * carries — reasoning, answer text, and Tool-call arguments — because the
31750
31777
  * 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.
31778
+ * sequence.
31754
31779
  *
31755
31780
  * Sampling happens in a layout effect and the figure it yields is published as
31756
31781
  * state, so no render reads a clock or mutates the window: a render React starts
31757
31782
  * and then discards must not decide what the next committed one displays. The
31758
31783
  * rate itself comes from stats-line-core's StreamingRateSampler — a bounded
31759
- * window over observed token growth, smoothed, and held while nothing arrives.
31784
+ * window over observed token growth, smoothed, and held while nothing arrives
31785
+ * inside the step it belongs to.
31760
31786
  */
31761
31787
  function LiveSpeed({ useChat, scale, fallback, t }) {
31762
31788
  const partial = useChat((s) => s.legacy.partial);
@@ -31768,22 +31794,20 @@ window.__ModuleLoader__.load({
31768
31794
  rate: new StreamingRateSampler()
31769
31795
  };
31770
31796
  const rate = sampler.current.rate;
31771
- const [published, setPublished] = (0, react.useState)({
31772
- key: null,
31773
- rate: null
31774
- });
31797
+ const [reading, setReading] = (0, react.useState)(null);
31775
31798
  const scaleRef = (0, react.useRef)(scale);
31776
31799
  const [beat, setBeat] = (0, react.useState)(0);
31777
31800
  (0, react.useLayoutEffect)(() => {
31801
+ if (key === null) return;
31778
31802
  if (scaleRef.current !== scale) {
31779
31803
  scaleRef.current = scale;
31780
31804
  rate.reset();
31781
31805
  }
31782
31806
  rate.observe(tokens, Date.now());
31783
31807
  const next = rate.ratePerSecond();
31784
- setPublished((previous) => previous.key === key && previous.rate === next ? previous : {
31808
+ if (next !== null) setReading((previous) => previous !== null && previous.key === key && previous.value === next ? previous : {
31785
31809
  key,
31786
- rate: next
31810
+ value: next
31787
31811
  });
31788
31812
  }, [
31789
31813
  tokens,
@@ -31802,8 +31826,8 @@ window.__ModuleLoader__.load({
31802
31826
  clearInterval(handle);
31803
31827
  };
31804
31828
  }, [streaming]);
31805
- const reading = published.key === key ? published.rate : null;
31806
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeedFigure, { text: reading === null ? fallback : t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(reading) }) });
31829
+ const measured = reading !== null && reading.key === key ? reading.value : null;
31830
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeedFigure, { text: measured === null ? fallback : t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(measured) }) });
31807
31831
  }
31808
31832
  function TimePill({ stats, t, dialog, useChat, scale, dock }) {
31809
31833
  const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
@@ -32707,24 +32731,55 @@ window.__ModuleLoader__.load({
32707
32731
  };
32708
32732
  }
32709
32733
  //#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"
32734
+ //#region src/usage-buckets.ts
32735
+ const USAGE_BUCKET_ORDER = [
32736
+ "systemPrompt",
32737
+ "user",
32738
+ "skill",
32739
+ "toolResult",
32740
+ "toolDef",
32741
+ "injected",
32742
+ "assistant",
32743
+ "output"
32719
32744
  ];
32720
- /** Per-category token totals in legend order, empty categories dropped. */
32721
- function contextCategoryTotals(items) {
32745
+ /** The buckets that describe the live window (output never occupies it). */
32746
+ const CONTEXT_BUCKET_ORDER = USAGE_BUCKET_ORDER.filter((key) => key !== "output");
32747
+ //#endregion
32748
+ //#region src/client/context-window-core.ts
32749
+ /** Which legend bucket one window row belongs to.
32750
+ *
32751
+ * The row categories are the treemap's (they group files separately); the
32752
+ * cards speak the eight-bucket legend instead. A folded `rest` row lost its
32753
+ * kind, so a conversation tail lands with the assistant — the larger half of
32754
+ * what a turn replays.
32755
+ */
32756
+ function contextBucketOf(item) {
32757
+ switch (item.category) {
32758
+ case "system": return "systemPrompt";
32759
+ case "tools": return "toolDef";
32760
+ case "memory": return item.kind === "skill-catalog" ? "skill" : "injected";
32761
+ case "files":
32762
+ case "results": return "toolResult";
32763
+ case "conversation": return item.kind === "user" ? "user" : "assistant";
32764
+ default: return "injected";
32765
+ }
32766
+ }
32767
+ /** The live window folded into the same legend the spend card prints, in
32768
+ * legend order and keeping zero buckets so both cards stay aligned. */
32769
+ function contextBucketTotals(items) {
32722
32770
  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) => ({
32771
+ for (const item of items) {
32772
+ const key = contextBucketOf(item);
32773
+ totals.set(key, (totals.get(key) ?? 0) + item.tokens);
32774
+ }
32775
+ return CONTEXT_BUCKET_ORDER.map((key) => ({
32725
32776
  key,
32726
32777
  tokens: totals.get(key) ?? 0
32727
- })).filter((row) => row.tokens > 0);
32778
+ }));
32779
+ }
32780
+ /** What a share of the whole is, for the legend column. */
32781
+ function bucketPercent(tokens, total) {
32782
+ return total > 0 ? tokens / total * 100 : 0;
32728
32783
  }
32729
32784
  /** Window occupancy in whole percent, or null when the host never reported the
32730
32785
  * model's window (the panel then says "unknown" instead of guessing one). */
@@ -32743,98 +32798,10 @@ window.__ModuleLoader__.load({
32743
32798
  estimateDiffers: context.promptTokens !== null && gap > .15
32744
32799
  };
32745
32800
  }
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
32801
  //#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";
32802
+ //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/UsageCards.module.css?inline
32803
+ 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";
32804
+ const tagId$1 = "dsh-token-total/UsageCards.module.css";
32838
32805
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$1 + "\"]") === null) {
32839
32806
  const tag = document.createElement("style");
32840
32807
  tag.dataset.plugin = "dsh-token-total";
@@ -32842,423 +32809,305 @@ window.__ModuleLoader__.load({
32842
32809
  tag.textContent = css$1;
32843
32810
  document.head.appendChild(tag);
32844
32811
  }
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"
32812
+ var UsageCards_module_default = {
32813
+ "grid": "grid_673061119",
32814
+ "card": "card_673061119",
32815
+ "head": "head_673061119",
32816
+ "title": "title_673061119",
32817
+ "model": "model_673061119",
32818
+ "cacheBadge": "cacheBadge_673061119",
32819
+ "body": "body_673061119",
32820
+ "totalRow": "totalRow_673061119",
32821
+ "total": "total_673061119",
32822
+ "totalShare": "totalShare_673061119",
32823
+ "bar": "bar_673061119",
32824
+ "barSeg": "barSeg_673061119",
32825
+ "barEmpty": "barEmpty_673061119",
32826
+ "statsMain": "statsMain_673061119",
32827
+ "ringWrap": "ringWrap_673061119",
32828
+ "ring": "ring_673061119",
32829
+ "ringTrack": "ringTrack_673061119",
32830
+ "ringCenter": "ringCenter_673061119",
32831
+ "ringTotal": "ringTotal_673061119",
32832
+ "ringCaption": "ringCaption_673061119",
32833
+ "legend": "legend_673061119",
32834
+ "legendRow": "legendRow_673061119",
32835
+ "swatch": "swatch_673061119",
32836
+ "legendName": "legendName_673061119",
32837
+ "legendPercent": "legendPercent_673061119",
32838
+ "legendValue": "legendValue_673061119",
32839
+ "legendSuffix": "legendSuffix_673061119",
32840
+ "empty": "empty_673061119",
32841
+ "toneSystemPrompt": "toneSystemPrompt_673061119",
32842
+ "toneUser": "toneUser_673061119",
32843
+ "toneSkill": "toneSkill_673061119",
32844
+ "toneToolResult": "toneToolResult_673061119",
32845
+ "toneToolDef": "toneToolDef_673061119",
32846
+ "toneInjected": "toneInjected_673061119",
32847
+ "toneAssistant": "toneAssistant_673061119",
32848
+ "toneOutput": "toneOutput_673061119"
32898
32849
  };
32899
32850
  //#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"
32908
- };
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
32851
+ //#region src/client/UsageCards.tsx
32852
+ /** Legend names, one per bucket (the context-window panel's category names are
32853
+ * a different split — files live inside tool results here). */
32854
+ const BUCKET_LABEL = {
32855
+ systemPrompt: "bucketSystemPrompt",
32856
+ user: "bucketUser",
32857
+ skill: "bucketSkill",
32858
+ toolResult: "bucketToolResult",
32859
+ toolDef: "bucketToolDef",
32860
+ injected: "bucketInjected",
32861
+ assistant: "bucketAssistant",
32862
+ output: "bucketOutput"
32916
32863
  };
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"
32864
+ /** Each bucket brings its own accent as a CSS custom property, so the swatch,
32865
+ * the ring segment, the bar segment and the value text all read one variable. */
32866
+ const BUCKET_TONE = {
32867
+ systemPrompt: UsageCards_module_default.toneSystemPrompt,
32868
+ user: UsageCards_module_default.toneUser,
32869
+ skill: UsageCards_module_default.toneSkill,
32870
+ toolResult: UsageCards_module_default.toneToolResult,
32871
+ toolDef: UsageCards_module_default.toneToolDef,
32872
+ injected: UsageCards_module_default.toneInjected,
32873
+ assistant: UsageCards_module_default.toneAssistant,
32874
+ output: UsageCards_module_default.toneOutput
32926
32875
  };
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);
32876
+ /** Approximate value as the card prints it: "≈299.1k". */
32877
+ const approx = (n) => `≈${formatCompact(n)}`;
32878
+ /** Ring geometry: a 116 px donut with a 15 px stroke. */
32879
+ const RING_SIZE = 116;
32880
+ const RING_STROKE = 15;
32881
+ const RING_RADIUS = 101 / 2 - 2;
32882
+ const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
32883
+ /** Donut of bucket shares with the grand total in the middle. Segments are
32884
+ * stroked along one circle and offset by the running length, so the ring is
32885
+ * DPI-independent and needs no chart library. `total` sizes the segments (the
32886
+ * replay estimate); `center` is the exact billed figure the card headlines. */
32887
+ function Ring({ rows, total, center, centerTitle, caption }) {
32888
+ const drawn = rows.filter((row) => row.tokens > 0);
32889
+ let offset = 0;
32954
32890
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32955
- className: ContextWindowPanel_module_default.tip,
32956
- role: "tooltip",
32957
- style: {
32958
- left,
32959
- top
32960
- },
32891
+ className: UsageCards_module_default.ringWrap,
32892
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
32893
+ className: UsageCards_module_default.ring,
32894
+ width: RING_SIZE,
32895
+ height: RING_SIZE,
32896
+ viewBox: `0 0 ${RING_SIZE} ${RING_SIZE}`,
32897
+ role: "img",
32898
+ "aria-label": caption,
32899
+ children: [
32900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: caption }),
32901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
32902
+ className: UsageCards_module_default.ringTrack,
32903
+ cx: RING_SIZE / 2,
32904
+ cy: RING_SIZE / 2,
32905
+ r: RING_RADIUS,
32906
+ fill: "none",
32907
+ strokeWidth: RING_STROKE
32908
+ }),
32909
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
32910
+ transform: `rotate(-90 ${RING_SIZE / 2} ${RING_SIZE / 2})`,
32911
+ children: drawn.map((row) => {
32912
+ const length = total > 0 ? row.tokens / total * RING_CIRCUMFERENCE : 0;
32913
+ const visible = Math.max(length - 1.2, .6);
32914
+ const dash = `${visible} ${RING_CIRCUMFERENCE - visible}`;
32915
+ const circle = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
32916
+ className: BUCKET_TONE[row.key],
32917
+ cx: RING_SIZE / 2,
32918
+ cy: RING_SIZE / 2,
32919
+ r: RING_RADIUS,
32920
+ fill: "none",
32921
+ stroke: "var(--bucket)",
32922
+ strokeWidth: RING_STROKE,
32923
+ strokeDasharray: dash,
32924
+ strokeDashoffset: -offset
32925
+ }, row.key);
32926
+ offset += length;
32927
+ return circle;
32928
+ })
32929
+ })
32930
+ ]
32931
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32932
+ className: UsageCards_module_default.ringCenter,
32933
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
32934
+ className: UsageCards_module_default.ringTotal,
32935
+ title: centerTitle,
32936
+ children: center
32937
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32938
+ className: UsageCards_module_default.ringCaption,
32939
+ children: caption
32940
+ })]
32941
+ })]
32942
+ });
32943
+ }
32944
+ /** One legend entry: swatch, name, share, approximate value. */
32945
+ function LegendRow({ row, percent, suffix, t }) {
32946
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
32947
+ className: `${UsageCards_module_default.legendRow} ${BUCKET_TONE[row.key]}`,
32961
32948
  children: [
32962
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32963
- className: ContextWindowPanel_module_default.tipName,
32964
- children: itemText(item, t)
32949
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
32950
+ className: UsageCards_module_default.swatch,
32951
+ "aria-hidden": "true"
32965
32952
  }),
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
- ]
32953
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32954
+ className: UsageCards_module_default.legendName,
32955
+ children: t(BUCKET_LABEL[row.key])
32956
+ }),
32957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32958
+ className: UsageCards_module_default.legendPercent,
32959
+ children: [percent.toFixed(1), "%"]
32973
32960
  }),
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 })}`]
32961
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32962
+ className: UsageCards_module_default.legendValue,
32963
+ title: formatTokens(row.tokens),
32964
+ children: [approx(row.tokens), suffix === void 0 ? "" : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
32965
+ className: UsageCards_module_default.legendSuffix,
32966
+ children: [" · ", suffix]
32967
+ })]
32977
32968
  })
32978
32969
  ]
32979
32970
  });
32980
32971
  }
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);
32972
+ /** Left card: the live window's composition, its occupancy and the hard cap. */
32973
+ function ContextCard({ context, t }) {
33073
32974
  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", {
32975
+ const rows = contextBucketTotals(context.items);
32976
+ const total = rows.reduce((sum, row) => sum + row.tokens, 0);
32977
+ const capacity = context.contextWindow !== null && context.contextWindow > 0 ? context.contextWindow : total;
32978
+ const occupied = Math.min(100, bucketPercent(reading.usedTokens, capacity));
32979
+ const windowText = context.contextWindow !== null && context.contextWindow > 0 ? t("contextWindowTotal", {
33091
32980
  used: formatCompact(reading.usedTokens),
33092
- window: formatCompact(context.contextWindow),
33093
- percent: String(percent ?? 0)
33094
- });
32981
+ window: formatCompact(context.contextWindow)
32982
+ }) : t("contextWindowTotalUnknown", { used: formatCompact(reading.usedTokens) });
33095
32983
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33096
- className: ContextWindowPanel_module_default.panel,
33097
- "data-dsh-context-window": true,
32984
+ className: `${UsageCards_module_default.card} ${UsageCards_module_default.contextCard}`,
33098
32985
  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
- })]
32986
+ className: UsageCards_module_default.head,
32987
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
32988
+ className: UsageCards_module_default.title,
32989
+ children: t("currentContextTitle")
32990
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32991
+ className: UsageCards_module_default.model,
32992
+ title: `${context.provider} / ${context.model}`,
32993
+ children: [context.provider, context.model].filter(Boolean).join(" · ") || "—"
33158
32994
  })]
33159
32995
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33160
- id: "dsh-context-window-body",
33161
- className: ContextWindowPanel_module_default.body,
33162
- hidden: !open,
32996
+ className: UsageCards_module_default.body,
33163
32997
  children: [
33164
32998
  /* @__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,
32999
+ className: UsageCards_module_default.totalRow,
33000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
33001
+ className: UsageCards_module_default.total,
33002
+ title: formatTokens(reading.usedTokens),
33003
+ children: windowText
33004
+ }), reading.percent !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33005
+ className: UsageCards_module_default.totalShare,
33168
33006
  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
- })
33007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: formatPercent(reading.percent) }),
33008
+ " ",
33009
+ t("contextUsedShort")
33223
33010
  ]
33224
33011
  })]
33225
33012
  }),
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
33013
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33014
+ className: UsageCards_module_default.bar,
33015
+ role: "img",
33016
+ "aria-label": t("currentContextTitle"),
33017
+ children: [rows.map((row) => row.tokens > 0 && total > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33018
+ className: `${UsageCards_module_default.barSeg} ${BUCKET_TONE[row.key]}`,
33019
+ style: { width: `${bucketPercent(row.tokens, total) * occupied / 100}%` }
33020
+ }, row.key) : null), total <= 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageCards_module_default.barEmpty })]
33240
33021
  }),
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))
33022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
33023
+ className: UsageCards_module_default.legend,
33024
+ children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LegendRow, {
33025
+ row,
33026
+ percent: bucketPercent(row.tokens, total),
33027
+ t
33028
+ }, row.key))
33257
33029
  })
33258
33030
  ]
33259
33031
  })]
33260
33032
  });
33261
33033
  }
33034
+ /** Right card: every request's replayed content plus the billed output. */
33035
+ function StatsCard({ detail, t }) {
33036
+ const composition = detail.composition ?? null;
33037
+ const rows = composition?.buckets ?? [];
33038
+ const total = rows.reduce((sum, row) => sum + row.tokens, 0);
33039
+ const billed = composition?.billedTokens ?? 0;
33040
+ const hit = composition !== null ? cacheHitPercentOf(composition.totals) : detail.context?.cacheKnown ? cacheRate(detail.context.cacheHitTokens, detail.context.cacheMissTokens) : null;
33041
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33042
+ className: `${UsageCards_module_default.card} ${UsageCards_module_default.statsCard}`,
33043
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
33044
+ className: UsageCards_module_default.head,
33045
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
33046
+ className: UsageCards_module_default.title,
33047
+ children: t("statsTitle")
33048
+ }), hit !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33049
+ className: UsageCards_module_default.cacheBadge,
33050
+ children: [
33051
+ t("statsCacheHit"),
33052
+ " ",
33053
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", { children: [hit.toFixed(2), "%"] })
33054
+ ]
33055
+ })]
33056
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33057
+ className: UsageCards_module_default.body,
33058
+ children: composition === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33059
+ className: UsageCards_module_default.empty,
33060
+ children: t("statsEmpty")
33061
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33062
+ className: UsageCards_module_default.statsMain,
33063
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Ring, {
33064
+ rows,
33065
+ total,
33066
+ center: formatCompact(billed),
33067
+ centerTitle: formatTokens(billed),
33068
+ caption: t("statsTotal")
33069
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
33070
+ className: UsageCards_module_default.legend,
33071
+ children: USAGE_BUCKET_ORDER.map((key) => {
33072
+ const row = rows.find((item) => item.key === key) ?? {
33073
+ key,
33074
+ tokens: 0
33075
+ };
33076
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LegendRow, {
33077
+ row,
33078
+ percent: bucketPercent(row.tokens, total),
33079
+ suffix: key === "output" ? t("statsIncludesThinking") : void 0,
33080
+ t
33081
+ }, key);
33082
+ })
33083
+ })]
33084
+ }) })
33085
+ })]
33086
+ });
33087
+ }
33088
+ /** The pair, rendered where the context-window panel used to sit. */
33089
+ function SessionUsageCards({ detail, t }) {
33090
+ if (detail.context === null || detail.context === void 0) {
33091
+ if (detail.composition === null || detail.composition === void 0) return null;
33092
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33093
+ className: UsageCards_module_default.grid,
33094
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsCard, {
33095
+ detail,
33096
+ t
33097
+ })
33098
+ });
33099
+ }
33100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33101
+ className: UsageCards_module_default.grid,
33102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextCard, {
33103
+ context: detail.context,
33104
+ t
33105
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsCard, {
33106
+ detail,
33107
+ t
33108
+ })]
33109
+ });
33110
+ }
33262
33111
  //#endregion
33263
33112
  //#region src/client/model-logos-data.ts
33264
33113
  /**
@@ -34003,11 +33852,10 @@ window.__ModuleLoader__.load({
34003
33852
  detail,
34004
33853
  t
34005
33854
  }),
34006
- detail.context ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextWindowPanel, {
34007
- context: detail.context,
34008
- t,
34009
- updatedAt
34010
- }) : null,
33855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionUsageCards, {
33856
+ detail,
33857
+ t
33858
+ }),
34011
33859
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Records, {
34012
33860
  detail,
34013
33861
  t
@@ -34152,6 +34000,7 @@ window.__ModuleLoader__.load({
34152
34000
  asOf: "统计截至",
34153
34001
  refresh: "刷新",
34154
34002
  contextTitle: "上下文窗口",
34003
+ currentContextTitle: "当前上下文",
34155
34004
  contextUsed: "已用上下文",
34156
34005
  contextLive: "实时",
34157
34006
  contextWindowKnown: "模型窗口 {value} · 已用 {percent}%",
@@ -34181,7 +34030,23 @@ window.__ModuleLoader__.load({
34181
34030
  contextEmpty: "本会话还没有可统计的上下文",
34182
34031
  contextTipShare: "占上下文 {percent}",
34183
34032
  contextTipWindow: "占窗口 {percent}",
34184
- contextEstimateNote: "色块按历史重放估算 {estimate};供应商实测提示词 {billed}(上游已压缩或裁剪)"
34033
+ contextEstimateNote: "色块按历史重放估算 {estimate};供应商实测提示词 {billed}(上游已压缩或裁剪)",
34034
+ statsTitle: "Token 统计",
34035
+ statsTotal: "总用量",
34036
+ statsCacheHit: "缓存命中",
34037
+ statsIncludesThinking: "含思考",
34038
+ statsEmpty: "本会话还没有可统计的 token",
34039
+ contextWindowTotal: "{used} / {window} tokens",
34040
+ contextWindowTotalUnknown: "{used} tokens",
34041
+ contextUsedShort: "上下文已用",
34042
+ bucketSystemPrompt: "系统提示词",
34043
+ bucketUser: "用户消息",
34044
+ bucketSkill: "技能注入",
34045
+ bucketToolResult: "工具结果",
34046
+ bucketToolDef: "工具定义",
34047
+ bucketInjected: "注入内容",
34048
+ bucketAssistant: "助手消息",
34049
+ bucketOutput: "输出"
34185
34050
  };
34186
34051
  const usageEn = {
34187
34052
  tab: "Usage",
@@ -34243,6 +34108,7 @@ window.__ModuleLoader__.load({
34243
34108
  asOf: "as of",
34244
34109
  refresh: "Refresh",
34245
34110
  contextTitle: "Context window",
34111
+ currentContextTitle: "Current context",
34246
34112
  contextUsed: "Context used",
34247
34113
  contextLive: "Live",
34248
34114
  contextWindowKnown: "Window {value} · {percent}% used",
@@ -34272,7 +34138,23 @@ window.__ModuleLoader__.load({
34272
34138
  contextEmpty: "Nothing in this session’s context yet",
34273
34139
  contextTipShare: "{percent} of context",
34274
34140
  contextTipWindow: "{percent} of window",
34275
- contextEstimateNote: "Blocks estimate {estimate} by replay; the provider billed {billed} after upstream compression"
34141
+ contextEstimateNote: "Blocks estimate {estimate} by replay; the provider billed {billed} after upstream compression",
34142
+ statsTitle: "Token stats",
34143
+ statsTotal: "Total usage",
34144
+ statsCacheHit: "Cache hit",
34145
+ statsIncludesThinking: "incl. thinking",
34146
+ statsEmpty: "No token spend recorded for this session yet",
34147
+ contextWindowTotal: "{used} / {window} tokens",
34148
+ contextWindowTotalUnknown: "{used} tokens",
34149
+ contextUsedShort: "context used",
34150
+ bucketSystemPrompt: "System prompt",
34151
+ bucketUser: "User messages",
34152
+ bucketSkill: "Skill injection",
34153
+ bucketToolResult: "Tool results",
34154
+ bucketToolDef: "Tool definitions",
34155
+ bucketInjected: "Injected content",
34156
+ bucketAssistant: "Assistant messages",
34157
+ bucketOutput: "Output"
34276
34158
  };
34277
34159
  const usageZhTW = {
34278
34160
  ...usageZh,
@@ -34334,6 +34216,7 @@ window.__ModuleLoader__.load({
34334
34216
  asOf: "統計截至",
34335
34217
  refresh: "重新整理",
34336
34218
  contextTitle: "上下文視窗",
34219
+ currentContextTitle: "當前上下文",
34337
34220
  contextUsed: "已用上下文",
34338
34221
  contextLive: "即時",
34339
34222
  contextWindowKnown: "模型視窗 {value} · 已用 {percent}%",
@@ -34363,7 +34246,23 @@ window.__ModuleLoader__.load({
34363
34246
  contextEmpty: "本會話還沒有可統計的上下文",
34364
34247
  contextTipShare: "佔上下文 {percent}",
34365
34248
  contextTipWindow: "佔視窗 {percent}",
34366
- contextEstimateNote: "色塊按歷史重放估算 {estimate};供應商實測提示詞 {billed}(上游已壓縮或裁剪)"
34249
+ contextEstimateNote: "色塊按歷史重放估算 {estimate};供應商實測提示詞 {billed}(上游已壓縮或裁剪)",
34250
+ statsTitle: "Token 統計",
34251
+ statsTotal: "總用量",
34252
+ statsCacheHit: "快取命中",
34253
+ statsIncludesThinking: "含思考",
34254
+ statsEmpty: "本會話還沒有可統計的 token",
34255
+ contextWindowTotal: "{used} / {window} tokens",
34256
+ contextWindowTotalUnknown: "{used} tokens",
34257
+ contextUsedShort: "上下文已用",
34258
+ bucketSystemPrompt: "系統提示詞",
34259
+ bucketUser: "使用者訊息",
34260
+ bucketSkill: "技能注入",
34261
+ bucketToolResult: "工具結果",
34262
+ bucketToolDef: "工具定義",
34263
+ bucketInjected: "注入內容",
34264
+ bucketAssistant: "助手訊息",
34265
+ bucketOutput: "輸出"
34367
34266
  };
34368
34267
  //#endregion
34369
34268
  //#region src/client/index.tsx