dsh-token-total 1.0.6 → 1.0.8

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
@@ -29650,6 +29650,20 @@ window.__ModuleLoader__.load({
29650
29650
  }
29651
29651
  return out;
29652
29652
  }
29653
+ /** Decode throughput across rows: Σ(output tokens) / Σ(decode window), matching
29654
+ * the `sessionStats` projection's decodeTokens/decodeMs rate that the composer
29655
+ * bottom bar shows. Rows without a decode window (failed attempts, cancelled
29656
+ * steps, running requests) are excluded, so the card, the turn summaries and
29657
+ * the bottom bar all report one speed. */
29658
+ function decodeRateOf(rows) {
29659
+ let ms = 0;
29660
+ let tokens = 0;
29661
+ for (const row of rows) if (typeof row.decodeMs === "number" && Number.isFinite(row.decodeMs)) {
29662
+ ms += row.decodeMs;
29663
+ tokens += row.totals.outputTokens;
29664
+ }
29665
+ return ms > 0 ? tokens / (ms / 1e3) : null;
29666
+ }
29653
29667
  /** Canonicalize a DISPLAY NAME, never a route id. Case and word separators
29654
29668
  * do not distinguish models, but versions, punctuation and suffixes do:
29655
29669
  * gpt-6.1-sol / GPT 6.1 sol -> gpt-6.1-sol, not gpt-6.1 or gpt-61-sol.
@@ -32638,6 +32652,7 @@ window.__ModuleLoader__.load({
32638
32652
  (0, react.useEffect)(() => {
32639
32653
  let active = true;
32640
32654
  let pending = false;
32655
+ let wantedNow = false;
32641
32656
  let timer;
32642
32657
  let controller;
32643
32658
  const request = () => {
@@ -32649,23 +32664,29 @@ window.__ModuleLoader__.load({
32649
32664
  if (active) setReading({
32650
32665
  sessionId,
32651
32666
  detail,
32652
- error: false
32667
+ error: false,
32668
+ updatedAt: Date.now()
32653
32669
  });
32654
32670
  }).catch(() => {
32655
32671
  if (active) setReading((previous) => ({
32656
32672
  sessionId,
32657
32673
  detail: previous?.sessionId === sessionId ? previous.detail : null,
32658
- error: true
32674
+ error: true,
32675
+ updatedAt: previous?.sessionId === sessionId ? previous.updatedAt : null
32659
32676
  }));
32660
32677
  }).finally(() => {
32661
32678
  pending = false;
32662
- if (active) timer = setTimeout(request, DETAIL_POLL_MS);
32679
+ if (active) timer = setTimeout(request, wantedNow ? 0 : DETAIL_POLL_MS);
32680
+ wantedNow = false;
32663
32681
  });
32664
32682
  };
32665
32683
  const wake = () => {
32666
32684
  if (document.visibilityState === "visible") request();
32667
32685
  };
32668
- refresh.current = request;
32686
+ refresh.current = () => {
32687
+ if (pending) wantedNow = true;
32688
+ else request();
32689
+ };
32669
32690
  document.addEventListener("visibilitychange", wake);
32670
32691
  request();
32671
32692
  return () => {
@@ -32681,7 +32702,8 @@ window.__ModuleLoader__.load({
32681
32702
  detail: current?.detail ?? null,
32682
32703
  loading: current === null,
32683
32704
  error: current?.error ?? false,
32684
- retry: () => refresh.current?.()
32705
+ refresh: () => refresh.current?.(),
32706
+ updatedAt: current?.updatedAt ?? null
32685
32707
  };
32686
32708
  }
32687
32709
  //#endregion
@@ -32854,7 +32876,7 @@ window.__ModuleLoader__.load({
32854
32876
  }
32855
32877
  //#endregion
32856
32878
  //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/SessionUsageView.module.css?inline
32857
- const css = "/* A light, self-contained Usage surface. Tables fit the available width:\n * no horizontal scroll containers, fixed column contracts, titles for names.\n * Stat cards follow the reference dashboard: tinted left edge + icon chip,\n * label, big value, small subtitle. Request log folds by conversation turn. */\n.scroll_900471699 { height:100%; min-height:0; overflow-y:auto; background:#ffffff; color:#252936; color-scheme:light; container-type:inline-size; }\n.page_900471699 { box-sizing:border-box; width:100%; max-width:1160px; margin:0 auto; padding:24px 26px 32px; font-family:system-ui,-apple-system,\"Segoe UI\",sans-serif; font-size:13px; }\n.page_900471699 * { box-sizing:border-box; }\n.scope_900471699 { color:#8790a3; font-size:11px; margin:0 0 14px; }\n.stats_900471699 { display:grid; gap:12px; margin-bottom:18px; }\n.statRow_900471699 { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px; }\n.stat_900471699 { min-width:0; position:relative; overflow:hidden; background:#fff; border:1px solid #e4e7ee; border-left:4px solid var(--tone); border-radius:12px; padding:12px 16px 11px; }\n.statHead_900471699 { display:flex; align-items:center; gap:8px; margin-bottom:8px; }\n.statIcon_900471699 { display:flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:10px; color:#fff; background:linear-gradient(135deg, var(--tone), var(--tone-soft)); flex-shrink:0; box-shadow:0 3px 8px var(--tone-soft); }\n.statMark_900471699 { position:absolute; right:-14px; bottom:-18px; width:92px; height:92px; color:var(--tone); pointer-events:none; }\n.statLabel_900471699 { font-size:12px; color:#7d8495; }\n.statValue_900471699 { display:block; font-size:25px; font-weight:650; letter-spacing:-0.5px; white-space:nowrap; font-variant-numeric:tabular-nums; }\n.statSub_900471699 { display:block; margin-top:5px; font-size:11px; color:#939aac; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.toneBlue_900471699 { --tone:#4f79f6; --tone-soft:#eaf0fe; }\n.tonePurple_900471699 { --tone:#8b5cf6; --tone-soft:#f1ebfe; }\n.toneGreen_900471699 { --tone:#18a97b; --tone-soft:#e5f6f0; }\n.toneOrange_900471699 { --tone:#e8892c; --tone-soft:#fcf1e4; }\n.toneTeal_900471699 { --tone:#16a4b8; --tone-soft:#e4f5f8; }\n.tonePink_900471699 { --tone:#d9538c; --tone-soft:#fbeaf1; }\n.card_900471699 { min-width:0; background:#fff; border:1px solid #e4e7ee; border-radius:12px; margin-bottom:18px; overflow:hidden; }\n.cardHead_900471699 { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:54px; padding:15px 18px; border-bottom:1px solid #eef0f4; }\n.cardHead_900471699 h2 { font-size:14px; font-weight:600; margin:0; }\n.cardHead_900471699 > span { display:flex; gap:7px; align-items:center; font-size:11px; }\n.count_900471699 { display:inline-flex; align-items:center; justify-content:center; min-width:21px; padding:2px 6px; font-size:10px; color:#8b93a3; background:#f3f5f8; border-radius:5px; margin-left:5px; font-weight:500; }\n.mid_900471699 { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:18px; align-items:stretch; }\n.mid_900471699 .card_900471699 { height:100%; margin-bottom:0; }\n.mid_900471699 { margin-bottom:18px; }\n.table_900471699 { border-collapse:collapse; width:100%; table-layout:fixed; font-size:11px; }\n.table_900471699 th { color:#929aaa; font-weight:500; padding:12px 5px; text-align:right; vertical-align:middle; line-height:1.3; overflow-wrap:anywhere; }\n.table_900471699 td { padding:13px 5px; text-align:right; border-top:1px solid #eef0f4; color:#626c80; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.table_900471699 th:first-child,.table_900471699 td:first-child { text-align:left; padding-left:16px; }\n.table_900471699 th:last-child,.table_900471699 td:last-child { padding-right:16px; }\n.childName_900471699 { width:28%; }\n.childRequests_900471699 { width:11%; }\n.modelName_900471699 { width:30%; }\n.modelRequests_900471699 { width:11%; }\n.modelHit_900471699 { width:16%; }\n.ellipsis_900471699 { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:#394354; }\n/* By-model rows lead with the model's brand logo; the name keeps truncating\n * inside the flexible remainder of the cell. */\n.modelNameCell_900471699 { display:flex; align-items:center; gap:6px; min-width:0; }\n.modelNameCell_900471699 .ellipsis_900471699 { flex:1; min-width:0; }\n/* Gray provider label under a model name shown by several rows (同名模型). */\n.modelProvider_900471699 { display:block; margin-top:2px; font-size:10px; color:#939aaa; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.number_900471699 { font-variant-numeric:tabular-nums; }\n.speedCell_900471699 { display:inline-flex; align-items:baseline; gap:3px; white-space:nowrap; }\n.speedCell_900471699 small { font-size:9px; color:#929aaa; }\n.good_900471699 { color:#21a17b; }\n.warn_900471699 { color:#d69a21; }\n.bad_900471699 { color:#e06571; }\n.live_900471699 { color:#169bb3; }\n.muted_900471699 { color:#939aaa; }\n.empty_900471699 { margin:0; padding:28px 18px; color:#9aa2b1; font-size:12px; text-align:center; }\n.action_900471699 { color:#818b9e; background:none; border:0; padding:3px 0; font:inherit; font-size:11px; cursor:pointer; }\n.action_900471699:hover { color:#279cb6; }\n.action_900471699:focus-visible,.turnHead_900471699:focus-visible { outline:2px solid #2ab1c6; outline-offset:-2px; }\n.turnList_900471699 { padding:0 14px 14px; }\n.turn_900471699 { margin-top:10px; border:1px solid #e7eaf0; border-radius:9px; background:#fff; overflow:hidden; }\n.turnHead_900471699 { display:grid; grid-template-columns:12px minmax(0,1fr) auto; align-items:center; gap:12px; padding:13px 14px; width:100%; text-align:left; background:#f8f9fc; border:0; cursor:pointer; color:#616c7f; font:inherit; font-size:11px; }\n.chevron_900471699 { color:#99a1af; transition:transform 0.2s; }\n.rotated_900471699 { transform:rotate(90deg); }\n.turnTitle_900471699 { display:flex; align-items:center; gap:6px; min-width:0; font-size:12px; font-weight:600; color:#394354; }\n/* Turn topic is the group title now (the page header owns the session name),\n * so it carries the primary text color and truncates before the metrics. */\n.turnTopic_900471699 { min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\n/* A subagent's turns look like this session's turns apart from the agent tag;\n * the tag keeps the full session name in its tooltip. */\n.turnAgent_900471699 { flex:none; padding:1px 6px; border-radius:5px; background:#e9edf4; color:#6f7889; font-size:10px; font-weight:500; }\n.turnSummary_900471699 { display:flex; gap:5px; align-items:center; white-space:nowrap; }\n.turnSummary_900471699 > span:last-child { margin-left:8px; }\n.turnBody_900471699 { border-top:1px solid #eef0f4; }\n.turnBody_900471699[hidden] { display:none; }\n/* A request row is one line: time, what answered, and its metrics pinned to the\n * row end. The metrics never wrap; they truncate with a tooltip before the head\n * gives up its model name. */\n.turnRequest_900471699 { display:grid; grid-template-columns:64px minmax(0,1fr) auto; align-items:center; gap:12px; padding:10px 16px; border-top:1px solid #f3f5f9; }\n.turnRequest_900471699:first-child { border-top:0; }\n.turnRequestHead_900471699 { display:flex; align-items:center; gap:8px; font-size:11px; color:#616c7f; min-width:0; }\n.requestTime_900471699 { color:#9aa3b3; font-variant-numeric:tabular-nums; flex-shrink:0; }\n.statusDot_900471699 { width:5px; height:5px; border-radius:50%; background:currentColor; flex-shrink:0; }\n/* Per-model brand logo: colors are baked into each logo's markup, only the\n * layout contract lives here. Unknown models get the neutral gray fallback. */\n.modelLogo_900471699 { flex-shrink:0; }\n.recordModel_900471699 { min-width:0; display:flex; align-items:center; gap:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#394354; font-weight:500; }\n.routeText_900471699 { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#8b93a4; flex-shrink:1; }\n.effort_900471699 { white-space:nowrap; color:#9aa3b3; background:#f1f3f7; border-radius:5px; padding:1px 6px; }\n.recordMeta_900471699 { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:right; font-size:10px; color:#949cab; font-variant-numeric:tabular-nums; }\n.note_900471699 { font-size:10px; line-height:1.7; color:#9aa3b3; margin:0; }\n.error_900471699 { background:#fff4f1; color:#b86751; border:1px solid #f4d8ce; border-radius:9px; padding:12px 16px; margin-bottom:16px; font-size:12px; display:flex; gap:16px; justify-content:space-between; }\n@container (max-width:1050px) {\n .mid_900471699 { grid-template-columns:minmax(0,1fr); }\n .statRow_900471699 { grid-template-columns:repeat(2,minmax(0,1fr)); }\n .statValue_900471699 { font-size:23px; }\n .table_900471699 { font-size:12px; }\n .childName_900471699 { width:34%; }\n}\n@container (max-width:700px) {\n .page_900471699 { padding:18px 14px 24px; }\n .stat_900471699 { padding:12px 12px 11px; }\n .statValue_900471699 { font-size:20px; }\n .table_900471699 { font-size:10px; }\n .table_900471699 th,.table_900471699 td { padding:10px 3px; }\n .table_900471699 th:first-child,.table_900471699 td:first-child { padding-left:10px; }\n .table_900471699 th:last-child,.table_900471699 td:last-child { padding-right:10px; }\n .childName_900471699 { width:25%; }\n .childRequests_900471699 { width:11%; }\n .modelName_900471699 { width:25%; }\n .modelRequests_900471699 { width:11%; }\n .modelHit_900471699 { width:17%; }\n .turnHead_900471699 { gap:8px; padding:11px 10px; }\n .turnSummary_900471699 { grid-column:2; font-size:10px; }\n .turnRequest_900471699 { grid-template-columns:56px minmax(120px,1fr) auto; gap:8px; padding:10px; }\n .turnRequestHead_900471699 { gap:6px; }\n .routeText_900471699 { display:none; }\n}\n@media (prefers-reduced-motion:reduce) {\n .chevron_900471699 { transition:none; }\n}\n";
32879
+ const css = "/* A light, self-contained Usage surface. Tables fit the available width:\n * no horizontal scroll containers, fixed column contracts, titles for names.\n * Six stat cards sit in one row (≤1050px: 3-up, ≤700px: 2-up) as quiet white\n * surfaces on a pale canvas: no accent edges or watermarks, a flat tone chip\n * leading each label, hierarchy from the type scale and a hairline above the\n * footnote. Request log folds by conversation turn. */\n.scroll_900471699 { height:100%; min-height:0; overflow-y:auto; background:#f6f7f9; color:#252936; color-scheme:light; container-type:inline-size; }\n.page_900471699 { box-sizing:border-box; width:100%; max-width:1160px; margin:0 auto; padding:24px 26px 32px; font-family:system-ui,-apple-system,\"Segoe UI\",sans-serif; font-size:13px; }\n.page_900471699 * { box-sizing:border-box; }\n/* Freshness header: live badge + last-read stamp on the left, manual refresh\n * pinned to the right edge. */\n.head_900471699 { display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 12px; }\n.headStatus_900471699 { display:flex; align-items:center; gap:7px; min-width:0; font-size:11px; color:#8790a3; }\n.liveDot_900471699 { width:6px; height:6px; border-radius:50%; background:#18a97b; box-shadow:0 0 0 3px #e5f6f0; flex-shrink:0; }\n.asOf_900471699 { color:#9aa3b3; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.refresh_900471699 { display:inline-flex; align-items:center; gap:5px; flex:none; color:#6f7a8d; background:#fff; border:1px solid #e4e7ee; border-radius:8px; padding:5px 11px; font:inherit; font-size:11px; cursor:pointer; }\n.refresh_900471699:hover { color:#279cb6; border-color:#cbd3e0; }\n.refresh_900471699:disabled { opacity:.55; cursor:default; }\n.scope_900471699 { color:#8790a3; font-size:11px; margin:0 0 14px; }\n.stats_900471699 { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:8px; margin-bottom:16px; }\n.stat_900471699 { min-width:0; position:relative; background:#fff; border:1px solid #e7eaf0; border-radius:12px; padding:12px 14px 11px; transition:border-color .18s ease, box-shadow .18s ease, transform .18s ease; }\n.stat_900471699:hover { border-color:#d9dfe9; box-shadow:0 14px 30px -22px rgba(23,32,54,.32); transform:translateY(-1px); }\n.statHead_900471699 { display:flex; align-items:center; gap:5px; margin-bottom:6px; }\n.statIcon_900471699 { display:flex; align-items:center; justify-content:center; width:20px; height:20px; border-radius:6px; color:var(--tone); background:var(--tone-soft); flex-shrink:0; }\n.statLabel_900471699 { font-size:12px; font-weight:500; color:#7b8494; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\n.statValue_900471699 { display:block; font-size:21px; font-weight:650; letter-spacing:-0.4px; color:#1b2130; white-space:nowrap; font-variant-numeric:tabular-nums; }\n.statSub_900471699 { display:block; margin-top:8px; padding-top:7px; border-top:1px solid #f2f4f8; font-size:11px; color:#939aab; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.toneBlue_900471699 { --tone:#4f79f6; --tone-soft:#eaf0fe; }\n.tonePurple_900471699 { --tone:#8b5cf6; --tone-soft:#f1ebfe; }\n.toneGreen_900471699 { --tone:#18a97b; --tone-soft:#e5f6f0; }\n.toneOrange_900471699 { --tone:#e8892c; --tone-soft:#fcf1e4; }\n.toneTeal_900471699 { --tone:#16a4b8; --tone-soft:#e4f5f8; }\n.card_900471699 { min-width:0; background:#fff; border:1px solid #e4e7ee; border-radius:12px; margin-bottom:18px; overflow:hidden; }\n.cardHead_900471699 { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:54px; padding:15px 18px; border-bottom:1px solid #eef0f4; }\n.cardHead_900471699 h2 { font-size:14px; font-weight:600; margin:0; }\n.cardHead_900471699 > span { display:flex; gap:7px; align-items:center; font-size:11px; }\n.count_900471699 { display:inline-flex; align-items:center; justify-content:center; min-width:21px; padding:2px 6px; font-size:10px; color:#8b93a3; background:#f3f5f8; border-radius:5px; margin-left:5px; font-weight:500; }\n.mid_900471699 { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:18px; align-items:stretch; }\n.mid_900471699 .card_900471699 { height:100%; margin-bottom:0; }\n.mid_900471699 { margin-bottom:18px; }\n.table_900471699 { border-collapse:collapse; width:100%; table-layout:fixed; font-size:11px; }\n.table_900471699 th { color:#929aaa; font-weight:500; padding:12px 5px; text-align:right; vertical-align:middle; line-height:1.3; overflow-wrap:anywhere; }\n.table_900471699 td { padding:13px 5px; text-align:right; border-top:1px solid #eef0f4; color:#626c80; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.table_900471699 th:first-child,.table_900471699 td:first-child { text-align:left; padding-left:16px; }\n.table_900471699 th:last-child,.table_900471699 td:last-child { padding-right:16px; }\n.childName_900471699 { width:28%; }\n.childRequests_900471699 { width:11%; }\n.modelName_900471699 { width:30%; }\n.modelRequests_900471699 { width:11%; }\n.modelHit_900471699 { width:16%; }\n.ellipsis_900471699 { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:#394354; }\n/* By-model rows lead with the model's brand logo; the name keeps truncating\n * inside the flexible remainder of the cell. */\n.modelNameCell_900471699 { display:flex; align-items:center; gap:6px; min-width:0; }\n.modelNameCell_900471699 .ellipsis_900471699 { flex:1; min-width:0; }\n/* Gray provider label under a model name shown by several rows (同名模型). */\n.modelProvider_900471699 { display:block; margin-top:2px; font-size:10px; color:#939aaa; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.number_900471699 { font-variant-numeric:tabular-nums; }\n.speedCell_900471699 { display:inline-flex; align-items:baseline; gap:3px; white-space:nowrap; }\n.speedCell_900471699 small { font-size:9px; color:#929aaa; }\n.good_900471699 { color:#21a17b; }\n.warn_900471699 { color:#d69a21; }\n.bad_900471699 { color:#e06571; }\n.live_900471699 { color:#169bb3; }\n.muted_900471699 { color:#939aaa; }\n.empty_900471699 { margin:0; padding:28px 18px; color:#9aa2b1; font-size:12px; text-align:center; }\n.action_900471699 { color:#818b9e; background:none; border:0; padding:3px 0; font:inherit; font-size:11px; cursor:pointer; }\n.action_900471699:hover { color:#279cb6; }\n.action_900471699:focus-visible,.turnHead_900471699:focus-visible,.refresh_900471699:focus-visible { outline:2px solid #2ab1c6; outline-offset:-2px; }\n.turnList_900471699 { padding:0 14px 14px; }\n.turn_900471699 { margin-top:10px; border:1px solid #e7eaf0; border-radius:9px; background:#fff; overflow:hidden; }\n.turnHead_900471699 { display:grid; grid-template-columns:12px minmax(0,1fr) auto; align-items:center; gap:12px; padding:13px 14px; width:100%; text-align:left; background:#f8f9fc; border:0; cursor:pointer; color:#616c7f; font:inherit; font-size:11px; }\n.chevron_900471699 { color:#99a1af; transition:transform 0.2s; }\n.rotated_900471699 { transform:rotate(90deg); }\n.turnTitle_900471699 { display:flex; align-items:center; gap:6px; min-width:0; font-size:12px; font-weight:600; color:#394354; }\n/* Turn topic is the group title now (the page header owns the session name),\n * so it carries the primary text color and truncates before the metrics. */\n.turnTopic_900471699 { min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\n/* A subagent's turns look like this session's turns apart from the agent tag;\n * the tag keeps the full session name in its tooltip. */\n.turnAgent_900471699 { flex:none; padding:1px 6px; border-radius:5px; background:#e9edf4; color:#6f7889; font-size:10px; font-weight:500; }\n.turnSummary_900471699 { display:flex; gap:5px; align-items:center; white-space:nowrap; }\n.turnSummary_900471699 > span:last-child { margin-left:8px; }\n.turnBody_900471699 { border-top:1px solid #eef0f4; }\n.turnBody_900471699[hidden] { display:none; }\n/* A request row is one line: time, what answered, and its metrics pinned to the\n * row end. The metrics never wrap; they truncate with a tooltip before the head\n * gives up its model name. */\n.turnRequest_900471699 { display:grid; grid-template-columns:64px minmax(0,1fr) auto; align-items:center; gap:12px; padding:10px 16px; border-top:1px solid #f3f5f9; }\n.turnRequest_900471699:first-child { border-top:0; }\n.turnRequestHead_900471699 { display:flex; align-items:center; gap:8px; font-size:11px; color:#616c7f; min-width:0; }\n.requestTime_900471699 { color:#9aa3b3; font-variant-numeric:tabular-nums; flex-shrink:0; }\n.statusDot_900471699 { width:5px; height:5px; border-radius:50%; background:currentColor; flex-shrink:0; }\n/* Per-model brand logo: colors are baked into each logo's markup, only the\n * layout contract lives here. Unknown models get the neutral gray fallback. */\n.modelLogo_900471699 { flex-shrink:0; }\n.recordModel_900471699 { min-width:0; display:flex; align-items:center; gap:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#394354; font-weight:500; }\n.routeText_900471699 { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#8b93a4; flex-shrink:1; }\n.effort_900471699 { white-space:nowrap; color:#9aa3b3; background:#f1f3f7; border-radius:5px; padding:1px 6px; }\n.recordMeta_900471699 { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:right; font-size:10px; color:#949cab; font-variant-numeric:tabular-nums; }\n.note_900471699 { font-size:10px; line-height:1.7; color:#9aa3b3; margin:0; }\n.error_900471699 { background:#fff4f1; color:#b86751; border:1px solid #f4d8ce; border-radius:9px; padding:12px 16px; margin-bottom:16px; font-size:12px; display:flex; gap:16px; justify-content:space-between; }\n@container (max-width:1050px) {\n .mid_900471699 { grid-template-columns:minmax(0,1fr); }\n .stats_900471699 { grid-template-columns:repeat(3,minmax(0,1fr)); }\n .table_900471699 { font-size:12px; }\n .childName_900471699 { width:34%; }\n}\n@container (max-width:700px) {\n .page_900471699 { padding:18px 14px 24px; }\n .stats_900471699 { grid-template-columns:repeat(2,minmax(0,1fr)); }\n .stat_900471699 { padding:10px 12px 9px; }\n .statValue_900471699 { font-size:18px; }\n .table_900471699 { font-size:10px; }\n .table_900471699 th,.table_900471699 td { padding:10px 3px; }\n .table_900471699 th:first-child,.table_900471699 td:first-child { padding-left:10px; }\n .table_900471699 th:last-child,.table_900471699 td:last-child { padding-right:10px; }\n .childName_900471699 { width:25%; }\n .childRequests_900471699 { width:11%; }\n .modelName_900471699 { width:25%; }\n .modelRequests_900471699 { width:11%; }\n .modelHit_900471699 { width:17%; }\n .turnHead_900471699 { gap:8px; padding:11px 10px; }\n .turnSummary_900471699 { grid-column:2; font-size:10px; }\n .turnRequest_900471699 { grid-template-columns:56px minmax(120px,1fr) auto; gap:8px; padding:10px; }\n .turnRequestHead_900471699 { gap:6px; }\n .routeText_900471699 { display:none; }\n}\n@media (prefers-reduced-motion:reduce) {\n .chevron_900471699 { transition:none; }\n .stat_900471699 { transition:none; }\n .stat_900471699:hover { transform:none; }\n}\n";
32858
32880
  const tagId = "dsh-token-total/SessionUsageView.module.css";
32859
32881
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
32860
32882
  const tag = document.createElement("style");
@@ -32866,13 +32888,16 @@ window.__ModuleLoader__.load({
32866
32888
  var SessionUsageView_module_default = {
32867
32889
  "scroll": "scroll_900471699",
32868
32890
  "page": "page_900471699",
32891
+ "head": "head_900471699",
32892
+ "headStatus": "headStatus_900471699",
32893
+ "liveDot": "liveDot_900471699",
32894
+ "asOf": "asOf_900471699",
32895
+ "refresh": "refresh_900471699",
32869
32896
  "scope": "scope_900471699",
32870
32897
  "stats": "stats_900471699",
32871
- "statRow": "statRow_900471699",
32872
32898
  "stat": "stat_900471699",
32873
32899
  "statHead": "statHead_900471699",
32874
32900
  "statIcon": "statIcon_900471699",
32875
- "statMark": "statMark_900471699",
32876
32901
  "statLabel": "statLabel_900471699",
32877
32902
  "statValue": "statValue_900471699",
32878
32903
  "statSub": "statSub_900471699",
@@ -32881,7 +32906,6 @@ window.__ModuleLoader__.load({
32881
32906
  "toneGreen": "toneGreen_900471699",
32882
32907
  "toneOrange": "toneOrange_900471699",
32883
32908
  "toneTeal": "toneTeal_900471699",
32884
- "tonePink": "tonePink_900471699",
32885
32909
  "card": "card_900471699",
32886
32910
  "cardHead": "cardHead_900471699",
32887
32911
  "count": "count_900471699",
@@ -32971,50 +32995,20 @@ window.__ModuleLoader__.load({
32971
32995
  hit: "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z",
32972
32996
  speed: "M13 2 4 14h7l-1 8 9-12h-7l1-8Z",
32973
32997
  input: "M12 3v12M7 10l5 5 5-5M4 21h16",
32974
- output: "M12 21V9M7 14l5-5 5 5M4 3h16",
32975
- read: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7ZM15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z",
32976
- write: "M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5Z"
32998
+ output: "M12 21V9M7 14l5-5 5 5M4 3h16"
32977
32999
  };
32978
33000
  function Stat({ tone, name, label, value, sub, title }) {
32979
33001
  const icon = icons[name];
32980
33002
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
32981
33003
  className: `${SessionUsageView_module_default.stat} ${tone}`,
32982
33004
  children: [
32983
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
32984
- className: SessionUsageView_module_default.statMark,
32985
- viewBox: "0 0 24 24",
32986
- "aria-hidden": "true",
32987
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
32988
- id: `usage-mark-${name}`,
32989
- x1: "0",
32990
- y1: "0",
32991
- x2: "1",
32992
- y2: "1",
32993
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
32994
- offset: "0",
32995
- stopColor: "currentColor",
32996
- stopOpacity: "0.3"
32997
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
32998
- offset: "1",
32999
- stopColor: "currentColor",
33000
- stopOpacity: "0.03"
33001
- })]
33002
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
33003
- d: icon,
33004
- fill: `url(#usage-mark-${name})`,
33005
- stroke: `url(#usage-mark-${name})`,
33006
- strokeWidth: "1.1",
33007
- strokeLinecap: "round",
33008
- strokeLinejoin: "round"
33009
- })]
33010
- }),
33011
33005
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33012
33006
  className: SessionUsageView_module_default.statHead,
33013
33007
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33014
33008
  className: SessionUsageView_module_default.statIcon,
33015
33009
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
33016
- width: "18",
33017
- height: "18",
33010
+ width: "12",
33011
+ height: "12",
33018
33012
  viewBox: "0 0 24 24",
33019
33013
  fill: "none",
33020
33014
  stroke: "currentColor",
@@ -33047,76 +33041,53 @@ window.__ModuleLoader__.load({
33047
33041
  const failRate = detail.requests > 0 ? (failed / detail.requests * 100).toFixed(1) : "0.0";
33048
33042
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33049
33043
  className: SessionUsageView_module_default.stats,
33050
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33051
- className: SessionUsageView_module_default.statRow,
33052
- children: [
33053
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33054
- tone: SessionUsageView_module_default.toneBlue,
33055
- name: "requests",
33056
- label: t("requestsCount"),
33057
- value: formatTokens(detail.requests),
33058
- sub: `${t("failed")} ${failed} · ${failRate}%`
33059
- }),
33060
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33061
- tone: SessionUsageView_module_default.tonePurple,
33062
- name: "tokens",
33063
- label: t("tokens"),
33064
- value: formatCompact(billed),
33065
- title: formatTokens(billed),
33066
- sub: `${formatTokens(billed)} tokens`
33067
- }),
33068
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33069
- tone: SessionUsageView_module_default.toneGreen,
33070
- name: "hit",
33071
- label: t("hit"),
33072
- value: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hit, { totals: detail.totals }),
33073
- sub: `${formatCompact(detail.totals.cacheReadTokens)} ${t("cacheHit")}`
33074
- }),
33075
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33076
- tone: SessionUsageView_module_default.toneOrange,
33077
- name: "speed",
33078
- label: t("speed"),
33079
- value: speed(detail.tokensPerSecond),
33080
- sub: `${t("first")} ${seconds(detail.averageFirstTokenMs)}`
33081
- })
33082
- ]
33083
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33084
- className: SessionUsageView_module_default.statRow,
33085
- children: [
33086
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33087
- tone: SessionUsageView_module_default.toneTeal,
33088
- name: "input",
33089
- label: t("inputFull"),
33090
- value: formatCompact(detail.totals.uncachedInputTokens),
33091
- title: formatTokens(detail.totals.uncachedInputTokens),
33092
- sub: formatTokens(detail.totals.uncachedInputTokens)
33093
- }),
33094
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33095
- tone: SessionUsageView_module_default.toneOrange,
33096
- name: "output",
33097
- label: t("outputFull"),
33098
- value: formatCompact(detail.totals.outputTokens),
33099
- title: formatTokens(detail.totals.outputTokens),
33100
- sub: formatTokens(detail.totals.outputTokens)
33101
- }),
33102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33103
- tone: SessionUsageView_module_default.tonePurple,
33104
- name: "read",
33105
- label: t("readFull"),
33106
- value: formatCompact(detail.totals.cacheReadTokens),
33107
- title: formatTokens(detail.totals.cacheReadTokens),
33108
- sub: formatTokens(detail.totals.cacheReadTokens)
33109
- }),
33110
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33111
- tone: SessionUsageView_module_default.tonePink,
33112
- name: "write",
33113
- label: t("writeFull"),
33114
- value: formatCompact(detail.totals.cacheWriteTokens),
33115
- title: formatTokens(detail.totals.cacheWriteTokens),
33116
- sub: formatTokens(detail.totals.cacheWriteTokens)
33117
- })
33118
- ]
33119
- })]
33044
+ children: [
33045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33046
+ tone: SessionUsageView_module_default.toneBlue,
33047
+ name: "requests",
33048
+ label: t("requestsCount"),
33049
+ value: formatTokens(detail.requests),
33050
+ sub: `${t("failed")} ${failed} · ${failRate}%`
33051
+ }),
33052
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33053
+ tone: SessionUsageView_module_default.tonePurple,
33054
+ name: "tokens",
33055
+ label: t("tokens"),
33056
+ value: formatCompact(billed),
33057
+ title: formatTokens(billed),
33058
+ sub: `${formatTokens(billed)} tokens`
33059
+ }),
33060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33061
+ tone: SessionUsageView_module_default.toneTeal,
33062
+ name: "input",
33063
+ label: t("inputFull"),
33064
+ value: formatCompact(detail.totals.uncachedInputTokens),
33065
+ title: formatTokens(detail.totals.uncachedInputTokens),
33066
+ sub: formatTokens(detail.totals.uncachedInputTokens)
33067
+ }),
33068
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33069
+ tone: SessionUsageView_module_default.toneOrange,
33070
+ name: "output",
33071
+ label: t("outputFull"),
33072
+ value: formatCompact(detail.totals.outputTokens),
33073
+ title: formatTokens(detail.totals.outputTokens),
33074
+ sub: formatTokens(detail.totals.outputTokens)
33075
+ }),
33076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33077
+ tone: SessionUsageView_module_default.toneGreen,
33078
+ name: "hit",
33079
+ label: t("hit"),
33080
+ value: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hit, { totals: detail.totals }),
33081
+ sub: `${formatCompact(detail.totals.cacheReadTokens)} ${t("cacheHit")}`
33082
+ }),
33083
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33084
+ tone: SessionUsageView_module_default.toneOrange,
33085
+ name: "speed",
33086
+ label: t("speed"),
33087
+ value: speed(detail.tokensPerSecond),
33088
+ sub: `${t("first")} ${seconds(detail.averageFirstTokenMs)}`
33089
+ })
33090
+ ]
33120
33091
  });
