dsh-context 0.56.2 → 0.58.0

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.
Files changed (3) hide show
  1. package/lib/client.js +1024 -708
  2. package/lib/index.js +47 -7
  3. package/package.json +2 -2
package/lib/client.js CHANGED
@@ -354,8 +354,10 @@ window.__ModuleLoader__.load({
354
354
  "ov.kpi.ofTotal": "共 {n} 个",
355
355
  "ov.kpi.tokens": "累计消耗",
356
356
  "ov.kpi.sessionsSub": "含 {n} 个会话",
357
+ "ov.kpi.pricedSub": "已计价 {n}/{total} 个会话",
357
358
  "ov.kpi.toolSub": "工具执行 {dur}",
358
359
  "ov.kpi.wallSub": "模型调用 {n}",
360
+ "ov.stats.empty": "统计范围内暂无 Token 用量",
359
361
  "balance.title": "DeepSeek 余额",
360
362
  "balance.tip.granted": "赠送余额",
361
363
  "balance.tip.toppedUp": "充值余额",
@@ -733,8 +735,10 @@ window.__ModuleLoader__.load({
733
735
  "ov.kpi.ofTotal": "of {n} total",
734
736
  "ov.kpi.tokens": "Tokens Used",
735
737
  "ov.kpi.sessionsSub": "across {n} sessions",
738
+ "ov.kpi.pricedSub": "{n} of {total} sessions priced",
736
739
  "ov.kpi.toolSub": "tool runs {dur}",
737
740
  "ov.kpi.wallSub": "{n} model calls",
741
+ "ov.stats.empty": "No billed tokens in this range",
738
742
  "balance.title": "DeepSeek balance",
739
743
  "balance.tip.granted": "Granted balance",
740
744
  "balance.tip.toppedUp": "Topped-up balance",
@@ -2904,6 +2908,7 @@ window.__ModuleLoader__.load({
2904
2908
  const BAR_W = 14;
2905
2909
  const BAR_GAP = 2;
2906
2910
  const STAGGER_CAP = 20;
2911
+ const STEP_FLAG_EVERY = 5;
2907
2912
  const TURN_FILLS = ["color-mix(in srgb, var(--color-neutral-500) 12%, transparent)", "color-mix(in srgb, var(--color-neutral-500) 26%, transparent)"];
2908
2913
  const LABEL_OVERHANG = 48;
2909
2914
  const LABEL_GAP = 2;
@@ -2960,62 +2965,73 @@ window.__ModuleLoader__.load({
2960
2965
  onMouseEnter: () => {
2961
2966
  props.onHover(req.seq);
2962
2967
  },
2963
- children: [marker !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2964
- className: "lc-bar-marker",
2965
- title: "✂ " + (markerAt !== null ? markerAt + " — " : "") + eventLabel(marker),
2966
- children: "✂"
2967
- }) : null, diverge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2968
- className: "lc-bar-up animate-lc-bar-in motion-reduce:animate-none",
2969
- style: {
2970
- bottom: `${props.downPx}px`,
2971
- ...enterStyle
2972
- },
2973
- children: CATS.map((c) => {
2974
- const d = req[c.key] || 0;
2975
- if (d <= 0) return null;
2976
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2977
- "data-cat": c.key,
2978
- className: "lc-cat-seg",
2979
- style: {
2980
- height: `${Math.max(1, Math.round(d * props.deltaScale))}px`,
2981
- background: c.color
2982
- }
2983
- }, c.key);
2984
- })
2985
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2986
- className: "lc-bar-down animate-lc-bar-in motion-reduce:animate-none",
2987
- style: {
2988
- top: `${props.upPx}px`,
2989
- ...enterStyle
2990
- },
2991
- children: CATS.map((c) => {
2992
- const d = req[c.key] || 0;
2993
- if (d >= 0) return null;
2994
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2995
- "data-cat": c.key,
2996
- className: "lc-cat-seg",
2997
- style: {
2998
- height: `${Math.max(1, Math.round(-d * props.deltaScale))}px`,
2999
- background: c.color
3000
- }
3001
- }, c.key);
3002
- })
3003
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3004
- className: "lc-bar-stack animate-lc-bar-in motion-reduce:animate-none",
3005
- style: enterStyle,
3006
- children: CATS.map((c) => {
3007
- const v = req[c.key] || 0;
3008
- if (!v) return null;
3009
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3010
- "data-cat": c.key,
3011
- className: "lc-cat-seg",
3012
- style: {
3013
- height: `${Math.max(1, Math.round(v / props.maxTotal * CHART_H))}px`,
3014
- background: c.color
3015
- }
3016
- }, c.key);
2968
+ children: [
2969
+ props.flag !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2970
+ className: "lc-step-flag",
2971
+ "aria-hidden": "true",
2972
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2973
+ className: "lc-step-flag-label",
2974
+ children: props.flag
2975
+ })
2976
+ }) : null,
2977
+ marker !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2978
+ className: "lc-bar-marker",
2979
+ title: "✂ " + (markerAt !== null ? markerAt + " — " : "") + eventLabel(marker),
2980
+ children: "✂"
2981
+ }) : null,
2982
+ diverge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2983
+ className: "lc-bar-up animate-lc-bar-in motion-reduce:animate-none",
2984
+ style: {
2985
+ bottom: `${props.downPx}px`,
2986
+ ...enterStyle
2987
+ },
2988
+ children: CATS.map((c) => {
2989
+ const d = req[c.key] || 0;
2990
+ if (d <= 0) return null;
2991
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2992
+ "data-cat": c.key,
2993
+ className: "lc-cat-seg",
2994
+ style: {
2995
+ height: `${Math.max(1, Math.round(d * props.deltaScale))}px`,
2996
+ background: c.color
2997
+ }
2998
+ }, c.key);
2999
+ })
3000
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3001
+ className: "lc-bar-down animate-lc-bar-in motion-reduce:animate-none",
3002
+ style: {
3003
+ top: `${props.upPx}px`,
3004
+ ...enterStyle
3005
+ },
3006
+ children: CATS.map((c) => {
3007
+ const d = req[c.key] || 0;
3008
+ if (d >= 0) return null;
3009
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3010
+ "data-cat": c.key,
3011
+ className: "lc-cat-seg",
3012
+ style: {
3013
+ height: `${Math.max(1, Math.round(-d * props.deltaScale))}px`,
3014
+ background: c.color
3015
+ }
3016
+ }, c.key);
3017
+ })
3018
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3019
+ className: "lc-bar-stack animate-lc-bar-in motion-reduce:animate-none",
3020
+ style: enterStyle,
3021
+ children: CATS.map((c) => {
3022
+ const v = req[c.key] || 0;
3023
+ if (!v) return null;
3024
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3025
+ "data-cat": c.key,
3026
+ className: "lc-cat-seg",
3027
+ style: {
3028
+ height: `${Math.max(1, Math.round(v / props.maxTotal * CHART_H))}px`,
3029
+ background: c.color
3030
+ }
3031
+ }, c.key);
3032
+ })
3017
3033
  })
3018
- })]
3034
+ ]
3019
3035
  });
3020
3036
  });
