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.
- package/lib/client.js +1024 -708
- package/lib/index.js +47 -7
- 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: [
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
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 —
|
|
5491
|
-
*
|
|
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
|
|
5557
|
-
* falls back to
|
|
5558
|
-
*
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
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
|
-
|
|
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,
|
|
5583
|
-
if (usage === null || usage === void 0 ||
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
5743
|
-
*
|
|
5744
|
-
*
|
|
5745
|
-
*
|
|
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
|
-
*
|
|
5755
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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/
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
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
|
-
|
|
6969
|
-
|
|
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
|
-
|
|
6972
|
-
className: "lc-
|
|
6973
|
-
|
|
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-
|
|
6977
|
-
title:
|
|
6978
|
-
children:
|
|
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
|
-
|
|
7009
|
-
className: "lc-
|
|
7010
|
-
children:
|
|
7220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7221
|
+
className: "lc-sl-pct",
|
|
7222
|
+
children: r.pct
|
|
7011
7223
|
})
|
|
7012
7224
|
]
|
|
7013
|
-
}),
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
children:
|
|
7017
|
-
}) :
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
})
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
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
|
|
7086
|
-
*
|
|
7087
|
-
*
|
|
7088
|
-
*
|
|
7089
|
-
*
|
|
7090
|
-
*
|
|
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 {
|
|
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,
|
|
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)(
|
|
7221
|
-
|
|
7222
|
-
|
|
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-
|
|
7774
|
+
className: "lc-stat lc-ov-kpi",
|
|
7227
7775
|
children: [
|
|
7228
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7229
|
-
className: "lc-stat
|
|
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.
|
|
7246
|
-
className: "lc-stat
|
|
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.
|
|
7267
|
-
className: "lc-stat
|
|
7268
|
-
children:
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
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.
|
|
7284
|
-
className: "lc-stat
|
|
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)("
|
|
7301
|
-
className: "lc-stat
|
|
7801
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7802
|
+
className: "lc-stat-sub",
|
|
7302
7803
|
children: [
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
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.
|
|
7318
|
-
className: "lc-stat
|
|
7319
|
-
children:
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
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
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
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-
|
|
7956
|
+
className: "lc-ov-day-chip",
|
|
7957
|
+
title: t("ov.list.dayClear"),
|
|
7426
7958
|
onClick: () => {
|
|
7427
|
-
|
|
7959
|
+
setDay(null);
|
|
7428
7960
|
},
|
|
7429
|
-
children: t("ov.list.
|
|
7430
|
-
},
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
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-
|
|
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
|
-
|
|
7996
|
+
setGroup(null);
|
|
7484
7997
|
},
|
|
7485
|
-
children: "
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
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
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
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,
|
|
8072
|
+
function cardCostOf(row, book, currency) {
|
|
7518
8073
|
if (row.timeline?.cost === void 0) return "—";
|
|
7519
|
-
const cost = estimateSessionCost(row.timeline.cost,
|
|
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.
|
|
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.
|
|
10005
|
-
const nameText = "dsh-context (v0.
|
|
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,
|
|
10479
|
-
if (usage === void 0 ||
|
|
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(
|
|
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 {
|
|
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,
|
|
10559
|
-
const subCost = estimateSessionCost(subUsage,
|
|
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,
|
|
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 ||
|
|
10566
|
-
const subUnpriced = subUsage !== null && priceRowsOf(subUsage,
|
|
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
|
|
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");
|