33121
33092
  }
33122
33093
  function Tables({ detail, t }) {
@@ -33290,6 +33261,8 @@ window.__ModuleLoader__.load({
33290
33261
  }
33291
33262
  group.rows.push(record);
33292
33263
  }
33264
+ const latest = (group) => group.rows.reduce((max, row) => Math.max(max, row.startedAt), 0);
33265
+ groups.sort((a, b) => latest(b) - latest(a));
33293
33266
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33294
33267
  className: SessionUsageView_module_default.card,
33295
33268
  children: [
@@ -33316,8 +33289,8 @@ window.__ModuleLoader__.load({
33316
33289
  const totals = sum(group.rows);
33317
33290
  const billed = billedTotal(totals);
33318
33291
  const running = group.rows.filter((row) => row.status === "running").length;
33319
- const rates = group.rows.map((row) => row.tokensPerSecond).filter((n) => n !== null);
33320
- const avgRate = rates.length ? Math.round(rates.reduce((a, b) => a + b, 0) / rates.length) : null;
33292
+ const rate = decodeRateOf(group.rows);
33293
+ const avgRate = rate === null ? null : Math.round(rate);
33321
33294
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
33322
33295
  className: SessionUsageView_module_default.turn,
33323
33296
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -33388,7 +33361,7 @@ window.__ModuleLoader__.load({
33388
33361
  id: `usage-turn-${encodeURIComponent(group.key)}`,
33389
33362
  className: SessionUsageView_module_default.turnBody,
33390
33363
  hidden: !open,
33391
- children: group.rows.map((record) => {
33364
+ children: group.rows.slice().reverse().map((record) => {
33392
33365
  const duration = record.finishedAt !== null ? record.finishedAt - record.startedAt : null;
33393
33366
  const billed = billedTotal(record.totals);
33394
33367
  const hit = cacheHitPercentOf(record.totals);
@@ -33431,14 +33404,14 @@ window.__ModuleLoader__.load({
33431
33404
  modelShort(record.model) || "—"
33432
33405
  ]
33433
33406
  }),
33434
- statusText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33435
- className: record.status === "running" ? SessionUsageView_module_default.live : record.status === "failed" ? SessionUsageView_module_default.bad : SessionUsageView_module_default.muted,
33436
- children: statusText
33437
- }),
33438
33407
  record.effort ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33439
33408
  className: SessionUsageView_module_default.effort,
33440
33409
  children: ["· ", record.effort]
33441
- }) : null
33410
+ }) : null,
33411
+ statusText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33412
+ className: record.status === "running" ? SessionUsageView_module_default.live : record.status === "failed" ? SessionUsageView_module_default.bad : SessionUsageView_module_default.muted,
33413
+ children: statusText
33414
+ })
33442
33415
  ]