3021
3037
  return function TrendChart(props) {
@@ -3369,6 +3385,7 @@ window.__ModuleLoader__.load({
3369
3385
  selected: props.selectedSeq === req.seq,
3370
3386
  hovered: props.hoveredSeq === req.seq,
3371
3387
  inTurn: props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn,
3388
+ flag: props.granularity === "step" && (i + 1) % STEP_FLAG_EVERY === 0 ? i + 1 : null,
3372
3389
  maxTotal,
3373
3390
  upPx: delta ? upPx : void 0,
3374
3391
  downPx: delta ? downPx : void 0,
@@ -5487,11 +5504,14 @@ window.__ModuleLoader__.load({
5487
5504
  * The provider-id seam between dsh request envelopes and the models.dev
5488
5505
  * registry: client/cost.ts resolves price-book branches through it, and
5489
5506
  * host/fold.ts uses the DeepSeek resolution to split the session-cost totals
5490
- * into peak/off-peak periods. Only the renames live here — an id absent
5491
- * from the table passes through verbatim.
5507
+ * into peak/off-peak periods. Only the renames live here — a dsh route key
5508
+ * that is not a registry id (user-declared gateways, `claude`/`codex`
5509
+ * subscriptions, org-prefixed catalogs) passes through and prices through
5510
+ * the model-side resolution index instead (client/cost.ts).
5492
5511
  */
5493
5512
  const MODELS_DEV_PROVIDER_IDS = {
5494
5513
  "deepseek-official": "deepseek",
5514
+ "deepseek-account": "deepseek",
5495
5515
  "kimi-coding": "moonshotai",
5496
5516
  "minimax-cn": "minimax",
5497
5517
  "zai-coding-cn": "zhipuai"
@@ -5514,6 +5534,120 @@ window.__ModuleLoader__.load({
5514
5534
  function toCurrency(usd, currency) {
5515
5535
  return currency === "cny" ? usd / USD_PER_CNY : usd;
5516
5536
  }
5537
+ /** Rate equality within the registry's float noise (sync drift prints 0.125000…003). */
5538
+ function sameRate(a, b) {
5539
+ const close = (x, y) => Math.abs(x - y) <= 1e-9 * Math.max(1, Math.abs(x));
5540
+ return close(a.hit, b.hit) && close(a.miss, b.miss) && close(a.write, b.write) && close(a.out, b.out);
5541
+ }
5542
+ /** One proven candidate rate off a built book branch, or null. */
5543
+ function rateOfEntry(value) {
5544
+ const v = value;
5545
+ if (v === null || typeof v !== "object") return null;
5546
+ const r = v;
5547
+ const num = (x) => typeof x === "number" && Number.isFinite(x);
5548
+ return num(r.hit) && num(r.miss) && num(r.write) && num(r.out) ? {
5549
+ hit: r.hit,
5550
+ miss: r.miss,
5551
+ write: r.write,
5552
+ out: r.out
5553
+ } : null;
5554
+ }
5555
+ /**
5556
+ * Build the resolution index over a proven book. `npmOf` carries each
5557
+ * provider's registry `npm` package (null when absent) — the vendor signal:
5558
+ * a provider whose package is `@ai-sdk/<its own id>` is the model's first
5559
+ * party, while mirrors and gateways ride `@ai-sdk/openai-compatible` or
5560
+ * someone else's package. Suffix tails are indexed alongside exact ids so a
5561
+ * short dsh spelling (`k3`) resolves book-wide too.
5562
+ */
5563
+ function priceIndexOf(prices, npmOf) {
5564
+ const byModel = /* @__PURE__ */ new Map();
5565
+ const push = (key, cand) => {
5566
+ const list = byModel.get(key);
5567
+ if (list === void 0) byModel.set(key, [cand]);
5568
+ else if (!list.some((c) => c.pid === cand.pid && sameRate(c.rate, cand.rate))) list.push(cand);
5569
+ };
5570
+ for (const pid of Object.keys(prices)) {
5571
+ const branch = branchOf(prices, pid);
5572
+ if (branch === null) continue;
5573
+ const npm = npmOf[pid];
5574
+ const primary = typeof npm === "string" && npm.startsWith("@ai-sdk/") && npm.slice(8) === pid;
5575
+ for (const mid of Object.keys(branch)) {
5576
+ const rate = rateOfEntry(branch[mid]);
5577
+ if (rate === null) continue;
5578
+ const lower = mid.toLowerCase();
5579
+ const cand = {
5580
+ pid,
5581
+ rate,
5582
+ primary
5583
+ };
5584
+ push(lower, cand);
5585
+ const parts = lower.split("-");
5586
+ for (let i = 1; i < parts.length; i++) push(parts.slice(i).join("-"), cand);
5587
+ }
5588
+ }
5589
+ return { byModel };
5590
+ }
5591
+ /**
5592
+ * The rate shared by a candidate group, or null: the largest equal-rate
5593
+ * group wins, a tie refuses. Agreeing mirrors are noise; disagreeing ones
5594
+ * price nothing rather than guess.
5595
+ */
5596
+ function majorityRate(cands) {
5597
+ const groups = [];
5598
+ for (const c of cands) {
5599
+ const g = groups.find((g) => sameRate(g.rate, c.rate));
5600
+ if (g === void 0) groups.push({
5601
+ rate: c.rate,
5602
+ n: 1
5603
+ });
5604
+ else g.n++;
5605
+ }
5606
+ groups.sort((a, b) => b.n - a.n);
5607
+ return groups.length > 1 && groups[0].n === groups[1].n ? null : groups[0].rate;
5608
+ }
5609
+ /**
5610
+ * Pick one candidate group's rate: the model's own vendor (unique or
5611
+ * unanimous), then a provider the model id itself names (`deepseek-v4-flash`
5612
+ * under `deepseek`), then a lone carrier. Everything else prices null.
5613
+ */
5614
+ function resolveCandidates(lower, cands, org) {
5615
+ if (org !== null) {
5616
+ const named = cands.filter((c) => c.pid === org || org.startsWith(c.pid + "-") || org.length >= 4 && org.startsWith(c.pid));
5617
+ if (named.length > 0) {
5618
+ const rate = majorityRate(named);
5619
+ if (rate !== null) return rate;
5620
+ }
5621
+ }
5622
+ const primaries = cands.filter((c) => c.primary);
5623
+ if (primaries.length > 0) {
5624
+ const rate = majorityRate(primaries);
5625
+ if (rate !== null) return rate;
5626
+ }
5627
+ const prefixed = cands.filter((c) => lower.startsWith(c.pid + "-") || lower.startsWith(c.pid + "/"));
5628
+ if (prefixed.length > 0) {
5629
+ const rate = majorityRate(prefixed);
5630
+ if (rate !== null) return rate;
5631
+ }
5632
+ return cands.length === 1 ? cands[0].rate : null;
5633
+ }
5634
+ /**
5635
+ * Resolve a model id book-wide: the full id first, then its last `/`-segment
5636
+ * (an org-prefixed dialect whose exact spelling no registry provider lists).
5637
+ * Null when every tier refuses.
5638
+ */
5639
+ function resolveRate(index, model) {
5640
+ const lower = model.toLowerCase();
5641
+ const slash = lower.lastIndexOf("/");
5642
+ const org = slash > 0 ? lower.slice(0, slash) : null;
5643
+ for (const key of slash > 0 ? [lower, lower.slice(slash + 1)] : [lower]) {
5644
+ const cands = index.byModel.get(key);
5645
+ if (cands === void 0) continue;
5646
+ const rate = resolveCandidates(key, cands, org);
5647
+ if (rate !== null) return rate;
5648
+ }
5649
+ return null;
5650
+ }
5517
5651
  /** One rate triple at the doubled peak rate (the tooltip's `peak | off` pair). */
5518
5652
  function peakOf(rate) {
5519
5653
  return {
@@ -5553,22 +5687,16 @@ window.__ModuleLoader__.load({
5553
5687
  * The book's rates for one folded (provider, model) bucket, or null when
5554
5688
  * the book cannot price it: the dsh provider id resolves through
5555
5689
  * modelsDevProviderOf (unmapped ids pass through) and prices by model id —
5556
- * exact, case-insensitive, or suffix; a provider the book does not carry
5557
- * falls back to a cross-provider scan, priced only when exactly one branch
5558
- * carries the model id.
5559
- */
5560
- function priceOf(prices, provider, model) {
5561
- if (prices === null || prices === void 0) return null;
5562
- const direct = branchOf(prices, modelsDevProviderOf(provider));
5690
+ * exact, case-insensitive, or suffix. A provider the book does not carry
5691
+ * falls back to the model-side resolution index, which names the vendor
5692
+ * from data (org segment, own-vendor SDK, id prefix, lone carrier) — never
5693
+ * from a per-model hardcode.
5694
+ */
5695
+ function priceOf(book, provider, model) {
5696
+ if (book === null || book === void 0) return null;
5697
+ const direct = branchOf(book.prices, modelsDevProviderOf(provider));
5563
5698
  if (direct !== null) return lookup(direct, model);
5564
- let found = null;
5565
- for (const models of Object.values(prices)) {
5566
- const rate = lookup(models, model);
5567
- if (rate === null) continue;
5568
- if (found !== null) return null;
5569
- found = rate;
5570
- }
5571
- return found;
5699
+ return resolveRate(book.index, model);
5572
5700
  }
5573
5701
  /**
5574
5702
  * Price the session's cumulative billed-token totals. Cache reads bill at
@@ -5579,8 +5707,8 @@ window.__ModuleLoader__.load({
5579
5707
  * Null when nothing was priced (no usage folded, no book yet, or no model
5580
5708
  * the book prices), so the cell can show a dash.
5581
5709
  */
5582
- function estimateSessionCost(usage, prices, currency) {
5583
- if (usage === null || usage === void 0 || prices === null || prices === void 0) return null;
5710
+ function estimateSessionCost(usage, book, currency) {
5711
+ if (usage === null || usage === void 0 || book === null || book === void 0) return null;
5584
5712
  let total = 0;
5585
5713
  let any = false;
5586
5714
  for (const provider of Object.keys(usage)) {
@@ -5588,7 +5716,7 @@ window.__ModuleLoader__.load({
5588
5716
  if (models === null) continue;
5589
5717
  const deepseek = isDeepSeekProvider(provider);
5590
5718
  for (const model of Object.keys(models)) {
5591
- const rate = priceOf(prices, provider, model);
5719
+ const rate = priceOf(book, provider, model);
5592
5720
  const periods = asRecord(models[model]);
5593
5721
  if (rate === null || periods === null) continue;
5594
5722
  for (const period of ["peak", "off"]) {
@@ -5660,7 +5788,7 @@ window.__ModuleLoader__.load({
5660
5788
  return formatCost(amount, currency).replace(/0+$/, "").replace(/\.$/, "");
5661
5789
  }
5662
5790
  //#endregion
5663
- //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/error.js
5791
+ //#region node_modules/.pnpm/@opencode-ai+models@0.0.87/node_modules/@opencode-ai/models/dist/error.js
5664
5792
  /**
5665
5793
  * The only error thrown by the models.dev client.
5666
5794
  *
@@ -5677,7 +5805,7 @@ window.__ModuleLoader__.load({
5677
5805
  }
5678
5806
  };
5679
5807
  //#endregion
5680
- //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/client.js
5808
+ //#region node_modules/.pnpm/@opencode-ai+models@0.0.87/node_modules/@opencode-ai/models/dist/client.js
5681
5809
  /**
5682
5810
  * Creates a stateless models.dev client. Every method performs exactly one
5683
5811
  * `GET` and nothing is ever cached — callers who want caching should wrap
@@ -5694,7 +5822,11 @@ window.__ModuleLoader__.load({
5694
5822
  for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value);
5695
5823
  let response;
5696
5824
  try {
5697
- response = await fetch(new URL(path, base), {
5825
+ const url = new URL(path, base);
5826
+ const modelTypes = requestOptions?.modelTypes;
5827
+ if (modelTypes === "all") url.searchParams.set("type", "all");
5828
+ else if (modelTypes && modelTypes.length > 0) url.searchParams.set("type", modelTypes.join(","));
5829
+ response = await fetch(url, {
5698
5830
  method: "GET",
5699
5831
  headers,
5700
5832
  signal: requestOptions?.signal
@@ -5739,10 +5871,12 @@ window.__ModuleLoader__.load({
5739
5871
  * re-proves every field at the boundary — a non-conforming provider/model/
5740
5872
  * cost entry drops whole. The book keeps EVERY provider that prices (keyed
5741
5873
  * by the registry's own provider id), so a dsh provider id outside the
5742
- * rename table still prices by direct passthrough. One fetch per page load,
5743
- * kicked on first subscribe; a failure degrades to a visible state the cost
5744
- * cell notes, with a backed-off automatic retry — never a spinner, and
5745
- * never an unhandled rejection.
5874
+ * rename table still prices by direct passthrough, and carries each
5875
+ * provider's `npm` package into the model-side resolution index
5876
+ * (cost.ts `priceIndexOf`) so unknown provider ids price by model id alone.
5877
+ * One fetch per page load, kicked on first subscribe; a failure degrades to
5878
+ * a visible state the cost cell notes, with a backed-off automatic retry —
5879
+ * never a spinner, and never an unhandled rejection.
5746
5880
  */
5747
5881
  /** One finite non-negative registry figure, or null. */
5748
5882
  function rateOf(value) {
@@ -5750,18 +5884,23 @@ window.__ModuleLoader__.load({
5750
5884
  }
5751
5885
  /**
5752
5886
  * Extract the whole registry's per-model USD rates from a delivered
5753
- * providers payload (`/api.json`), keyed by the registry's provider ids.
5754
- * Any shape failure skips just that entry; a payload that is not a record
5755
- * at all returns null (the store treats it as a failed fetch and retries).
5887
+ * providers payload (`/api.json`), keyed by the registry's provider ids,
5888
+ * with the resolution index built over it. Any shape failure skips just
5889
+ * that entry; a payload that is not a record at all returns null (the store
5890
+ * treats it as a failed fetch and retries).
5756
5891
  */
5757
5892
  function pricesBookOf(value) {
5758
5893
  const data = asRecord(value);
5759
5894
  if (data === null || Array.isArray(data)) return null;
5760
5895
  const book = {};
5896
+ const npmOf = {};
5761
5897
  for (const providerId of Object.keys(data)) {
5762
5898
  const provider = asRecord(data[providerId]);
5763
- const modelList = provider !== null ? asRecord(provider.models) : null;
5899
+ if (provider === null) continue;
5900
+ const modelList = asRecord(provider.models);
5764
5901
  if (modelList === null) continue;
5902
+ const npm = provider.npm;
5903
+ npmOf[providerId] = typeof npm === "string" ? npm : null;
5765
5904
  const models = {};
5766
5905
  for (const id of Object.keys(modelList)) {
5767
5906
  const model = asRecord(modelList[id]);
@@ -5779,14 +5918,17 @@ window.__ModuleLoader__.load({
5779
5918
  }
5780
5919
  if (Object.keys(models).length > 0) book[providerId] = models;
5781
5920
  }
5782
- return book;
5921
+ return {
5922
+ prices: book,
5923
+ index: priceIndexOf(book, npmOf)
5924
+ };
5783
5925
  }
5784
5926
  const defaultLoader = () => make().providers();
5785
5927
  /** The retry backoff base; each consecutive failure doubles the wait, capped at 3 doublings. */
5786
5928
  const RETRY_BASE_MS = 3e4;
5787
5929
  let loader = defaultLoader;
5788
5930
  let snap = {
5789
- prices: null,
5931
+ book: null,
5790
5932
  failed: false
5791
5933
  };
5792
5934
  let inFlight = false;
@@ -5810,7 +5952,7 @@ window.__ModuleLoader__.load({
5810
5952
  const book = pricesBookOf(await loader());
5811
5953
  if (book !== null) {
5812
5954
  snap = {
5813
- prices: book,
5955
+ book,
5814
5956
  failed: false
5815
5957
  };
5816
5958
  failures = 0;
@@ -5822,7 +5964,7 @@ window.__ModuleLoader__.load({
5822
5964
  for (const fn of listeners) fn();
5823
5965
  }
5824
5966
  function kick() {
5825
- if (snap.prices !== null || inFlight || timer !== null) return;
5967
+ if (snap.book !== null || inFlight || timer !== null) return;
5826
5968
  fire();
5827
5969
  }
5828
5970
  /** The useSyncExternalStore seam: subscribing also kicks the first fetch. */
@@ -6206,7 +6348,106 @@ window.__ModuleLoader__.load({
6206
6348
  totals.total = totals.input + totals.cacheRead + totals.cacheWrite + totals.output;
6207
6349
  return totals;
6208
6350
  }
6209
- function kpisOf(rows, listed, prices, currency) {
6351
+ /**
6352
+ * The range's summed timing totals — the aggregate Timing Stats card's
6353
+ * source, folded from the rows' host-folded whole-session totals exactly as
6354
+ * the KPI band folds the scalar figures. Null when no row carries timing.
6355
+ * The additive-optional fields (the decode split, the throughput seat) sum
6356
+ * their carriers only: a row cached before a field existed contributes
6357
+ * nothing to it, and a field no row carries stays absent — the card keeps
6358
+ * its un-split shape / no-chip fallback instead of materialized zeros.
6359
+ */
6360
+ function timingSumOf(rows) {
6361
+ let out = null;
6362
+ for (const row of rows) {
6363
+ const t = row.timeline?.timing;
6364
+ if (t === void 0) continue;
6365
+ if (out === null) {
6366
+ out = {
6367
+ ...t,
6368
+ tools: { ...t.tools }
6369
+ };
6370
+ continue;
6371
+ }
6372
+ out.wallMs += t.wallMs;
6373
+ out.ttftMs += t.ttftMs;
6374
+ out.genMs += t.genMs;
6375
+ out.calls += t.calls;
6376
+ out.toolsMs += t.toolsMs;
6377
+ out.toolCalls += t.toolCalls;
6378
+ addOptionalOf(out, t, "reasoningMs");
6379
+ addOptionalOf(out, t, "reasoningBlocks");
6380
+ addOptionalOf(out, t, "textMs");
6381
+ addOptionalOf(out, t, "textBlocks");
6382
+ addOptionalOf(out, t, "toolArgMs");
6383
+ addOptionalOf(out, t, "toolArgBlocks");
6384
+ addOptionalOf(out, t, "speedTokens");
6385
+ addOptionalOf(out, t, "speedMs");
6386
+ for (const name of Object.keys(t.tools)) {
6387
+ const tool = t.tools[name];
6388
+ const prev = out.tools[name];
6389
+ out.tools[name] = prev === void 0 ? { ...tool } : {
6390
+ calls: prev.calls + tool.calls,
6391
+ ms: prev.ms + tool.ms
6392
+ };
6393
+ }
6394
+ }
6395
+ return out;
6396
+ }
6397
+ /** One additive-optional timing field's in-place merge: absent on both sides stays absent (never a materialized undefined). */
6398
+ function addOptionalOf(target, source, key) {
6399
+ const a = target[key];
6400
+ const b = source[key];
6401
+ if (a !== void 0 || b !== void 0) target[key] = (a ?? 0) + (b ?? 0);
6402
+ }
6403
+ /**
6404
+ * The range's billed tokens split by WHAT they are — the Context tab's Token
6405
+ * card categorization, folded across sessions: each session's
6406
+ * `billedParts` estimate (the composition ratios proportioning that
6407
+ * session's provider-reported prompt total, output exact) sums by category,
6408
+ * and every session's parts total its own billed figure, so the aggregate's
6409
+ * total stays the exact merged billed volume while the split inherits the
6410
+ * per-session card's `≈` estimate convention. Null when no session billed
6411
+ * anything. A non-finite part estimate (a hostile fast-path composition)
6412
+ * drops whole instead of poisoning the sums.
6413
+ */
6414
+ function tokenPartsOf(rows) {
6415
+ const sums = /* @__PURE__ */ new Map();
6416
+ let total = 0;
6417
+ let any = false;
6418
+ for (const row of rows) {
6419
+ const timeline = row.timeline;
6420
+ if (timeline === null) continue;
6421
+ const totals = usageTotalsOf(timeline.cost);
6422
+ if (totals === null) continue;
6423
+ any = true;
6424
+ const usage = {
6425
+ uncachedInputTokens: totals.input,
6426
+ cacheReadTokens: totals.cacheRead,
6427
+ cacheWriteTokens: totals.cacheWrite,
6428
+ outputTokens: totals.output
6429
+ };
6430
+ for (const part of billedParts(timeline.current, null, usage)) {
6431
+ if (!Number.isFinite(part.value) || part.value <= 0) continue;
6432
+ total += part.value;
6433
+ const prev = sums.get(part.key);
6434
+ sums.set(part.key, prev === void 0 ? {
6435
+ key: part.key,
6436
+ color: part.color,
6437
+ value: part.value
6438
+ } : {
6439
+ ...prev,
6440
+ value: prev.value + part.value
6441
+ });
6442
+ }
6443
+ }
6444
+ if (!any) return null;
6445
+ return {
6446
+ parts: [...sums.values()],
6447
+ total
6448
+ };
6449
+ }
6450
+ function kpisOf(rows, listed, book, currency) {
6210
6451
  const usage = mergeCostUsage(...rows.map((row) => row.timeline?.cost));
6211
6452
  const totals = usageTotalsOf(usage);
6212
6453
  let turns = 0;
@@ -6218,7 +6459,7 @@ window.__ModuleLoader__.load({
6218
6459
  let usageSessions = 0;
6219
6460
  for (const row of rows) {
6220
6461
  const cost = row.timeline?.cost;
6221
- if (estimateSessionCost(cost, prices, currency) !== null) costSessions++;
6462
+ if (estimateSessionCost(cost, book, currency) !== null) costSessions++;
6222
6463
  if (usageTotalsOf(cost) !== null) usageSessions++;
6223
6464
  turns += turnsOf(row.timeline);
6224
6465
  const timing = row.timeline?.timing;
@@ -6232,14 +6473,16 @@ window.__ModuleLoader__.load({
6232
6473
  listed,
6233
6474
  tokens: totals?.total ?? 0,
6234
6475
  turns,
6235
- cost: estimateSessionCost(usage, prices, currency),
6476
+ cost: estimateSessionCost(usage, book, currency),
6236
6477
  costSessions,
6237
6478
  cacheHit: totals === null ? null : cacheHitPercent(totals.cacheRead, totals.input + totals.cacheRead + totals.cacheWrite),
6238
6479
  usageSessions,
6239
6480
  toolCalls,
6240
6481
  toolsMs,
6241
6482
  calls,
6242
- wallMs
6483
+ wallMs,
6484
+ tokenParts: tokenPartsOf(rows),
6485
+ timing: timingSumOf(rows)
6243
6486
  };
6244
6487
  }
6245
6488
  /**
@@ -6939,141 +7182,439 @@ window.__ModuleLoader__.load({
6939
7182
  };
6940
7183
  }
6941
7184
  //#endregion
6942
- //#region src/client/components/overviewCard.tsx
6943
- function makeOverviewCard(kit) {
6944
- const { t } = kit;
6945
- const Donut = makeDonut(kit);
6946
- return function OverviewCard(props) {
6947
- const { row } = props;
6948
- const timeline = row.timeline;
6949
- const billed = billedOf(timeline);
6950
- const occupancy = timeline !== null && typeof timeline.contextWindow === "number" && timeline.contextWindow > 0 ? fmtShare(timeline.current.total, timeline.contextWindow) : null;
6951
- const turns = turnsOf(timeline);
6952
- const steps = timeline?.counts?.steps;
6953
- const turnsLabel = steps === void 0 ? String(turns) : t("ov.card.turns", {
6954
- n: turns,
6955
- s: steps
6956
- });
6957
- const project = projectOf(row.cwd);
6958
- const group = props.group !== void 0 && props.group !== project ? props.group : void 0;
6959
- const created = createdDayOf(row.activity);
6960
- const lastUser = timeline?.lastUser;
6961
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6962
- type: "button",
6963
- className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
6964
- onClick: () => {
6965
- props.onOpen(row.id);
7185
+ //#region src/client/components/sliceList.tsx
7186
+ /**
7187
+ * The stats cards' slice rows: the donut's legend with numbers, two tiers
7188
+ * per slice — the primary line pairs the color dot and the name with the
7189
+ * bold share closing a fixed right column, and the secondary line drops the
7190
+ * absolute quantity (plus a qualifier like the call count) under the name in
7191
+ * muted small print. Hovering a row lights it and its donut segment together
7192
+ * (the shared hover key), and vice versa. No tracks or bars — the donut IS
7193
+ * the proportion chart; the rows are its legend. Preformatted strings in,
7194
+ * dumb markup out.
7195
+ */
7196
+ function makeSliceList(kit) {
7197
+ return function SliceList(props) {
7198
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7199
+ className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
7200
+ onMouseLeave: () => {
7201
+ if (props.onHoverKey !== void 0) props.onHoverKey(null);
6966
7202
  },
6967
- children: [
6968
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6969
- className: "lc-ov-session-head",
7203
+ children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7204
+ className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
7205
+ onMouseEnter: () => {
7206
+ if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
7207
+ },
7208
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7209
+ className: "lc-sl-main",
6970
7210
  children: [
6971
- row.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6972
- className: "lc-ov-running",
6973
- title: t("ov.running")
7211
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
7212
+ className: "lc-sl-dot",
7213
+ style: { background: r.color }
6974
7214
  }),
6975
7215
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6976
- className: "lc-ov-session-title",
6977
- title: row.title,
6978
- children: row.title
6979
- }),
6980
- row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6981
- className: "lc-ov-current",
6982
- children: t("ov.current")
6983
- }),
6984
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6985
- className: "lc-ov-session-times",
6986
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6987
- className: "lc-ov-session-time",
6988
- children: relativeTime(t, row.updatedAt, props.now)
6989
- }), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6990
- className: "lc-ov-session-created",
6991
- children: created
6992
- })]
6993
- })
6994
- ]
6995
- }),
6996
- (group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6997
- className: "lc-ov-session-crumb",
6998
- title: row.cwd,
6999
- children: [
7000
- group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7001
- className: "lc-ov-crumb-group",
7002
- children: group
7003
- }),
7004
- group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7005
- className: "lc-ov-crumb-sep",
7006
- children: "/"
7216
+ className: "lc-sl-label",
7217
+ title: r.label,
7218
+ children: r.label
7007
7219
  }),
7008
- project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7009
- className: "lc-ov-crumb-project",
7010
- children: project
7220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7221
+ className: "lc-sl-pct",
7222
+ children: r.pct
7011
7223
  })
7012
7224
  ]
7013
- }),
7014
- timeline === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7015
- className: "lc-ov-session-empty",
7016
- children: t("ov.list.noData")
7017
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7018
- className: "lc-ov-session-body",
7019
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7020
- size: 64,
7021
- segments: partsOf(timeline.current),
7022
- centerTop: fmt(timeline.current.total),
7023
- centerSub: occupancy ?? void 0
7024
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7025
- className: "lc-ov-mini-stats",
7026
- children: [
7027
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7028
- className: "lc-ov-mini-stat",
7029
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7030
- className: "lc-ov-mini-label",
7031
- children: t("stats.turns")
7032
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7033
- className: "lc-ov-mini-value",
7034
- children: turnsLabel
7035
- })]
7036
- }),
7037
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7038
- className: "lc-ov-mini-stat",
7039
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7040
- className: "lc-ov-mini-label",
7041
- children: t("tokens.total")
7042
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7043
- className: "lc-ov-mini-value",
7044
- children: billed === null ? "—" : fmt(billed)
7045
- })]
7046
- }),
7047
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7048
- className: "lc-ov-mini-stat",
7049
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7050
- className: "lc-ov-mini-label",
7051
- children: t("stats.cost")
7052
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7053
- className: "lc-ov-mini-value",
7054
- children: props.costLabel
7055
- })]
7056
- })
7057
- ]
7058
- })]
7059
- }),
7060
- lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7061
- className: "lc-ov-session-preview",
7062
- title: lastUser,
7063
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7064
- className: "lc-ov-session-preview-label",
7065
- children: t("ov.card.lastUser")
7066
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7067
- className: "lc-ov-session-preview-text",
7068
- children: lastUser
7069
- })]
7070
- })
7071
- ]
7072
- });
7073
- };
7074
- }
7075
- //#endregion
7076
- //#region src/client/components/overviewPanel.tsx
7225
+ }), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7226
+ className: "lc-sl-sub",
7227
+ title: r.count,
7228
+ children: r.count
7229
+ }) : null]
7230
+ }, r.key))
7231
+ });
7232
+ };
7233
+ }
7234
+ //#endregion
7235
+ //#region src/client/components/overviewTokens.tsx
7236
+ /**
7237
+ * The Context Insights panel's aggregate Token Stats card — the first row's
7238
+ * token half, categorized exactly like the Context tab's Token card
7239
+ * (statsTokens.tsx): the range's billed volume split by WHAT the tokens are —
7240
+ * the composition categories' `≈` estimates proportioning each session's
7241
+ * provider-reported prompt total, plus the exact output — folded by
7242
+ * tokenPartsOf. The center figure stays the exact merged billed total the
7243
+ * KPI band's Tokens Used cell shows.
7244
+ */
7245
+ function makeOverviewTokens(kit, Donut) {
7246
+ const { t, fmt, fmtShare, catLabel } = kit;
7247
+ const SliceList = makeSliceList(kit);
7248
+ return function OverviewTokens(props) {
7249
+ const [hoverKey, setHoverKey] = (0, react.useState)(null);
7250
+ const tokens = props.tokens;
7251
+ const total = tokens?.total ?? 0;
7252
+ const shown = tokens !== null && total > 0 ? tokens.parts.filter((p) => p.value > 0) : [];
7253
+ const rows = shown.map((p) => ({
7254
+ key: p.key,
7255
+ color: p.color,
7256
+ label: p.key === "output" ? t("tokens.output") : catLabel(p.key),
7257
+ pct: fmtShare(p.value, total),
7258
+ count: p.key === "output" ? `${fmt(p.value)} · ${t("tokens.outputNote")}` : "≈" + fmt(p.value)
7259
+ }));
7260
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7261
+ className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
7262
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7263
+ className: "lc-card-title",
7264
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7265
+ className: "lc-card-title-text",
7266
+ children: t("tokens.title")
7267
+ })
7268
+ }), shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7269
+ className: "lc-empty",
7270
+ children: t("ov.stats.empty")
7271
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7272
+ className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
7273
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7274
+ segments: shown.map((p) => ({
7275
+ key: p.key,
7276
+ color: p.color,
7277
+ value: p.value
7278
+ })),
7279
+ size: 96,
7280
+ centerTop: fmt(total),
7281
+ centerSub: t("tokens.total"),
7282
+ hoverKey,
7283
+ onHoverKey: setHoverKey
7284
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
7285
+ rows,
7286
+ hoverKey,
7287
+ onHoverKey: setHoverKey
7288
+ })]
7289
+ })]
7290
+ });
7291
+ };
7292
+ }
7293
+ //#endregion
7294
+ //#region src/client/components/statsTiming.tsx
7295
+ /**
7296
+ * The Timing card: where the session's ACTIVE time went. The donut splits
7297
+ * whole-step wall time into the model-call slices — TTFT (step start → first
7298
+ * token, the wait) and LLM generation (first token → assistant message) —
7299
+ * and the tool-execution slice, with the residue as overhead; a call whose
7300
+ * stream recorded no token delta (legacy or aborted) stays unattributed and
7301
+ * lands in the residue. When the host folded the generation split (see
7302
+ * TimingTotals), the generation slice expands into what was being decoded:
7303
+ * thinking, answer text, and tool-call arguments. The slice rows lead with
7304
+ * the true duration and qualify it with a tally on the secondary line — the
7305
+ * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
7306
+ * (zero to many per call, so a call count would be a false tally there). A
7307
+ * slice that never happened (zero time) is omitted entirely — the ring already skips
7308
+ * its arc, and a zero row would otherwise borrow the session's call count and
7309
+ * read as "no time, many calls". The donut and the rows sit side by side so
7310
+ * the head row stays half-height. Parallel tool calls each count, so the tools
7311
+ * figure can overlap — the ring clamps it into the post-model window while the
7312
+ * row numbers stay true.
7313
+ */
7314
+ const COLOR = {
7315
+ ttft: "var(--color-blue-500)",
7316
+ reasoning: "var(--color-violet-500)",
7317
+ text: "var(--color-pink-500)",
7318
+ toolarg: "var(--color-amber-500)",
7319
+ tools: "var(--color-teal-500)",
7320
+ other: "var(--color-slate-400)"
7321
+ };
7322
+ /**
7323
+ * The decode-throughput figure, formatted exactly as the harness's own
7324
+ * session-stats seat formats it: whole tokens from ten up, one decimal below.
7325
+ */
7326
+ function formatTokensPerSecond(tps) {
7327
+ const clamped = Math.max(0, tps);
7328
+ return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
7329
+ }
7330
+ function makeStatsTiming(kit, Donut) {
7331
+ const { t, fmt, fmtDuration, fmtShare } = kit;
7332
+ const SliceList = makeSliceList(kit);
7333
+ return function StatsTiming(props) {
7334
+ const [hoverKey, setHoverKey] = (0, react.useState)(null);
7335
+ const timing = props.timing;
7336
+ const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
7337
+ const tps = timing !== null && typeof timing.speedMs === "number" && Number.isFinite(timing.speedMs) && timing.speedMs > 0 && typeof timing.speedTokens === "number" && Number.isFinite(timing.speedTokens) && timing.speedTokens >= 0 ? formatTokensPerSecond(timing.speedTokens / (timing.speedMs / 1e3)) : null;
7338
+ let segments = [];
7339
+ let rows = [];
7340
+ if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
7341
+ const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
7342
+ const modelSlices = [{
7343
+ key: "ttft",
7344
+ color: COLOR.ttft,
7345
+ label: t("timing.ttft"),
7346
+ ms: timing.ttftMs,
7347
+ times: callTimes
7348
+ }];
7349
+ const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
7350
+ const buckets = [
7351
+ [
7352
+ "reasoning",
7353
+ COLOR.reasoning,
7354
+ t("timing.reasoning"),
7355
+ timing.reasoningMs ?? 0,
7356
+ blockTimes(timing.reasoningBlocks)
7357
+ ],
7358
+ [
7359
+ "text",
7360
+ COLOR.text,
7361
+ t("timing.text"),
7362
+ timing.textMs ?? 0,
7363
+ blockTimes(timing.textBlocks)
7364
+ ],
7365
+ [
7366
+ "toolarg",
7367
+ COLOR.toolarg,
7368
+ t("timing.toolArgs"),
7369
+ timing.toolArgMs ?? 0,
7370
+ blockTimes(timing.toolArgBlocks)
7371
+ ]
7372
+ ];
7373
+ if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
7374
+ key,
7375
+ color,
7376
+ label,
7377
+ ms,
7378
+ ...times !== void 0 ? { times } : {}
7379
+ });
7380
+ else modelSlices.push({
7381
+ key: "gen",
7382
+ color: COLOR.reasoning,
7383
+ label: t("timing.gen"),
7384
+ ms: timing.genMs
7385
+ });
7386
+ const ttftRing = Math.min(timing.ttftMs, wall);
7387
+ const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
7388
+ const modelRings = [ttftRing];
7389
+ let used = ttftRing;
7390
+ const genEnd = ttftRing + genRing;
7391
+ for (const slice of modelSlices.slice(1)) {
7392
+ const take = Math.max(0, Math.min(slice.ms, genEnd - used));
7393
+ modelRings.push(take);
7394
+ used += take;
7395
+ }
7396
+ const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
7397
+ const other = Math.max(0, wall - ttftRing - genRing - toolRing);
7398
+ const share = (ms) => wall > 0 ? ms / wall : 0;
7399
+ const countOf = (ms, times) => {
7400
+ const dur = fmtDuration(ms);
7401
+ if (times === void 0) return dur;
7402
+ return ms > 0 ? `${dur} · ${times}` : times;
7403
+ };
7404
+ const modelSegments = modelSlices.map((slice, index) => ({
7405
+ key: slice.key,
7406
+ color: slice.color,
7407
+ value: share(modelRings[index])
7408
+ }));
7409
+ const toolSlice = {
7410
+ key: "tools",
7411
+ color: COLOR.tools,
7412
+ label: t("timing.tools"),
7413
+ ms: timing.toolsMs,
7414
+ times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
7415
+ };
7416
+ const otherSlice = {
7417
+ key: "other",
7418
+ color: COLOR.other,
7419
+ label: t("timing.other"),
7420
+ ms: other
7421
+ };
7422
+ segments = [
7423
+ ...modelSegments,
7424
+ {
7425
+ key: "tools",
7426
+ color: COLOR.tools,
7427
+ value: share(toolRing)
7428
+ },
7429
+ {
7430
+ key: "other",
7431
+ color: COLOR.other,
7432
+ value: share(other)
7433
+ }
7434
+ ];
7435
+ const toRow = (slice) => ({
7436
+ key: slice.key,
7437
+ color: slice.color,
7438
+ label: slice.label,
7439
+ dim: slice.ms === 0,
7440
+ pct: fmtShare(slice.ms, wall),
7441
+ count: countOf(slice.ms, slice.times)
7442
+ });
7443
+ rows = [
7444
+ ...modelSlices.map(toRow),
7445
+ toRow(toolSlice),
7446
+ toRow(otherSlice)
7447
+ ].filter((row) => !row.dim);
7448
+ }
7449
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7450
+ className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
7451
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7452
+ className: "lc-card-title",
7453
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7454
+ className: "lc-card-title-text",
7455
+ children: t("timing.title")
7456
+ }), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7457
+ className: "lc-timing-tps",
7458
+ title: t("timing.tpsTip"),
7459
+ children: t("timing.tps", { tps })
7460
+ })]
7461
+ }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7462
+ className: "lc-empty",
7463
+ children: t("timing.empty")
7464
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7465
+ className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
7466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7467
+ segments,
7468
+ size: 96,
7469
+ centerTop: wall > 0 ? fmtDuration(wall) : "—",
7470
+ centerSub: t("timing.total"),
7471
+ hoverKey,
7472
+ onHoverKey: setHoverKey
7473
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
7474
+ rows,
7475
+ hoverKey,
7476
+ onHoverKey: setHoverKey
7477
+ })]
7478
+ })]
7479
+ });
7480
+ };
7481
+ }
7482
+ //#endregion
7483
+ //#region src/client/components/overviewCard.tsx
7484
+ function makeOverviewCard(kit) {
7485
+ const { t } = kit;
7486
+ const Donut = makeDonut(kit);
7487
+ return function OverviewCard(props) {
7488
+ const { row } = props;
7489
+ const timeline = row.timeline;
7490
+ const billed = billedOf(timeline);
7491
+ const occupancy = timeline !== null && typeof timeline.contextWindow === "number" && timeline.contextWindow > 0 ? fmtShare(timeline.current.total, timeline.contextWindow) : null;
7492
+ const turns = turnsOf(timeline);
7493
+ const steps = timeline?.counts?.steps;
7494
+ const turnsLabel = steps === void 0 ? String(turns) : t("ov.card.turns", {
7495
+ n: turns,
7496
+ s: steps
7497
+ });
7498
+ const project = projectOf(row.cwd);
7499
+ const group = props.group !== void 0 && props.group !== project ? props.group : void 0;
7500
+ const created = createdDayOf(row.activity);
7501
+ const lastUser = timeline?.lastUser;
7502
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7503
+ type: "button",
7504
+ className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
7505
+ onClick: () => {
7506
+ props.onOpen(row.id);
7507
+ },
7508
+ children: [
7509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7510
+ className: "lc-ov-session-head",
7511
+ children: [
7512
+ row.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7513
+ className: "lc-ov-running",
7514
+ title: t("ov.running")
7515
+ }),
7516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7517
+ className: "lc-ov-session-title",
7518
+ title: row.title,
7519
+ children: row.title
7520
+ }),
7521
+ row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7522
+ className: "lc-ov-current",
7523
+ children: t("ov.current")
7524
+ }),
7525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7526
+ className: "lc-ov-session-times",
7527
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7528
+ className: "lc-ov-session-time",
7529
+ children: relativeTime(t, row.updatedAt, props.now)
7530
+ }), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7531
+ className: "lc-ov-session-created",
7532
+ children: created
7533
+ })]
7534
+ })
7535
+ ]
7536
+ }),
7537
+ (group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7538
+ className: "lc-ov-session-crumb",
7539
+ title: row.cwd,
7540
+ children: [
7541
+ group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7542
+ className: "lc-ov-crumb-group",
7543
+ children: group
7544
+ }),
7545
+ group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7546
+ className: "lc-ov-crumb-sep",
7547
+ children: "/"
7548
+ }),
7549
+ project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7550
+ className: "lc-ov-crumb-project",
7551
+ children: project
7552
+ })
7553
+ ]
7554
+ }),
7555
+ timeline === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7556
+ className: "lc-ov-session-empty",
7557
+ children: t("ov.list.noData")
7558
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7559
+ className: "lc-ov-session-body",
7560
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7561
+ size: 64,
7562
+ segments: partsOf(timeline.current),
7563
+ centerTop: fmt(timeline.current.total),
7564
+ centerSub: occupancy ?? void 0
7565
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7566
+ className: "lc-ov-mini-stats",
7567
+ children: [
7568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7569
+ className: "lc-ov-mini-stat",
7570
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7571
+ className: "lc-ov-mini-label",
7572
+ children: t("stats.turns")
7573
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7574
+ className: "lc-ov-mini-value",
7575
+ children: turnsLabel
7576
+ })]
7577
+ }),
7578
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7579
+ className: "lc-ov-mini-stat",
7580
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7581
+ className: "lc-ov-mini-label",
7582
+ children: t("tokens.total")
7583
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7584
+ className: "lc-ov-mini-value",
7585
+ children: billed === null ? "—" : fmt(billed)
7586
+ })]
7587
+ }),
7588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7589
+ className: "lc-ov-mini-stat",
7590
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7591
+ className: "lc-ov-mini-label",
7592
+ children: t("stats.cost")
7593
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7594
+ className: "lc-ov-mini-value",
7595
+ children: props.costLabel
7596
+ })]
7597
+ })
7598
+ ]
7599
+ })]
7600
+ }),
7601
+ lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7602
+ className: "lc-ov-session-preview",
7603
+ title: lastUser,
7604
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7605
+ className: "lc-ov-session-preview-label",
7606
+ children: t("ov.card.lastUser")
7607
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7608
+ className: "lc-ov-session-preview-text",
7609
+ children: lastUser
7610
+ })]
7611
+ })
7612
+ ]
7613
+ });
7614
+ };
7615
+ }
7616
+ //#endregion
7617
+ //#region src/client/components/overviewPanel.tsx
7077
7618
  /**
7078
7619
  * The Context Dashboard panel — the cross-session insight surface opened
7079
7620
  * from the sidebar foot (overviewButton.tsx). Rendered from the frame-wide
@@ -7082,12 +7623,17 @@ window.__ModuleLoader__.load({
7082
7623
  * projection values), so the panel draws every session's insight without
7083
7624
  * opening one log.
7084
7625
  *
7085
- * The body is a 3:7 column pair: the insight column (the KPI 2×3 block over
7086
- * the activity heatmap, then the preferences entry row) beside the session
7087
- * column (search, group chips, and the card grid); the heatmap keeps its own
7088
- * fixed 8-week window and PINs the list to a picked day (the panel's
7089
- * drill-down gesture). A session card click jumps to that session through
7090
- * the harness's own selection verb (openSessionVia) and closes the panel.
7626
+ * The panel's first row is the KPI metrics band: the range's six figures
7627
+ * (sessions, billed tokens, cost, cache hit, tool calls, active time) in one
7628
+ * full-width line. Below it, the aggregate stats row folds the same range's
7629
+ * sessions into the two donut cards the per-session Context tab opens with —
7630
+ * Token Stats (the composition-split billed volume) and Timing Stats (the
7631
+ * summed totals). The body is then a 3:7 column pair: the insight column (the
7632
+ * activity heatmap, then the preferences entry row) beside the session column
7633
+ * (search, group chips, and the card grid); the heatmap keeps its own fixed
7634
+ * 8-week window and PINs the list to a picked day (the panel's drill-down
7635
+ * gesture). A session card click jumps to that session through the harness's
7636
+ * own selection verb (openSessionVia) and closes the panel.
7091
7637
  */
