dsh-token-total 1.0.7 → 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
@@ -32876,7 +32876,7 @@ window.__ModuleLoader__.load({
32876
32876
  }
32877
32877
  //#endregion
32878
32878
  //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/SessionUsageView.module.css?inline
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 * 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/* 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; 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,.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 .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";
32880
32880
  const tagId = "dsh-token-total/SessionUsageView.module.css";
32881
32881
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
32882
32882
  const tag = document.createElement("style");
@@ -32895,11 +32895,9 @@ window.__ModuleLoader__.load({
32895
32895
  "refresh": "refresh_900471699",
32896
32896
  "scope": "scope_900471699",
32897
32897
  "stats": "stats_900471699",
32898
- "statRow": "statRow_900471699",
32899
32898
  "stat": "stat_900471699",
32900
32899
  "statHead": "statHead_900471699",
32901
32900
  "statIcon": "statIcon_900471699",
32902
- "statMark": "statMark_900471699",
32903
32901
  "statLabel": "statLabel_900471699",
32904
32902
  "statValue": "statValue_900471699",
32905
32903
  "statSub": "statSub_900471699",
@@ -32908,7 +32906,6 @@ window.__ModuleLoader__.load({
32908
32906
  "toneGreen": "toneGreen_900471699",
32909
32907
  "toneOrange": "toneOrange_900471699",
32910
32908
  "toneTeal": "toneTeal_900471699",
32911
- "tonePink": "tonePink_900471699",
32912
32909
  "card": "card_900471699",
32913
32910
  "cardHead": "cardHead_900471699",
32914
32911
  "count": "count_900471699",
@@ -32998,50 +32995,20 @@ window.__ModuleLoader__.load({
32998
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",
32999
32996
  speed: "M13 2 4 14h7l-1 8 9-12h-7l1-8Z",
33000
32997
  input: "M12 3v12M7 10l5 5 5-5M4 21h16",
33001
- output: "M12 21V9M7 14l5-5 5 5M4 3h16",
33002
- 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",
33003
- 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"
33004
32999
  };
33005
33000
  function Stat({ tone, name, label, value, sub, title }) {
33006
33001
  const icon = icons[name];
33007
33002
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
33008
33003
  className: `${SessionUsageView_module_default.stat} ${tone}`,
33009
33004
  children: [
33010
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
33011
- className: SessionUsageView_module_default.statMark,
33012
- viewBox: "0 0 24 24",
33013
- "aria-hidden": "true",
33014
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
33015
- id: `usage-mark-${name}`,
33016
- x1: "0",
33017
- y1: "0",
33018
- x2: "1",
33019
- y2: "1",
33020
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
33021
- offset: "0",
33022
- stopColor: "currentColor",
33023
- stopOpacity: "0.3"
33024
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
33025
- offset: "1",
33026
- stopColor: "currentColor",
33027
- stopOpacity: "0.03"
33028
- })]
33029
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
33030
- d: icon,
33031
- fill: `url(#usage-mark-${name})`,
33032
- stroke: `url(#usage-mark-${name})`,
33033
- strokeWidth: "1.1",
33034
- strokeLinecap: "round",
33035
- strokeLinejoin: "round"
33036
- })]
33037
- }),
33038
33005
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33039
33006
  className: SessionUsageView_module_default.statHead,
33040
33007
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33041
33008
  className: SessionUsageView_module_default.statIcon,
33042
33009
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
33043
- width: "18",
33044
- height: "18",
33010
+ width: "12",
33011
+ height: "12",
33045
33012
  viewBox: "0 0 24 24",
33046
33013
  fill: "none",
33047
33014
  stroke: "currentColor",
@@ -33074,76 +33041,53 @@ window.__ModuleLoader__.load({
33074
33041
  const failRate = detail.requests > 0 ? (failed / detail.requests * 100).toFixed(1) : "0.0";
33075
33042
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33076
33043
  className: SessionUsageView_module_default.stats,
33077
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33078
- className: SessionUsageView_module_default.statRow,
33079
- children: [
33080
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33081
- tone: SessionUsageView_module_default.toneBlue,
33082
- name: "requests",
33083
- label: t("requestsCount"),
33084
- value: formatTokens(detail.requests),
33085
- sub: `${t("failed")} ${failed} · ${failRate}%`
33086
- }),
33087
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33088
- tone: SessionUsageView_module_default.tonePurple,
33089
- name: "tokens",
33090
- label: t("tokens"),
33091
- value: formatCompact(billed),
33092
- title: formatTokens(billed),
33093
- sub: `${formatTokens(billed)} tokens`
33094
- }),
33095
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33096
- tone: SessionUsageView_module_default.toneGreen,
33097
- name: "hit",
33098
- label: t("hit"),
33099
- value: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hit, { totals: detail.totals }),
33100
- sub: `${formatCompact(detail.totals.cacheReadTokens)} ${t("cacheHit")}`
33101
- }),
33102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33103
- tone: SessionUsageView_module_default.toneOrange,
33104
- name: "speed",
33105
- label: t("speed"),
33106
- value: speed(detail.tokensPerSecond),
33107
- sub: `${t("first")} ${seconds(detail.averageFirstTokenMs)}`
33108
- })
33109
- ]
33110
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33111
- className: SessionUsageView_module_default.statRow,
33112
- children: [
33113
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33114
- tone: SessionUsageView_module_default.toneTeal,
33115
- name: "input",
33116
- label: t("inputFull"),
33117
- value: formatCompact(detail.totals.uncachedInputTokens),
33118
- title: formatTokens(detail.totals.uncachedInputTokens),
33119
- sub: formatTokens(detail.totals.uncachedInputTokens)
33120
- }),
33121
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33122
- tone: SessionUsageView_module_default.toneOrange,
33123
- name: "output",
33124
- label: t("outputFull"),
33125
- value: formatCompact(detail.totals.outputTokens),
33126
- title: formatTokens(detail.totals.outputTokens),
33127
- sub: formatTokens(detail.totals.outputTokens)
33128
- }),
33129
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33130
- tone: SessionUsageView_module_default.tonePurple,
33131
- name: "read",
33132
- label: t("readFull"),
33133
- value: formatCompact(detail.totals.cacheReadTokens),
33134
- title: formatTokens(detail.totals.cacheReadTokens),
33135
- sub: formatTokens(detail.totals.cacheReadTokens)
33136
- }),
33137
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stat, {
33138
- tone: SessionUsageView_module_default.tonePink,
33139
- name: "write",
33140
- label: t("writeFull"),
33141
- value: formatCompact(detail.totals.cacheWriteTokens),
33142
- title: formatTokens(detail.totals.cacheWriteTokens),
33143
- sub: formatTokens(detail.totals.cacheWriteTokens)
33144
- })
33145
- ]
33146
- })]
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
+ ]
33147
33091
  });
33148
33092
  }
33149
33093
  function Tables({ detail, t }) {
@@ -33317,6 +33261,8 @@ window.__ModuleLoader__.load({
33317
33261
  }
33318
33262
  group.rows.push(record);
33319
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));
33320
33266
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33321
33267
  className: SessionUsageView_module_default.card,
33322
33268
  children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-token-total",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "DSH web plugin: token 用量统计看板 + 单会话(主智能体 + 子智能体)用量统计。提供今日/近3天/近7天/近1月/近3月/自定义筛选、六个汇总卡片、活跃热力图、缓存命中率折线与分模型柱状图、按模型/按供应商 Top5 列表。",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -1,8 +1,10 @@
1
1
  /* A light, self-contained Usage surface. Tables fit the available width:
2
2
  * no horizontal scroll containers, fixed column contracts, titles for names.
3
- * Stat cards follow the reference dashboard: tinted left edge + icon chip,
4
- * label, big value, small subtitle. Request log folds by conversation turn. */
5
- .scroll { height:100%; min-height:0; overflow-y:auto; background:#ffffff; color:#252936; color-scheme:light; container-type:inline-size; }
3
+ * Six stat cards sit in one row (≤1050px: 3-up, ≤700px: 2-up) as quiet white
4
+ * surfaces on a pale canvas: no accent edges or watermarks, a flat tone chip
5
+ * leading each label, hierarchy from the type scale and a hairline above the
6
+ * footnote. Request log folds by conversation turn. */
7
+ .scroll { height:100%; min-height:0; overflow-y:auto; background:#f6f7f9; color:#252936; color-scheme:light; container-type:inline-size; }
6
8
  .page { 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; }
7
9
  .page * { box-sizing:border-box; }
8
10
  /* Freshness header: live badge + last-read stamp on the left, manual refresh
@@ -15,21 +17,19 @@
15
17
  .refresh:hover { color:#279cb6; border-color:#cbd3e0; }
16
18
  .refresh:disabled { opacity:.55; cursor:default; }
17
19
  .scope { color:#8790a3; font-size:11px; margin:0 0 14px; }
18
- .stats { display:grid; gap:12px; margin-bottom:18px; }
19
- .statRow { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px; }
20
- .stat { 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; }
21
- .statHead { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
22
- .statIcon { 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); }
23
- .statMark { position:absolute; right:-14px; bottom:-18px; width:92px; height:92px; color:var(--tone); pointer-events:none; }
24
- .statLabel { font-size:12px; color:#7d8495; }
25
- .statValue { display:block; font-size:25px; font-weight:650; letter-spacing:-0.5px; white-space:nowrap; font-variant-numeric:tabular-nums; }
26
- .statSub { display:block; margin-top:5px; font-size:11px; color:#939aac; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
20
+ .stats { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:8px; margin-bottom:16px; }
21
+ .stat { 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; }
22
+ .stat:hover { border-color:#d9dfe9; box-shadow:0 14px 30px -22px rgba(23,32,54,.32); transform:translateY(-1px); }
23
+ .statHead { display:flex; align-items:center; gap:5px; margin-bottom:6px; }
24
+ .statIcon { 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; }
25
+ .statLabel { font-size:12px; font-weight:500; color:#7b8494; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
26
+ .statValue { display:block; font-size:21px; font-weight:650; letter-spacing:-0.4px; color:#1b2130; white-space:nowrap; font-variant-numeric:tabular-nums; }
27
+ .statSub { 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; }
27
28
  .toneBlue { --tone:#4f79f6; --tone-soft:#eaf0fe; }
28
29
  .tonePurple { --tone:#8b5cf6; --tone-soft:#f1ebfe; }
29
30
  .toneGreen { --tone:#18a97b; --tone-soft:#e5f6f0; }
30
31
  .toneOrange { --tone:#e8892c; --tone-soft:#fcf1e4; }
31
32
  .toneTeal { --tone:#16a4b8; --tone-soft:#e4f5f8; }
32
- .tonePink { --tone:#d9538c; --tone-soft:#fbeaf1; }
33
33
  .card { min-width:0; background:#fff; border:1px solid #e4e7ee; border-radius:12px; margin-bottom:18px; overflow:hidden; }
34
34
  .cardHead { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:54px; padding:15px 18px; border-bottom:1px solid #eef0f4; }
35
35
  .cardHead h2 { font-size:14px; font-weight:600; margin:0; }
@@ -102,15 +102,15 @@
102
102
  .error { 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; }
103
103
  @container (max-width:1050px) {
104
104
  .mid { grid-template-columns:minmax(0,1fr); }
105
- .statRow { grid-template-columns:repeat(2,minmax(0,1fr)); }
106
- .statValue { font-size:23px; }
105
+ .stats { grid-template-columns:repeat(3,minmax(0,1fr)); }
107
106
  .table { font-size:12px; }
108
107
  .childName { width:34%; }
109
108
  }
110
109
  @container (max-width:700px) {
111
110
  .page { padding:18px 14px 24px; }
112
- .stat { padding:12px 12px 11px; }
113
- .statValue { font-size:20px; }
111
+ .stats { grid-template-columns:repeat(2,minmax(0,1fr)); }
112
+ .stat { padding:10px 12px 9px; }
113
+ .statValue { font-size:18px; }
114
114
  .table { font-size:10px; }
115
115
  .table th,.table td { padding:10px 3px; }
116
116
  .table th:first-child,.table td:first-child { padding-left:10px; }
@@ -128,4 +128,6 @@
128
128
  }
129
129
  @media (prefers-reduced-motion:reduce) {
130
130
  .chevron { transition:none; }
131
+ .stat { transition:none; }
132
+ .stat:hover { transform:none; }
131
133
  }
@@ -45,19 +45,15 @@ const icons = {
45
45
  speed: 'M13 2 4 14h7l-1 8 9-12h-7l1-8Z',
46
46
  input: 'M12 3v12M7 10l5 5 5-5M4 21h16',
47
47
  output: 'M12 21V9M7 14l5-5 5 5M4 3h16',
48
- 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',
49
- write: 'M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5Z',
50
48
  }
51
49
 
52
50
  function Stat({ tone, name, label, value, sub, title }: { tone: string; name: keyof typeof icons; label: string; value: string | JSX.Element; sub?: string; title?: string }): JSX.Element {
53
51
  const icon = icons[name]
54
52
  return <article className={`${css.stat} ${tone}`}>
55
- {/* Oversized gradient watermark filling the card's empty right side. */}
56
- <svg className={css.statMark} viewBox="0 0 24 24" aria-hidden="true">
57
- <defs><linearGradient id={`usage-mark-${name}`} x1="0" y1="0" x2="1" y2="1"><stop offset="0" stopColor="currentColor" stopOpacity="0.3" /><stop offset="1" stopColor="currentColor" stopOpacity="0.03" /></linearGradient></defs>
58
- <path d={icon} fill={`url(#usage-mark-${name})`} stroke={`url(#usage-mark-${name})`} strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
59
- </svg>
60
- <div className={css.statHead}><span className={css.statIcon}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={icon} /></svg></span><span className={css.statLabel}>{label}</span></div>
53
+ <div className={css.statHead}>
54
+ <span className={css.statIcon}><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={icon} /></svg></span>
55
+ <span className={css.statLabel}>{label}</span>
56
+ </div>
61
57
  <strong className={css.statValue} title={title}>{value}</strong>
62
58
  {sub !== undefined && <span className={css.statSub}>{sub}</span>}
63
59
  </article>
@@ -68,18 +64,12 @@ function Metrics({ detail, t }: { detail: SessionUsageDetail; t: T }): JSX.Eleme
68
64
  const failed = detail.records.filter(row => row.status === 'failed').length
69
65
  const failRate = detail.requests > 0 ? (failed / detail.requests * 100).toFixed(1) : '0.0'
70
66
  return <div className={css.stats}>
71
- <div className={css.statRow}>
72
- <Stat tone={css.toneBlue} name="requests" label={t('requestsCount')} value={formatTokens(detail.requests)} sub={`${t('failed')} ${failed} · ${failRate}%`} />
73
- <Stat tone={css.tonePurple} name="tokens" label={t('tokens')} value={formatCompact(billed)} title={formatTokens(billed)} sub={`${formatTokens(billed)} tokens`} />
74
- <Stat tone={css.toneGreen} name="hit" label={t('hit')} value={<Hit totals={detail.totals} />} sub={`${formatCompact(detail.totals.cacheReadTokens)} ${t('cacheHit')}`} />
75
- <Stat tone={css.toneOrange} name="speed" label={t('speed')} value={speed(detail.tokensPerSecond)} sub={`${t('first')} ${seconds(detail.averageFirstTokenMs)}`} />
76
- </div>
77
- <div className={css.statRow}>
78
- <Stat tone={css.toneTeal} name="input" label={t('inputFull')} value={formatCompact(detail.totals.uncachedInputTokens)} title={formatTokens(detail.totals.uncachedInputTokens)} sub={formatTokens(detail.totals.uncachedInputTokens)} />
79
- <Stat tone={css.toneOrange} name="output" label={t('outputFull')} value={formatCompact(detail.totals.outputTokens)} title={formatTokens(detail.totals.outputTokens)} sub={formatTokens(detail.totals.outputTokens)} />
80
- <Stat tone={css.tonePurple} name="read" label={t('readFull')} value={formatCompact(detail.totals.cacheReadTokens)} title={formatTokens(detail.totals.cacheReadTokens)} sub={formatTokens(detail.totals.cacheReadTokens)} />
81
- <Stat tone={css.tonePink} name="write" label={t('writeFull')} value={formatCompact(detail.totals.cacheWriteTokens)} title={formatTokens(detail.totals.cacheWriteTokens)} sub={formatTokens(detail.totals.cacheWriteTokens)} />
82
- </div>
67
+ <Stat tone={css.toneBlue} name="requests" label={t('requestsCount')} value={formatTokens(detail.requests)} sub={`${t('failed')} ${failed} · ${failRate}%`} />
68
+ <Stat tone={css.tonePurple} name="tokens" label={t('tokens')} value={formatCompact(billed)} title={formatTokens(billed)} sub={`${formatTokens(billed)} tokens`} />
69
+ <Stat tone={css.toneTeal} name="input" label={t('inputFull')} value={formatCompact(detail.totals.uncachedInputTokens)} title={formatTokens(detail.totals.uncachedInputTokens)} sub={formatTokens(detail.totals.uncachedInputTokens)} />
70
+ <Stat tone={css.toneOrange} name="output" label={t('outputFull')} value={formatCompact(detail.totals.outputTokens)} title={formatTokens(detail.totals.outputTokens)} sub={formatTokens(detail.totals.outputTokens)} />
71
+ <Stat tone={css.toneGreen} name="hit" label={t('hit')} value={<Hit totals={detail.totals} />} sub={`${formatCompact(detail.totals.cacheReadTokens)} ${t('cacheHit')}`} />
72
+ <Stat tone={css.toneOrange} name="speed" label={t('speed')} value={speed(detail.tokensPerSecond)} sub={`${t('first')} ${seconds(detail.averageFirstTokenMs)}`} />
83
73
  </div>
84
74
  }
85
75
 
@@ -150,6 +140,11 @@ function Records({ detail, t }: { detail: SessionUsageDetail; t: T }): JSX.Eleme
150
140
  }