33443
33416
  }),
33444
33417
  record.finishedAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -33488,13 +33461,51 @@ window.__ModuleLoader__.load({
33488
33461
  ] });
33489
33462
  }
33490
33463
  function SessionUsageView({ sessionId, t }) {
33491
- const { detail, loading, error, retry } = useSessionDetail(sessionId);
33464
+ const { detail, loading, error, refresh, updatedAt } = useSessionDetail(sessionId);
33492
33465
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33493
33466
  className: SessionUsageView_module_default.scroll,
33494
33467
  "data-dsh-session-usage": true,
33495
33468
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33496
33469
  className: SessionUsageView_module_default.page,
33497
33470
  children: [
33471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
33472
+ className: SessionUsageView_module_default.head,
33473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33474
+ className: SessionUsageView_module_default.headStatus,
33475
+ children: [
33476
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33477
+ className: SessionUsageView_module_default.liveDot,
33478
+ "aria-hidden": "true"
33479
+ }),
33480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("live") }),
33481
+ updatedAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
33482
+ className: SessionUsageView_module_default.asOf,
33483
+ children: [
33484
+ t("asOf"),
33485
+ " ",
33486
+ clock(updatedAt)
33487
+ ]
33488
+ })
33489
+ ]
33490
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
33491
+ type: "button",
33492
+ className: SessionUsageView_module_default.refresh,
33493
+ onClick: refresh,
33494
+ disabled: loading,
33495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
33496
+ width: "12",
33497
+ height: "12",
33498
+ viewBox: "0 0 24 24",
33499
+ fill: "none",
33500
+ stroke: "currentColor",
33501
+ strokeWidth: "2",
33502
+ strokeLinecap: "round",
33503
+ strokeLinejoin: "round",
33504
+ "aria-hidden": "true",
33505
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M21 3v5h-5" })]
33506
+ }), t("refresh")]
33507
+ })]
33508
+ }),
33498
33509
  loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33499