7092
7638
  const RANGES = [
7093
7639
  "24h",
@@ -7107,6 +7653,9 @@ window.__ModuleLoader__.load({
7107
7653
  const Heatmap = makeHeatmap(kit);
7108
7654
  const OverviewCard = makeOverviewCard(kit);
7109
7655
  const BalanceCapsule = makeBalanceCapsule(ctx, kit);
7656
+ const Donut = makeDonut(kit);
7657
+ const OverviewTokens = makeOverviewTokens(kit, Donut);
7658
+ const StatsTiming = makeStatsTiming(kit, Donut);
7110
7659
  const ErrorBoundary = makeErrorBoundary(t);
7111
7660
  /** The display currency follows the active locale (zh → CNY), read per render — the slot outlet re-renders on a locale switch. */
7112
7661
  function activeCurrency() {
@@ -7115,7 +7664,7 @@ window.__ModuleLoader__.load({
7115
7664
  }
7116
7665
  function OverviewBody(props) {
7117
7666
  const open = (0, react.useSyncExternalStore)(overviewStore.subscribe, overviewStore.getSnapshot);
7118
- const { prices } = useModelPrices();
7667
+ const { book } = useModelPrices();
7119
7668
  const snapshot = sessionsSnapshotOf(props);
7120
7669
  const wsSnapshot = workspacesSnapshotOf(props);
7121
7670
  const [range, setRange] = (0, react.useState)("30d");
@@ -7167,7 +7716,7 @@ window.__ModuleLoader__.load({
7167
7716
  }] : counts;
7168
7717
  const visible = sortRows(group === null ? scoped : scoped.filter((row) => inGroup(row, group, groups)), sort);
7169
7718
  const paged = pageOf(visible, page);
7170
- const kpi = kpisOf(ranged, allRows.length, prices, currency);
7719
+ const kpi = kpisOf(ranged, allRows.length, book, currency);
7171
7720
  const days = aggregateDays(allRows);
7172
7721
  const openOne = (id) => {
7173
7722
  openSessionVia(ctx, id);
@@ -7217,123 +7766,130 @@ window.__ModuleLoader__.load({
7217
7766
  }), rows === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7218
7767
  className: "lc-empty",
7219
7768
  children: t("ov.unavailable")
7220
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7221
- className: "lc-ov-body",
7222
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7223
- className: "lc-ov-left",
7769
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7771
+ className: "lc-ov-kpis",
7224
7772
  children: [
7225
7773
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7226
- className: "lc-ov-kpis",
7774
+ className: "lc-stat lc-ov-kpi",
7227
7775
  children: [
7228
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7229
- className: "lc-stat lc-ov-kpi",
7230
- children: [
7231
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7232
- className: "lc-stat-label",
7233
- children: t("ov.kpi.sessions")
7234
- }),
7235
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7236
- className: "lc-stat-value",
7237
- children: kpi.sessions
7238
- }),
7239
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7240
- className: "lc-stat-sub",
7241
- children: t("ov.kpi.ofTotal", { n: kpi.listed })
7242
- })
7243
- ]
7776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7777
+ className: "lc-stat-label",
7778
+ children: t("ov.kpi.sessions")
7244
7779
  }),
7245
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7246
- className: "lc-stat lc-ov-kpi",
7247
- children: [
7248
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7249
- className: "lc-stat-label",
7250
- children: t("ov.kpi.tokens")
7251
- }),
7252
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7253
- className: "lc-stat-value",
7254
- children: fmt(kpi.tokens)
7255
- }),
7256
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7257
- className: "lc-stat-sub",
7258
- children: [
7259
- t("stats.turns"),
7260
- " ",
7261
- fmt(kpi.turns)
7262
- ]
7263
- })
7264
- ]
7780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7781
+ className: "lc-stat-value",
7782
+ children: kpi.sessions
7265
7783
  }),