151
141
  group.rows.push(record)
152
142
  }
143
+ // Newest turn on top: groups order by the time of their most recent request,
144
+ // not by first appearance, so the live work leads and finished history trails
145
+ // (records arrive root-session first, subagents after, regardless of time).
146
+ const latest = (group: { rows: Row[] }): number => group.rows.reduce((max, row) => Math.max(max, row.startedAt), 0)
147
+ groups.sort((a, b) => latest(b) - latest(a))
153
148
  return <section className={css.card}><header className={css.cardHead}><h2>{t('records')} <span className={css.count}>{detail.records.length}</span></h2><button type="button" className={css.action} onClick={toggleAll}>{t(allOpen ? 'collapse' : 'expand')}</button></header>
154
149
  <div className={css.turnList}>{groups.map(group => {
155
150
  const open = allOpen !== opened.has(group.key)
@@ -175,8 +170,8 @@ function Records({ detail, t }: { detail: SessionUsageDetail; t: T }): JSX.Eleme
175
170
  </span>
176
171
  </button>
177
172
  <div id={`usage-turn-${encodeURIComponent(group.key)}`} className={css.turnBody} hidden={!open}>
178
- {/* Requests arrive in event order, so every turn lists its newest
179
- request first; turn groups themselves keep chronological order. */}
173
+ {/* Newest first inside and out: every turn lists its newest request
174
+ first, and turn groups themselves lead with the latest turn. */}
180
175
  {group.rows.slice().reverse().map(record => {
181
176
  const duration = record.finishedAt !== null ? record.finishedAt - record.startedAt : null
182
177
  const billed = billedTotal(record.totals)