33510
  className: SessionUsageView_module_default.empty,
33500
33511
  role: "status",
@@ -33506,7 +33517,7 @@ window.__ModuleLoader__.load({
33506
33517
  children: [t("failedLoad"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
33507
33518
  type: "button",
33508
33519
  className: SessionUsageView_module_default.action,
33509
- onClick: retry,
33520
+ onClick: refresh,
33510
33521
  children: t("retry")
33511
33522
  })]
33512
33523
  }),
@@ -33577,7 +33588,10 @@ window.__ModuleLoader__.load({
33577
33588
  cacheShort: "缓存",
33578
33589
  avgCache: "平均缓存",
33579
33590
  avgRate: "平均速率",
33580
- inProgress: "个进行中"
33591
+ inProgress: "个进行中",
33592
+ live: "实时更新",
33593
+ asOf: "统计截至",
33594
+ refresh: "刷新"
33581
33595
  };
33582
33596
  const usageEn = {
33583
33597
  tab: "Usage",
@@ -33634,7 +33648,10 @@ window.__ModuleLoader__.load({
33634
33648
  cacheShort: "cache",
33635
33649
  avgCache: "avg cache",
33636
33650
  avgRate: "avg rate",
33637
- inProgress: "in progress"
33651
+ inProgress: "in progress",
33652
+ live: "Live",
33653
+ asOf: "as of",
33654
+ refresh: "Refresh"
33638
33655
  };
33639
33656
  const usageZhTW = {
33640
33657
  ...usageZh,
@@ -33691,7 +33708,10 @@ window.__ModuleLoader__.load({
33691
33708
  cacheShort: "快取",
33692
33709
  avgCache: "平均快取",
33693
33710
  avgRate: "平均速率",
33694
- inProgress: "個進行中"
33711
+ inProgress: "個進行中",
33712
+ live: "即時更新",
33713
+ asOf: "統計截至",
33714
+ refresh: "重新整理"
33695
33715
  };
33696
33716
  //#endregion
33697
33717
  //#region src/client/index.tsx