7266
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7267
- className: "lc-stat lc-ov-kpi",
7268
- children: [
7269
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7270
- className: "lc-stat-label",
7271
- children: t("stats.cost")
7272
- }),
7273
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7274
- className: "lc-stat-value",
7275
- children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7276
- }),
7277
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7278
- className: "lc-stat-sub",
7279
- children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
7280
- })
7281
- ]
7784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7785
+ className: "lc-stat-sub",
7786
+ children: t("ov.kpi.ofTotal", { n: kpi.listed })
7787
+ })
7788
+ ]
7789
+ }),
7790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7791
+ className: "lc-stat lc-ov-kpi",
7792
+ children: [
7793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7794
+ className: "lc-stat-label",
7795
+ children: t("ov.kpi.tokens")
7282
7796
  }),
7283
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7284
- className: "lc-stat lc-ov-kpi",
7285
- children: [
7286
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7287
- className: "lc-stat-label",
7288
- children: t("stats.cacheHit")
7289
- }),
7290
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7291
- className: "lc-stat-value",
7292
- children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7293
- }),
7294
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7295
- className: "lc-stat-sub",
7296
- children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7297
- })
7298
- ]
7797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7798
+ className: "lc-stat-value",
7799
+ children: fmt(kpi.tokens)
7299
7800
  }),
7300
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7301
- className: "lc-stat lc-ov-kpi",
7801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7802
+ className: "lc-stat-sub",
7302
7803
  children: [
7303
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7304
- className: "lc-stat-label",
7305
- children: t("stats.toolCalls")
7306
- }),
7307
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7308
- className: "lc-stat-value",
7309
- children: fmt(kpi.toolCalls)
7310
- }),
7311
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7312
- className: "lc-stat-sub",
7313
- children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7314
- })
7804
+ t("stats.turns"),
7805
+ " ",
7806
+ fmt(kpi.turns)
7315
7807
  ]
7808
+ })
7809
+ ]
7810
+ }),
7811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7812
+ className: "lc-stat lc-ov-kpi",
7813
+ children: [
7814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7815
+ className: "lc-stat-label",
7816
+ children: t("stats.cost")
7316
7817
  }),
7317
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7318
- className: "lc-stat lc-ov-kpi",
7319
- children: [
7320
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7321
- className: "lc-stat-label",
7322
- children: t("timing.total")
7323
- }),
7324
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7325
- className: "lc-stat-value",
7326
- children: fmtDuration(kpi.wallMs)
7327
- }),
7328
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7329
- className: "lc-stat-sub",
7330
- children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7331
- })
7332
- ]
7818
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7819
+ className: "lc-stat-value",
7820
+ children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7821
+ }),
7822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7823
+ className: "lc-stat-sub",
7824
+ children: t("ov.kpi.pricedSub", {
7825
+ n: kpi.costSessions,
7826
+ total: kpi.sessions
7827
+ })
7828
+ })
7829
+ ]
7830
+ }),
7831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7832
+ className: "lc-stat lc-ov-kpi",
7833
+ children: [
7834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7835
+ className: "lc-stat-label",
7836
+ children: t("stats.cacheHit")
7837
+ }),
7838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7839
+ className: "lc-stat-value",
7840
+ children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7841
+ }),
7842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7843
+ className: "lc-stat-sub",
7844
+ children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7845
+ })
7846
+ ]
7847
+ }),
7848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7849
+ className: "lc-stat lc-ov-kpi",
7850
+ children: [
7851
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7852
+ className: "lc-stat-label",
7853
+ children: t("stats.toolCalls")
7854
+ }),
7855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7856
+ className: "lc-stat-value",
7857
+ children: fmt(kpi.toolCalls)
7858
+ }),
7859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7860
+ className: "lc-stat-sub",
7861
+ children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7333
7862
  })
7334
7863
  ]
7335
7864
  }),
7336
7865
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7866
+ className: "lc-stat lc-ov-kpi",
7867
+ children: [
7868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7869
+ className: "lc-stat-label",
7870
+ children: t("timing.total")
7871
+ }),
7872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7873
+ className: "lc-stat-value",
7874
+ children: fmtDuration(kpi.wallMs)
7875
+ }),
7876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7877
+ className: "lc-stat-sub",
7878
+ children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7879
+ })
7880
+ ]
7881
+ })
7882
+ ]
7883
+ }),
7884
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7885
+ className: "lc-ov-stats",
7886
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewTokens, { tokens: kpi.tokenParts }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: kpi.timing })]
7887
+ }),
7888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7889
+ className: "lc-ov-body",
7890
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7891
+ className: "lc-ov-left",
7892
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7337
7893
  className: "lc-card lc-ov-heat-card",
7338
7894
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7339
7895
  className: "lc-card-title",
@@ -7366,8 +7922,7 @@ window.__ModuleLoader__.load({
7366
7922
  onSelect: setDay,
7367
7923
  today: todayKey()
7368
7924
  })]
7369
- }),
7370
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7925
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7371
7926
  type: "button",
7372
7927
  className: "lc-ov-settings",
7373
7928
  onClick: () => {
@@ -7381,131 +7936,131 @@ window.__ModuleLoader__.load({
7381
7936
  className: "lc-ov-settings-hint",
7382
7937
  children: t("plugin.settingsOpen")
7383
7938
  })]
7384
- })
7385
- ]
7386
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7387
- className: "lc-ov-right",
7388
- children: [
7389
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7390
- className: "lc-ov-list-head",
7391
- children: [
7392
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7393
- className: "lc-ov-list-title",
7394
- children: t("ov.list.title")
7395
- }),
7396
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7397
- className: "lc-ov-list-count",
7398
- children: visible.length
7399
- }),
7400
- day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7401
- type: "button",
7402
- className: "lc-ov-day-chip",
7403
- title: t("ov.list.dayClear"),
7404
- onClick: () => {
7405
- setDay(null);
7406
- },
7407
- children: [t("ov.list.dayFilter", { day }), " ×"]
7408
- }),
7409
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7410
- className: "lc-ov-search",
7411
- type: "search",
7412
- value: query,
7413
- placeholder: t("ov.list.search"),
7414
- "aria-label": t("ov.list.search"),
7415
- onChange: (ev) => {
7416
- setQuery(ev.target.value);
7417
- }
7418
- }),
7419
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7420
- className: "lc-gran",
7421
- role: "group",
7422
- "aria-label": t("ov.list.sortLabel"),
7423
- children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7939
+ })]
7940
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7941
+ className: "lc-ov-right",
7942
+ children: [
7943
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7944
+ className: "lc-ov-list-head",
7945
+ children: [
7946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7947
+ className: "lc-ov-list-title",
7948
+ children: t("ov.list.title")
7949
+ }),
7950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7951
+ className: "lc-ov-list-count",
7952
+ children: visible.length
7953
+ }),
7954
+ day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7424
7955
  type: "button",
7425
- className: "lc-gran-btn" + (sort === s ? " lc-gran-on" : ""),
7956
+ className: "lc-ov-day-chip",
7957
+ title: t("ov.list.dayClear"),
7426
7958
  onClick: () => {
7427
- setSort(s);
7959
+ setDay(null);
7428
7960
  },
7429
- children: t("ov.list.sort." + s)
7430
- }, s))
7431
- })
7432
- ]
7433
- }),
7434
- chips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7435
- className: "lc-ov-groups",
7436
- role: "group",
7437
- "aria-label": t("ov.group.label"),
7438
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7439
- type: "button",
7440
- className: "lc-ov-chip" + (group === null ? " lc-ov-chip-on" : ""),
7441
- onClick: () => {
7442
- setGroup(null);
7443
- },
7444
- children: [t("ov.range.all"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7445
- className: "lc-ov-chip-n",
7446
- children: scoped.length
7447
- })]
7448
- }), chips.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7449
- type: "button",
7450
- className: "lc-ov-chip" + (group === c.key ? " lc-ov-chip-on" : ""),
7451
- onClick: () => {
7452
- setGroup(group === c.key ? null : c.key);
7453
- },
7454
- children: [c.key === "__ungrouped__" ? t("ov.group.ungrouped") : c.key, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7455
- className: "lc-ov-chip-n",
7456
- children: c.count
7457
- })]
7458
- }, c.key))]
7459
- }),
7460
- visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7461
- className: "lc-empty",
7462
- children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
7463
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7464
- className: "lc-ov-grid",
7465
- children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
7466
- row,
7467
- ...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
7468
- costLabel: cardCostOf(row, prices, currency),
7469
- now,
7470
- onOpen: openOne
7471
- }, row.id))
7472
- }), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7473
- className: "lc-ov-pager",
7474
- role: "navigation",
7475
- "aria-label": t("ov.list.pager"),
7476
- children: [
7477
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7961
+ children: [t("ov.list.dayFilter", { day }), " ×"]
7962
+ }),
7963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7964
+ className: "lc-ov-search",
7965
+ type: "search",
7966
+ value: query,
7967
+ placeholder: t("ov.list.search"),
7968
+ "aria-label": t("ov.list.search"),
7969
+ onChange: (ev) => {
7970
+ setQuery(ev.target.value);
7971
+ }
7972
+ }),
7973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7974
+ className: "lc-gran",
7975
+ role: "group",
7976
+ "aria-label": t("ov.list.sortLabel"),
7977
+ children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7978
+ type: "button",
7979
+ className: "lc-gran-btn" + (sort === s ? " lc-gran-on" : ""),
7980
+ onClick: () => {
7981
+ setSort(s);
7982
+ },
7983
+ children: t("ov.list.sort." + s)
7984
+ }, s))
7985
+ })
7986
+ ]
7987
+ }),
7988
+ chips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7989
+ className: "lc-ov-groups",
7990
+ role: "group",
7991
+ "aria-label": t("ov.group.label"),
7992
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7478
7993
  type: "button",
7479
- className: "lc-ov-pager-btn",
7480
- disabled: paged.index === 0,
7481
- "aria-label": t("ov.list.prev"),
7994
+ className: "lc-ov-chip" + (group === null ? " lc-ov-chip-on" : ""),
7482
7995
  onClick: () => {
7483
- setPage(paged.index - 1);
7996
+ setGroup(null);
7484
7997
  },
7485
- children: "‹"
7486
- }),
7487
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7488
- className: "lc-ov-pager-n",
7489
- children: t("ov.list.page", {
7490
- n: paged.index + 1,
7491
- total: paged.count
7998
+ children: [t("ov.range.all"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7999
+ className: "lc-ov-chip-n",
8000
+ children: scoped.length
8001
+ })]
8002
+ }), chips.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8003
+ type: "button",
8004
+ className: "lc-ov-chip" + (group === c.key ? " lc-ov-chip-on" : ""),
8005
+ onClick: () => {
8006
+ setGroup(group === c.key ? null : c.key);
8007
+ },
8008
+ children: [c.key === "__ungrouped__" ? t("ov.group.ungrouped") : c.key, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8009
+ className: "lc-ov-chip-n",
8010
+ children: c.count
8011
+ })]
8012
+ }, c.key))]
8013
+ }),
8014
+ visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8015
+ className: "lc-empty",
8016
+ children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
8017
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8018
+ className: "lc-ov-grid",
8019
+ children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
8020
+ row,
8021
+ ...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
8022
+ costLabel: cardCostOf(row, book, currency),
8023
+ now,
8024
+ onOpen: openOne
8025
+ }, row.id))
8026
+ }), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8027
+ className: "lc-ov-pager",
8028
+ role: "navigation",
8029
+ "aria-label": t("ov.list.pager"),
8030
+ children: [
8031
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8032
+ type: "button",
8033
+ className: "lc-ov-pager-btn",
8034
+ disabled: paged.index === 0,
8035
+ "aria-label": t("ov.list.prev"),
8036
+ onClick: () => {
8037
+ setPage(paged.index - 1);
8038
+ },
8039
+ children: "‹"
8040
+ }),
8041
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8042
+ className: "lc-ov-pager-n",
8043
+ children: t("ov.list.page", {
8044
+ n: paged.index + 1,
8045
+ total: paged.count
8046
+ })
8047
+ }),
8048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8049
+ type: "button",
8050
+ className: "lc-ov-pager-btn",
8051
+ disabled: paged.index === paged.count - 1,
8052
+ "aria-label": t("ov.list.next"),
8053
+ onClick: () => {
8054
+ setPage(paged.index + 1);
8055
+ },
8056
+ children: "›"
7492
8057
  })
7493
- }),
7494
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7495
- type: "button",
7496
- className: "lc-ov-pager-btn",
7497
- disabled: paged.index === paged.count - 1,
7498
- "aria-label": t("ov.list.next"),
7499
- onClick: () => {
7500
- setPage(paged.index + 1);
7501
- },
7502
- children: "›"
7503
- })
7504
- ]
7505
- })] })
7506
- ]
7507
- })]
7508
- })]
8058
+ ]
8059
+ })] })
8060
+ ]
8061
+ })]
8062
+ })
8063
+ ] })]
7509
8064
  })
7510
8065
  });
7511
8066
  }
@@ -7514,9 +8069,9 @@ window.__ModuleLoader__.load({
7514
8069
  };
7515
8070
  }
7516
8071
  /** One card's priced cost label, or the dash (no book yet, nothing billed, unpriceable model). */
7517
- function cardCostOf(row, prices, currency) {
8072
+ function cardCostOf(row, book, currency) {
7518
8073
  if (row.timeline?.cost === void 0) return "—";
7519
- const cost = estimateSessionCost(row.timeline.cost, prices, currency);
8074
+ const cost = estimateSessionCost(row.timeline.cost, book, currency);
7520
8075
  return cost === null ? "—" : formatCost(cost, currency);
7521
8076
  }
7522
8077
  //#endregion
@@ -9992,7 +10547,7 @@ window.__ModuleLoader__.load({
9992
10547
  return function PluginInfo() {
9993
10548
  const [latest, setLatest] = (0, react.useState)(null);
9994
10549
  (0, react.useEffect)(() => {
9995
- if ("0.56.2".includes("-dev")) return;
10550
+ if ("0.58.0".includes("-dev")) return;
9996
10551
  let on = true;
9997
10552
  fetchLatestVersion().then((v) => {
9998
10553
  if (on && v) setLatest(v);
@@ -10001,8 +10556,8 @@ window.__ModuleLoader__.load({
10001
10556
  on = false;
10002
10557
  };
10003
10558
  }, []);
10004
- const update = latest !== null && isNewerVersion(latest, "0.56.2") ? latest : null;
10005
- const nameText = "dsh-context (v0.56.2)";
10559
+ const update = latest !== null && isNewerVersion(latest, "0.58.0") ? latest : null;
10560
+ const nameText = "dsh-context (v0.58.0)";
10006
10561
  const nameValue = [nameText];
10007
10562
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10008
10563
  className: "lc-pi-update",
@@ -10475,8 +11030,8 @@ window.__ModuleLoader__.load({
10475
11030
  * with an off-peak bucket (DeepSeek's period-based list) shows the
10476
11031
  * peak | off-peak pair.
10477
11032
  */
10478
- function priceRowsOf(usage, prices) {
10479
- if (usage === void 0 || prices === null) return [];
11033
+ function priceRowsOf(usage, book) {
11034
+ if (usage === void 0 || book === null) return [];
10480
11035
  const rows = [];
10481
11036
  const multi = Object.keys(usage).length > 1;
10482
11037
  for (const provider of Object.keys(usage)) {
@@ -10486,7 +11041,7 @@ window.__ModuleLoader__.load({
10486
11041
  reaches here; the guard stays for the helper's own contract. */
10487
11042
  if (models === null) continue;
10488
11043
  for (const model of Object.keys(models)) {
10489
- const rate = priceOf(prices, provider, model);
11044
+ const rate = priceOf(book, provider, model);
10490
11045
  if (rate === null) continue;
10491
11046
  const periods = asRecord(models[model]);
10492
11047
  const deepseek = isDeepSeekProvider(provider);
@@ -10552,18 +11107,18 @@ window.__ModuleLoader__.load({
10552
11107
  const { t, fmt } = kit;
10553
11108
  return function StatsContext(props) {
10554
11109
  const currency = props.locale === "zh" ? "cny" : "usd";
10555
- const { prices, failed } = useModelPrices();
11110
+ const { book, failed } = useModelPrices();
10556
11111
  const subUsage = useSubagentCost(props.sessionId);
10557
11112
  const usage = mergeCostUsage(props.cost, subUsage) ?? void 0;
10558
- const cost = estimateSessionCost(usage, prices, currency);
10559
- const subCost = estimateSessionCost(subUsage, prices, currency);
11113
+ const cost = estimateSessionCost(usage, book, currency);
11114
+ const subCost = estimateSessionCost(subUsage, book, currency);
10560
11115
  const fmtRate = (usd) => formatPriceRate(toCurrency(usd, currency), currency);
10561
- const rows = priceRowsOf(usage, prices);
11116
+ const rows = priceRowsOf(usage, book);
10562
11117
  const deepseek = usage !== void 0 && Object.keys(usage).some((p) => isDeepSeekProvider(p));
10563
11118
  const subDeepseek = subUsage !== null && Object.keys(subUsage).some((p) => isDeepSeekProvider(p));
10564
11119
  const anyPair = rows.some((r) => r.offRate !== void 0);
10565
- const unpriced = rows.length === 0 && usage !== void 0 && Object.keys(usage).length > 0 && (failed || prices !== null);
10566
- const subUnpriced = subUsage !== null && priceRowsOf(subUsage, prices).length === 0 && (failed || prices !== null);
11120
+ const unpriced = rows.length === 0 && usage !== void 0 && Object.keys(usage).length > 0 && (failed || book !== null);
11121
+ const subUnpriced = subUsage !== null && priceRowsOf(subUsage, book).length === 0 && (failed || book !== null);
10567
11122
  const costTip = [
10568
11123
  t("stats.costTip") + (deepseek ? " " + t("stats.costTipDeepseek") : ""),
10569
11124
  rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -10658,245 +11213,6 @@ window.__ModuleLoader__.load({
10658
11213
  };
10659
11214
  }
10660
11215
  //#endregion
10661
- //#region src/client/components/sliceList.tsx
10662
- /**
10663
- * The stats cards' slice rows: the donut's legend with numbers, two tiers
10664
- * per slice — the primary line pairs the color dot and the name with the
10665
- * bold share closing a fixed right column, and the secondary line drops the
10666
- * absolute quantity (plus a qualifier like the call count) under the name in
10667
- * muted small print. Hovering a row lights it and its donut segment together
10668
- * (the shared hover key), and vice versa. No tracks or bars — the donut IS
10669
- * the proportion chart; the rows are its legend. Preformatted strings in,
10670
- * dumb markup out.
10671
- */
10672
- function makeSliceList(kit) {
10673
- return function SliceList(props) {
10674
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10675
- className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
10676
- onMouseLeave: () => {
10677
- if (props.onHoverKey !== void 0) props.onHoverKey(null);
10678
- },
10679
- children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10680
- className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
10681
- onMouseEnter: () => {
10682
- if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
10683
- },
10684
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10685
- className: "lc-sl-main",
10686
- children: [
10687
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
10688
- className: "lc-sl-dot",
10689
- style: { background: r.color }
10690
- }),
10691
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10692
- className: "lc-sl-label",
10693
- title: r.label,
10694
- children: r.label
10695
- }),
10696
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10697
- className: "lc-sl-pct",
10698
- children: r.pct
10699
- })
10700
- ]
10701
- }), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10702
- className: "lc-sl-sub",
10703
- title: r.count,
10704
- children: r.count
10705
- }) : null]
10706
- }, r.key))
10707
- });
10708
- };
10709
- }
10710
- //#endregion
10711
- //#region src/client/components/statsTiming.tsx
10712
- /**
10713
- * The Timing card: where the session's ACTIVE time went. The donut splits
10714
- * whole-step wall time into the model-call slices — TTFT (step start → first
10715
- * token, the wait) and LLM generation (first token → assistant message) —
10716
- * and the tool-execution slice, with the residue as overhead; a call whose
10717
- * stream recorded no token delta (legacy or aborted) stays unattributed and
10718
- * lands in the residue. When the host folded the generation split (see
10719
- * TimingTotals), the generation slice expands into what was being decoded:
10720
- * thinking, answer text, and tool-call arguments. The slice rows lead with
10721
- * the true duration and qualify it with a tally on the secondary line — the
10722
- * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
10723
- * (zero to many per call, so a call count would be a false tally there). A
10724
- * slice that never happened (zero time) is omitted entirely — the ring already skips
10725
- * its arc, and a zero row would otherwise borrow the session's call count and
10726
- * read as "no time, many calls". The donut and the rows sit side by side so
10727
- * the head row stays half-height. Parallel tool calls each count, so the tools
10728
- * figure can overlap — the ring clamps it into the post-model window while the
10729
- * row numbers stay true.
10730
- */
10731
- const COLOR = {
10732
- ttft: "var(--color-blue-500)",
10733
- reasoning: "var(--color-violet-500)",
10734
- text: "var(--color-pink-500)",
10735
- toolarg: "var(--color-amber-500)",
10736
- tools: "var(--color-teal-500)",
10737
- other: "var(--color-slate-400)"
10738
- };
10739
- /**
10740
- * The decode-throughput figure, formatted exactly as the harness's own
10741
- * session-stats seat formats it: whole tokens from ten up, one decimal below.
10742
- */
10743
- function formatTokensPerSecond(tps) {
10744
- const clamped = Math.max(0, tps);
10745
- return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
10746
- }
10747
- function makeStatsTiming(kit, Donut) {
10748
- const { t, fmt, fmtDuration, fmtShare } = kit;
10749
- const SliceList = makeSliceList(kit);
10750
- return function StatsTiming(props) {
10751
- const [hoverKey, setHoverKey] = (0, react.useState)(null);
10752
- const timing = props.timing;
10753
- const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
10754
- const tps = timing !== null && typeof timing.speedMs === "number" && Number.isFinite(timing.speedMs) && timing.speedMs > 0 && typeof timing.speedTokens === "number" && Number.isFinite(timing.speedTokens) && timing.speedTokens >= 0 ? formatTokensPerSecond(timing.speedTokens / (timing.speedMs / 1e3)) : null;
10755
- let segments = [];
10756
- let rows = [];
10757
- if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
10758
- const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
10759
- const modelSlices = [{
10760
- key: "ttft",
10761
- color: COLOR.ttft,
10762
- label: t("timing.ttft"),
10763
- ms: timing.ttftMs,
10764
- times: callTimes
10765
- }];
10766
- const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
10767
- const buckets = [
10768
- [
10769
- "reasoning",
10770
- COLOR.reasoning,
10771
- t("timing.reasoning"),
10772
- timing.reasoningMs ?? 0,
10773
- blockTimes(timing.reasoningBlocks)
10774
- ],
10775
- [
10776
- "text",
10777
- COLOR.text,
10778
- t("timing.text"),
10779
- timing.textMs ?? 0,
10780
- blockTimes(timing.textBlocks)
10781
- ],
10782
- [
10783
- "toolarg",
10784
- COLOR.toolarg,
10785
- t("timing.toolArgs"),
10786
- timing.toolArgMs ?? 0,
10787
- blockTimes(timing.toolArgBlocks)
10788
- ]
10789
- ];
10790
- if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
10791
- key,
10792
- color,
10793
- label,
10794
- ms,
10795
- ...times !== void 0 ? { times } : {}
10796
- });
10797
- else modelSlices.push({
10798
- key: "gen",
10799
- color: COLOR.reasoning,
10800
- label: t("timing.gen"),
10801
- ms: timing.genMs
10802
- });
10803
- const ttftRing = Math.min(timing.ttftMs, wall);
10804
- const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
10805
- const modelRings = [ttftRing];
10806
- let used = ttftRing;
10807
- const genEnd = ttftRing + genRing;
10808
- for (const slice of modelSlices.slice(1)) {
10809
- const take = Math.max(0, Math.min(slice.ms, genEnd - used));
10810
- modelRings.push(take);
10811
- used += take;
10812
- }
10813
- const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
10814
- const other = Math.max(0, wall - ttftRing - genRing - toolRing);
10815
- const share = (ms) => wall > 0 ? ms / wall : 0;
10816
- const countOf = (ms, times) => {
10817
- const dur = fmtDuration(ms);
10818
- if (times === void 0) return dur;
10819
- return ms > 0 ? `${dur} · ${times}` : times;
10820
- };
10821
- const modelSegments = modelSlices.map((slice, index) => ({
10822
- key: slice.key,
10823
- color: slice.color,
10824
- value: share(modelRings[index])
10825
- }));
10826
- const toolSlice = {
10827
- key: "tools",
10828
- color: COLOR.tools,
10829
- label: t("timing.tools"),
10830
- ms: timing.toolsMs,
10831
- times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
10832
- };
10833
- const otherSlice = {
10834
- key: "other",
10835
- color: COLOR.other,
10836
- label: t("timing.other"),
10837
- ms: other
10838
- };
10839
- segments = [
10840
- ...modelSegments,
10841
- {
10842
- key: "tools",
10843
- color: COLOR.tools,
10844
- value: share(toolRing)
10845
- },
10846
- {
10847
- key: "other",
10848
- color: COLOR.other,
10849
- value: share(other)
10850
- }
10851
- ];
10852
- const toRow = (slice) => ({
10853
- key: slice.key,
10854
- color: slice.color,
10855
- label: slice.label,
10856
- dim: slice.ms === 0,
10857
- pct: fmtShare(slice.ms, wall),
10858
- count: countOf(slice.ms, slice.times)
10859
- });
10860
- rows = [
10861
- ...modelSlices.map(toRow),
10862
- toRow(toolSlice),
10863
- toRow(otherSlice)
10864
- ].filter((row) => !row.dim);
10865
- }
10866
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10867
- className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
10868
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10869
- className: "lc-card-title",
10870
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10871
- className: "lc-card-title-text",
10872
- children: t("timing.title")
10873
- }), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10874
- className: "lc-timing-tps",
10875
- title: t("timing.tpsTip"),
10876
- children: t("timing.tps", { tps })
10877
- })]
10878
- }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10879
- className: "lc-empty",
10880
- children: t("timing.empty")
10881
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10882
- className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
10883
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
10884
- segments,
10885
- size: 96,
10886
- centerTop: wall > 0 ? fmtDuration(wall) : "—",
10887
- centerSub: t("timing.total"),
10888
- hoverKey,
10889
- onHoverKey: setHoverKey
10890
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
10891
- rows,
10892
- hoverKey,
10893
- onHoverKey: setHoverKey
10894
- })]
10895
- })]
10896
- });
10897
- };
10898
- }
10899
- //#endregion
10900
11216
  //#region src/client/components/statsTokens.tsx
10901
11217
  /**
10902
11218
  * The Token card: the session's whole billed token usage — the SAME total
@@ -11848,7 +12164,7 @@ window.__ModuleLoader__.load({
11848
12164
  }
11849
12165
  //#endregion
11850
12166
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/trendChart.css.mjs
11851
- const css$9 = ".lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-q3{top:41px}.lc-axis-mid{top:69px}.lc-axis-q1{top:97px}.lc-axis-bot{top:125px}.lc-chart-wrap{flex:1;min-width:0;position:relative}.lc-chart-scroll{overscroll-behavior-x:contain;scrollbar-gutter:stable;padding-bottom:var(--dsh-scrollbar-width,8px);overflow:auto hidden}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l2);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-q3{top:46px}.lc-grid-mid{top:74px}.lc-grid-q1{top:102px}.lc-grid-zero{border-top-style:solid;border-top-color:color-mix(in srgb, var(--color-neutral-500) 50%, transparent);top:130px}.lc-bar{cursor:pointer;outline-offset:1px;width:14px;height:100%;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), outline-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;outline:1px dashed #0000;flex:none;align-items:flex-end;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;box-sizing:border-box;overflow-wrap:break-word;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:100%;color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;flex-direction:column;gap:2px;padding:3px 8px;font-size:12px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;box-shadow:0 2px 8px #0000002e}.lc-chart-tip span+span{color:var(--dsw-alias-label-secondary)}.lc-bar-hovered{outline-color:var(--dsw-alias-brand-primary)}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:color-mix(in srgb, var(--color-neutral-500) 14%, transparent)}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-stack,.lc-bar-up,.lc-bar-down{transform-origin:50% 100%}.lc-bar-down{transform-origin:50% 0}.lc-bar-stack>div:first-child{border-radius:0 0 2px 2px}.lc-bar-stack>div:last-child{border-radius:2px 2px 0 0}.lc-bar-stack>div:only-child{border-radius:2px}.lc-bar-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-bar-up>div:last-child{border-radius:2px 2px 0 0}.lc-bar-down>div:last-child{border-radius:0 0 2px 2px}.lc-cat-seg{transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chart[data-catdim] .lc-cat-seg{opacity:.35}.lc-chart[data-catdim=system] .lc-cat-seg[data-cat=system],.lc-chart[data-catdim=tools] .lc-cat-seg[data-cat=tools],.lc-chart[data-catdim=user] .lc-cat-seg[data-cat=user],.lc-chart[data-catdim=inject] .lc-cat-seg[data-cat=inject],.lc-chart[data-catdim=skill] .lc-cat-seg[data-cat=skill],.lc-chart[data-catdim=assistant] .lc-cat-seg[data-cat=assistant],.lc-chart[data-catdim=tool] .lc-cat-seg[data-cat=tool]{opacity:1;filter:brightness(1.18)}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-turns{gap:2px;width:max-content;min-width:100%;margin-top:4px;display:flex;overflow:hidden}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;cursor:pointer;height:14px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:3px;flex:none;font-size:10px;font-weight:600;line-height:14px}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{display:inline-block}";
12167
+ const css$9 = ".lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-q3{top:41px}.lc-axis-mid{top:69px}.lc-axis-q1{top:97px}.lc-axis-bot{top:125px}.lc-chart-wrap{flex:1;min-width:0;position:relative}.lc-chart-scroll{overscroll-behavior-x:contain;scrollbar-gutter:stable;padding-bottom:var(--dsh-scrollbar-width,8px);overflow:auto hidden}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding:18px 2px 0;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l2);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-q3{top:46px}.lc-grid-mid{top:74px}.lc-grid-q1{top:102px}.lc-grid-zero{border-top-style:solid;border-top-color:color-mix(in srgb, var(--color-neutral-500) 50%, transparent);top:130px}.lc-bar{cursor:pointer;outline-offset:1px;width:14px;height:100%;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), outline-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;outline:1px dashed #0000;flex:none;align-items:flex-end;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;box-sizing:border-box;overflow-wrap:break-word;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:100%;color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;flex-direction:column;gap:2px;padding:3px 8px;font-size:12px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;box-shadow:0 2px 8px #0000002e}.lc-chart-tip span+span{color:var(--dsw-alias-label-secondary)}.lc-bar-hovered{outline-color:var(--dsw-alias-brand-primary)}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:color-mix(in srgb, var(--color-neutral-500) 14%, transparent)}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-stack,.lc-bar-up,.lc-bar-down{transform-origin:50% 100%}.lc-bar-down{transform-origin:50% 0}.lc-bar-stack>div:first-child{border-radius:0 0 2px 2px}.lc-bar-stack>div:last-child{border-radius:2px 2px 0 0}.lc-bar-stack>div:only-child{border-radius:2px}.lc-bar-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-bar-up>div:last-child{border-radius:2px 2px 0 0}.lc-bar-down>div:last-child{border-radius:0 0 2px 2px}.lc-cat-seg{transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chart[data-catdim] .lc-cat-seg{opacity:.35}.lc-chart[data-catdim=system] .lc-cat-seg[data-cat=system],.lc-chart[data-catdim=tools] .lc-cat-seg[data-cat=tools],.lc-chart[data-catdim=user] .lc-cat-seg[data-cat=user],.lc-chart[data-catdim=inject] .lc-cat-seg[data-cat=inject],.lc-chart[data-catdim=skill] .lc-cat-seg[data-cat=skill],.lc-chart[data-catdim=assistant] .lc-cat-seg[data-cat=assistant],.lc-chart[data-catdim=tool] .lc-cat-seg[data-cat=tool]{opacity:1;filter:brightness(1.18)}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-step-flag{background:color-mix(in srgb, var(--color-neutral-500) 45%, transparent);pointer-events:none;width:1px;position:absolute;top:-17px;bottom:0;right:-1px}.lc-step-flag-label{box-sizing:border-box;border:1px solid color-mix(in srgb, var(--color-neutral-500) 45%, transparent);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:3px;padding:1px 3px;font-size:9px;font-weight:600;line-height:1;position:absolute;top:0;right:0}.lc-turns{box-sizing:border-box;gap:2px;width:max-content;min-width:100%;margin-top:4px;padding:0 2px;display:flex;overflow:hidden}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;cursor:pointer;height:14px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:3px;flex:none;font-size:10px;font-weight:600;line-height:14px}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{display:inline-block}";
11852
12168
  const tagId$9 = "dsh-context/trendChart.css";
11853
12169
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$9) + "]") === null) {
11854
12170
  const tag = document.createElement("style");
@@ -11947,7 +12263,7 @@ window.__ModuleLoader__.load({
11947
12263
  }
11948
12264
  //#endregion
11949
12265
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
11950
- const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);height:calc(100% - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-ov-settings{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex:none;align-items:center;gap:8px;padding:9px 12px;font-size:12px;display:flex}.lc-ov-settings:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.lc-ov-settings-label{white-space:nowrap;flex:none;align-items:center;gap:6px;display:inline-flex}.lc-ov-settings-hint{text-align:right;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-heat-wrap{flex-direction:column;flex:1;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:18px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{height:20px;color:var(--dsw-alias-label-secondary);text-align:center;white-space:nowrap;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:18px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-heat-legend{justify-content:flex-end;align-items:center;gap:3px;padding-top:6px;display:flex}.lc-heat-key{color:var(--dsw-alias-label-secondary);font-size:9px}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
12266
+ const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);height:calc(100% - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-kpis{flex:none;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin-bottom:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}@container lc-ov (width<=1000px){.lc-ov-kpis{grid-template-columns:repeat(3,minmax(0,1fr))}}@container lc-ov (width<=640px){.lc-ov-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}}.lc-ov-stats{flex:none;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:10px;display:grid}.lc-ov-stats>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}@container lc-ov (width<=700px){.lc-ov-stats{grid-template-columns:minmax(0,1fr)}}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-ov-settings{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex:none;align-items:center;gap:8px;padding:9px 12px;font-size:12px;display:flex}.lc-ov-settings:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.lc-ov-settings-label{white-space:nowrap;flex:none;align-items:center;gap:6px;display:inline-flex}.lc-ov-settings-hint{text-align:right;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-heat-wrap{flex-direction:column;flex:1;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:18px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{height:20px;color:var(--dsw-alias-label-secondary);text-align:center;white-space:nowrap;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:18px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-heat-legend{justify-content:flex-end;align-items:center;gap:3px;padding-top:6px;display:flex}.lc-heat-key{color:var(--dsw-alias-label-secondary);font-size:9px}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
11951
12267
  const tagId = "dsh-context/overview.css";
11952
12268
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11953
12269
  const tag = document.createElement("style");