dsh-context 0.56.1 → 0.57.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 +854 -675
- package/lib/index.js +41 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -356,6 +356,7 @@ window.__ModuleLoader__.load({
|
|
|
356
356
|
"ov.kpi.sessionsSub": "含 {n} 个会话",
|
|
357
357
|
"ov.kpi.toolSub": "工具执行 {dur}",
|
|
358
358
|
"ov.kpi.wallSub": "模型调用 {n}",
|
|
359
|
+
"ov.stats.empty": "统计范围内暂无 Token 用量",
|
|
359
360
|
"balance.title": "DeepSeek 余额",
|
|
360
361
|
"balance.tip.granted": "赠送余额",
|
|
361
362
|
"balance.tip.toppedUp": "充值余额",
|
|
@@ -735,6 +736,7 @@ window.__ModuleLoader__.load({
|
|
|
735
736
|
"ov.kpi.sessionsSub": "across {n} sessions",
|
|
736
737
|
"ov.kpi.toolSub": "tool runs {dur}",
|
|
737
738
|
"ov.kpi.wallSub": "{n} model calls",
|
|
739
|
+
"ov.stats.empty": "No billed tokens in this range",
|
|
738
740
|
"balance.title": "DeepSeek balance",
|
|
739
741
|
"balance.tip.granted": "Granted balance",
|
|
740
742
|
"balance.tip.toppedUp": "Topped-up balance",
|
|
@@ -1751,6 +1753,31 @@ window.__ModuleLoader__.load({
|
|
|
1751
1753
|
}
|
|
1752
1754
|
};
|
|
1753
1755
|
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Jump to one session — the harness's own session-selection verb (issue #90).
|
|
1758
|
+
* Every supported line selects through the view owner (`uiWorkspace`
|
|
1759
|
+
* .openSession, the sidebar row click's own verb); the sessions service's own
|
|
1760
|
+
* `open` is the retired spelling (still served through the 0.1.5 line, gone
|
|
1761
|
+
* since the 0.1.6 selection refactor) and stays as the degradation path for a
|
|
1762
|
+
* composition without the workspace module. Both faces are re-proved per call
|
|
1763
|
+
* (a service can land or be revoked across an HMR reload) and the verb is
|
|
1764
|
+
* invoked bound (the service instance reads its own state); a generation
|
|
1765
|
+
* serving neither, or a hostile face, swallows — the jump is best-effort by
|
|
1766
|
+
* nature.
|
|
1767
|
+
*/
|
|
1768
|
+
function openSessionVia(ctx, id) {
|
|
1769
|
+
try {
|
|
1770
|
+
const workspace = asRecord(ctx.get("uiWorkspace"));
|
|
1771
|
+
const open = workspace?.openSession;
|
|
1772
|
+
if (typeof open === "function") {
|
|
1773
|
+
open.call(workspace, id);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
const sessions = asRecord(ctx.get("sessions"));
|
|
1777
|
+
const legacy = sessions?.open;
|
|
1778
|
+
if (typeof legacy === "function") legacy.call(sessions, id);
|
|
1779
|
+
} catch {}
|
|
1780
|
+
}
|
|
1754
1781
|
//#endregion
|
|
1755
1782
|
//#region src/client/historyPage.ts
|
|
1756
1783
|
/**
|
|
@@ -6181,6 +6208,105 @@ window.__ModuleLoader__.load({
|
|
|
6181
6208
|
totals.total = totals.input + totals.cacheRead + totals.cacheWrite + totals.output;
|
|
6182
6209
|
return totals;
|
|
6183
6210
|
}
|
|
6211
|
+
/**
|
|
6212
|
+
* The range's summed timing totals — the aggregate Timing Stats card's
|
|
6213
|
+
* source, folded from the rows' host-folded whole-session totals exactly as
|
|
6214
|
+
* the KPI band folds the scalar figures. Null when no row carries timing.
|
|
6215
|
+
* The additive-optional fields (the decode split, the throughput seat) sum
|
|
6216
|
+
* their carriers only: a row cached before a field existed contributes
|
|
6217
|
+
* nothing to it, and a field no row carries stays absent — the card keeps
|
|
6218
|
+
* its un-split shape / no-chip fallback instead of materialized zeros.
|
|
6219
|
+
*/
|
|
6220
|
+
function timingSumOf(rows) {
|
|
6221
|
+
let out = null;
|
|
6222
|
+
for (const row of rows) {
|
|
6223
|
+
const t = row.timeline?.timing;
|
|
6224
|
+
if (t === void 0) continue;
|
|
6225
|
+
if (out === null) {
|
|
6226
|
+
out = {
|
|
6227
|
+
...t,
|
|
6228
|
+
tools: { ...t.tools }
|
|
6229
|
+
};
|
|
6230
|
+
continue;
|
|
6231
|
+
}
|
|
6232
|
+
out.wallMs += t.wallMs;
|
|
6233
|
+
out.ttftMs += t.ttftMs;
|
|
6234
|
+
out.genMs += t.genMs;
|
|
6235
|
+
out.calls += t.calls;
|
|
6236
|
+
out.toolsMs += t.toolsMs;
|
|
6237
|
+
out.toolCalls += t.toolCalls;
|
|
6238
|
+
addOptionalOf(out, t, "reasoningMs");
|
|
6239
|
+
addOptionalOf(out, t, "reasoningBlocks");
|
|
6240
|
+
addOptionalOf(out, t, "textMs");
|
|
6241
|
+
addOptionalOf(out, t, "textBlocks");
|
|
6242
|
+
addOptionalOf(out, t, "toolArgMs");
|
|
6243
|
+
addOptionalOf(out, t, "toolArgBlocks");
|
|
6244
|
+
addOptionalOf(out, t, "speedTokens");
|
|
6245
|
+
addOptionalOf(out, t, "speedMs");
|
|
6246
|
+
for (const name of Object.keys(t.tools)) {
|
|
6247
|
+
const tool = t.tools[name];
|
|
6248
|
+
const prev = out.tools[name];
|
|
6249
|
+
out.tools[name] = prev === void 0 ? { ...tool } : {
|
|
6250
|
+
calls: prev.calls + tool.calls,
|
|
6251
|
+
ms: prev.ms + tool.ms
|
|
6252
|
+
};
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
return out;
|
|
6256
|
+
}
|
|
6257
|
+
/** One additive-optional timing field's in-place merge: absent on both sides stays absent (never a materialized undefined). */
|
|
6258
|
+
function addOptionalOf(target, source, key) {
|
|
6259
|
+
const a = target[key];
|
|
6260
|
+
const b = source[key];
|
|
6261
|
+
if (a !== void 0 || b !== void 0) target[key] = (a ?? 0) + (b ?? 0);
|
|
6262
|
+
}
|
|
6263
|
+
/**
|
|
6264
|
+
* The range's billed tokens split by WHAT they are — the Context tab's Token
|
|
6265
|
+
* card categorization, folded across sessions: each session's
|
|
6266
|
+
* `billedParts` estimate (the composition ratios proportioning that
|
|
6267
|
+
* session's provider-reported prompt total, output exact) sums by category,
|
|
6268
|
+
* and every session's parts total its own billed figure, so the aggregate's
|
|
6269
|
+
* total stays the exact merged billed volume while the split inherits the
|
|
6270
|
+
* per-session card's `≈` estimate convention. Null when no session billed
|
|
6271
|
+
* anything. A non-finite part estimate (a hostile fast-path composition)
|
|
6272
|
+
* drops whole instead of poisoning the sums.
|
|
6273
|
+
*/
|
|
6274
|
+
function tokenPartsOf(rows) {
|
|
6275
|
+
const sums = /* @__PURE__ */ new Map();
|
|
6276
|
+
let total = 0;
|
|
6277
|
+
let any = false;
|
|
6278
|
+
for (const row of rows) {
|
|
6279
|
+
const timeline = row.timeline;
|
|
6280
|
+
if (timeline === null) continue;
|
|
6281
|
+
const totals = usageTotalsOf(timeline.cost);
|
|
6282
|
+
if (totals === null) continue;
|
|
6283
|
+
any = true;
|
|
6284
|
+
const usage = {
|
|
6285
|
+
uncachedInputTokens: totals.input,
|
|
6286
|
+
cacheReadTokens: totals.cacheRead,
|
|
6287
|
+
cacheWriteTokens: totals.cacheWrite,
|
|
6288
|
+
outputTokens: totals.output
|
|
6289
|
+
};
|
|
6290
|
+
for (const part of billedParts(timeline.current, null, usage)) {
|
|
6291
|
+
if (!Number.isFinite(part.value) || part.value <= 0) continue;
|
|
6292
|
+
total += part.value;
|
|
6293
|
+
const prev = sums.get(part.key);
|
|
6294
|
+
sums.set(part.key, prev === void 0 ? {
|
|
6295
|
+
key: part.key,
|
|
6296
|
+
color: part.color,
|
|
6297
|
+
value: part.value
|
|
6298
|
+
} : {
|
|
6299
|
+
...prev,
|
|
6300
|
+
value: prev.value + part.value
|
|
6301
|
+
});
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
if (!any) return null;
|
|
6305
|
+
return {
|
|
6306
|
+
parts: [...sums.values()],
|
|
6307
|
+
total
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6184
6310
|
function kpisOf(rows, listed, prices, currency) {
|
|
6185
6311
|
const usage = mergeCostUsage(...rows.map((row) => row.timeline?.cost));
|
|
6186
6312
|
const totals = usageTotalsOf(usage);
|
|
@@ -6214,7 +6340,9 @@ window.__ModuleLoader__.load({
|
|
|
6214
6340
|
toolCalls,
|
|
6215
6341
|
toolsMs,
|
|
6216
6342
|
calls,
|
|
6217
|
-
wallMs
|
|
6343
|
+
wallMs,
|
|
6344
|
+
tokenParts: tokenPartsOf(rows),
|
|
6345
|
+
timing: timingSumOf(rows)
|
|
6218
6346
|
};
|
|
6219
6347
|
}
|
|
6220
6348
|
/**
|
|
@@ -6248,18 +6376,6 @@ window.__ModuleLoader__.load({
|
|
|
6248
6376
|
return days;
|
|
6249
6377
|
}
|
|
6250
6378
|
/**
|
|
6251
|
-
* Jump to one session: the harness's own selection verb (`sessions.open`,
|
|
6252
|
-
* the sidebar row click's mechanism). The face is re-proved per call and a
|
|
6253
|
-
* hostile or absent service swallows silently — the panel still closes, so
|
|
6254
|
-
* the gesture never dead-ends on an error.
|
|
6255
|
-
*/
|
|
6256
|
-
function openSession(ctx, id) {
|
|
6257
|
-
try {
|
|
6258
|
-
const sessions = ctx.get("sessions");
|
|
6259
|
-
if (sessions !== void 0 && typeof sessions.open === "function") sessions.open(id);
|
|
6260
|
-
} catch {}
|
|
6261
|
-
}
|
|
6262
|
-
/**
|
|
6263
6379
|
* Re-pull the session-list baseline so host-side projection backfills
|
|
6264
6380
|
* (host/backfill.ts) reach a long-connected browser. Fire-and-forget: the
|
|
6265
6381
|
* verb is re-proved and every failure swallows — the panel renders off the
|
|
@@ -6926,206 +7042,512 @@ window.__ModuleLoader__.load({
|
|
|
6926
7042
|
};
|
|
6927
7043
|
}
|
|
6928
7044
|
//#endregion
|
|
6929
|
-
//#region src/client/components/
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
const created = createdDayOf(row.activity);
|
|
6947
|
-
const lastUser = timeline?.lastUser;
|
|
6948
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6949
|
-
type: "button",
|
|
6950
|
-
className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
|
|
6951
|
-
onClick: () => {
|
|
6952
|
-
props.onOpen(row.id);
|
|
7045
|
+
//#region src/client/components/sliceList.tsx
|
|
7046
|
+
/**
|
|
7047
|
+
* The stats cards' slice rows: the donut's legend with numbers, two tiers
|
|
7048
|
+
* per slice — the primary line pairs the color dot and the name with the
|
|
7049
|
+
* bold share closing a fixed right column, and the secondary line drops the
|
|
7050
|
+
* absolute quantity (plus a qualifier like the call count) under the name in
|
|
7051
|
+
* muted small print. Hovering a row lights it and its donut segment together
|
|
7052
|
+
* (the shared hover key), and vice versa. No tracks or bars — the donut IS
|
|
7053
|
+
* the proportion chart; the rows are its legend. Preformatted strings in,
|
|
7054
|
+
* dumb markup out.
|
|
7055
|
+
*/
|
|
7056
|
+
function makeSliceList(kit) {
|
|
7057
|
+
return function SliceList(props) {
|
|
7058
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7059
|
+
className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
|
|
7060
|
+
onMouseLeave: () => {
|
|
7061
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
6953
7062
|
},
|
|
6954
|
-
children:
|
|
6955
|
-
|
|
6956
|
-
|
|
7063
|
+
children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7064
|
+
className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
|
|
7065
|
+
onMouseEnter: () => {
|
|
7066
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
|
|
7067
|
+
},
|
|
7068
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7069
|
+
className: "lc-sl-main",
|
|
6957
7070
|
children: [
|
|
6958
|
-
|
|
6959
|
-
className: "lc-
|
|
6960
|
-
|
|
7071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
7072
|
+
className: "lc-sl-dot",
|
|
7073
|
+
style: { background: r.color }
|
|
6961
7074
|
}),
|
|
6962
7075
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6963
|
-
className: "lc-
|
|
6964
|
-
title:
|
|
6965
|
-
children:
|
|
6966
|
-
}),
|
|
6967
|
-
row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6968
|
-
className: "lc-ov-current",
|
|
6969
|
-
children: t("ov.current")
|
|
6970
|
-
}),
|
|
6971
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6972
|
-
className: "lc-ov-session-times",
|
|
6973
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6974
|
-
className: "lc-ov-session-time",
|
|
6975
|
-
children: relativeTime(t, row.updatedAt, props.now)
|
|
6976
|
-
}), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6977
|
-
className: "lc-ov-session-created",
|
|
6978
|
-
children: created
|
|
6979
|
-
})]
|
|
6980
|
-
})
|
|
6981
|
-
]
|
|
6982
|
-
}),
|
|
6983
|
-
(group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6984
|
-
className: "lc-ov-session-crumb",
|
|
6985
|
-
title: row.cwd,
|
|
6986
|
-
children: [
|
|
6987
|
-
group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6988
|
-
className: "lc-ov-crumb-group",
|
|
6989
|
-
children: group
|
|
6990
|
-
}),
|
|
6991
|
-
group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6992
|
-
className: "lc-ov-crumb-sep",
|
|
6993
|
-
children: "/"
|
|
7076
|
+
className: "lc-sl-label",
|
|
7077
|
+
title: r.label,
|
|
7078
|
+
children: r.label
|
|
6994
7079
|
}),
|
|
6995
|
-
|
|
6996
|
-
className: "lc-
|
|
6997
|
-
children:
|
|
7080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7081
|
+
className: "lc-sl-pct",
|
|
7082
|
+
children: r.pct
|
|
6998
7083
|
})
|
|
6999
7084
|
]
|
|
7000
|
-
}),
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
children:
|
|
7004
|
-
}) :
|
|
7005
|
-
|
|
7006
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
7007
|
-
size: 64,
|
|
7008
|
-
segments: partsOf(timeline.current),
|
|
7009
|
-
centerTop: fmt(timeline.current.total),
|
|
7010
|
-
centerSub: occupancy ?? void 0
|
|
7011
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7012
|
-
className: "lc-ov-mini-stats",
|
|
7013
|
-
children: [
|
|
7014
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7015
|
-
className: "lc-ov-mini-stat",
|
|
7016
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7017
|
-
className: "lc-ov-mini-label",
|
|
7018
|
-
children: t("stats.turns")
|
|
7019
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7020
|
-
className: "lc-ov-mini-value",
|
|
7021
|
-
children: turnsLabel
|
|
7022
|
-
})]
|
|
7023
|
-
}),
|
|
7024
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7025
|
-
className: "lc-ov-mini-stat",
|
|
7026
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7027
|
-
className: "lc-ov-mini-label",
|
|
7028
|
-
children: t("tokens.total")
|
|
7029
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7030
|
-
className: "lc-ov-mini-value",
|
|
7031
|
-
children: billed === null ? "—" : fmt(billed)
|
|
7032
|
-
})]
|
|
7033
|
-
}),
|
|
7034
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7035
|
-
className: "lc-ov-mini-stat",
|
|
7036
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7037
|
-
className: "lc-ov-mini-label",
|
|
7038
|
-
children: t("stats.cost")
|
|
7039
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7040
|
-
className: "lc-ov-mini-value",
|
|
7041
|
-
children: props.costLabel
|
|
7042
|
-
})]
|
|
7043
|
-
})
|
|
7044
|
-
]
|
|
7045
|
-
})]
|
|
7046
|
-
}),
|
|
7047
|
-
lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7048
|
-
className: "lc-ov-session-preview",
|
|
7049
|
-
title: lastUser,
|
|
7050
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7051
|
-
className: "lc-ov-session-preview-label",
|
|
7052
|
-
children: t("ov.card.lastUser")
|
|
7053
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7054
|
-
className: "lc-ov-session-preview-text",
|
|
7055
|
-
children: lastUser
|
|
7056
|
-
})]
|
|
7057
|
-
})
|
|
7058
|
-
]
|
|
7085
|
+
}), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7086
|
+
className: "lc-sl-sub",
|
|
7087
|
+
title: r.count,
|
|
7088
|
+
children: r.count
|
|
7089
|
+
}) : null]
|
|
7090
|
+
}, r.key))
|
|
7059
7091
|
});
|
|
7060
7092
|
};
|
|
7061
7093
|
}
|
|
7062
7094
|
//#endregion
|
|
7063
|
-
//#region src/client/components/
|
|
7095
|
+
//#region src/client/components/overviewTokens.tsx
|
|
7064
7096
|
/**
|
|
7065
|
-
* The Context
|
|
7066
|
-
*
|
|
7067
|
-
*
|
|
7068
|
-
*
|
|
7069
|
-
*
|
|
7070
|
-
*
|
|
7071
|
-
*
|
|
7072
|
-
* The body is a 3:7 column pair: the insight column (the KPI 2×3 block over
|
|
7073
|
-
* the activity heatmap, then the preferences entry row) beside the session
|
|
7074
|
-
* column (search, group chips, and the card grid); the heatmap keeps its own
|
|
7075
|
-
* fixed 8-week window and PINs the list to a picked day (the panel's
|
|
7076
|
-
* drill-down gesture). A session card click jumps to that session through
|
|
7077
|
-
* the harness's own `sessions.open` and closes the panel.
|
|
7097
|
+
* The Context Insights panel's aggregate Token Stats card — the first row's
|
|
7098
|
+
* token half, categorized exactly like the Context tab's Token card
|
|
7099
|
+
* (statsTokens.tsx): the range's billed volume split by WHAT the tokens are —
|
|
7100
|
+
* the composition categories' `≈` estimates proportioning each session's
|
|
7101
|
+
* provider-reported prompt total, plus the exact output — folded by
|
|
7102
|
+
* tokenPartsOf. The center figure stays the exact merged billed total the
|
|
7103
|
+
* KPI band's Tokens Used cell shows.
|
|
7078
7104
|
*/
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7105
|
+
function makeOverviewTokens(kit, Donut) {
|
|
7106
|
+
const { t, fmt, fmtShare, catLabel } = kit;
|
|
7107
|
+
const SliceList = makeSliceList(kit);
|
|
7108
|
+
return function OverviewTokens(props) {
|
|
7109
|
+
const [hoverKey, setHoverKey] = (0, react.useState)(null);
|
|
7110
|
+
const tokens = props.tokens;
|
|
7111
|
+
const total = tokens?.total ?? 0;
|
|
7112
|
+
const shown = tokens !== null && total > 0 ? tokens.parts.filter((p) => p.value > 0) : [];
|
|
7113
|
+
const rows = shown.map((p) => ({
|
|
7114
|
+
key: p.key,
|
|
7115
|
+
color: p.color,
|
|
7116
|
+
label: p.key === "output" ? t("tokens.output") : catLabel(p.key),
|
|
7117
|
+
pct: fmtShare(p.value, total),
|
|
7118
|
+
count: p.key === "output" ? `${fmt(p.value)} · ${t("tokens.outputNote")}` : "≈" + fmt(p.value)
|
|
7119
|
+
}));
|
|
7120
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7121
|
+
className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
|
|
7122
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7123
|
+
className: "lc-card-title",
|
|
7124
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7125
|
+
className: "lc-card-title-text",
|
|
7126
|
+
children: t("tokens.title")
|
|
7127
|
+
})
|
|
7128
|
+
}), shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7129
|
+
className: "lc-empty",
|
|
7130
|
+
children: t("ov.stats.empty")
|
|
7131
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7132
|
+
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",
|
|
7133
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
7134
|
+
segments: shown.map((p) => ({
|
|
7135
|
+
key: p.key,
|
|
7136
|
+
color: p.color,
|
|
7137
|
+
value: p.value
|
|
7138
|
+
})),
|
|
7139
|
+
size: 96,
|
|
7140
|
+
centerTop: fmt(total),
|
|
7141
|
+
centerSub: t("tokens.total"),
|
|
7142
|
+
hoverKey,
|
|
7143
|
+
onHoverKey: setHoverKey
|
|
7144
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
|
|
7145
|
+
rows,
|
|
7146
|
+
hoverKey,
|
|
7147
|
+
onHoverKey: setHoverKey
|
|
7148
|
+
})]
|
|
7149
|
+
})]
|
|
7150
|
+
});
|
|
7151
|
+
};
|
|
7152
|
+
}
|
|
7153
|
+
//#endregion
|
|
7154
|
+
//#region src/client/components/statsTiming.tsx
|
|
7155
|
+
/**
|
|
7156
|
+
* The Timing card: where the session's ACTIVE time went. The donut splits
|
|
7157
|
+
* whole-step wall time into the model-call slices — TTFT (step start → first
|
|
7158
|
+
* token, the wait) and LLM generation (first token → assistant message) —
|
|
7159
|
+
* and the tool-execution slice, with the residue as overhead; a call whose
|
|
7160
|
+
* stream recorded no token delta (legacy or aborted) stays unattributed and
|
|
7161
|
+
* lands in the residue. When the host folded the generation split (see
|
|
7162
|
+
* TimingTotals), the generation slice expands into what was being decoded:
|
|
7163
|
+
* thinking, answer text, and tool-call arguments. The slice rows lead with
|
|
7164
|
+
* the true duration and qualify it with a tally on the secondary line — the
|
|
7165
|
+
* model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
|
|
7166
|
+
* (zero to many per call, so a call count would be a false tally there). A
|
|
7167
|
+
* slice that never happened (zero time) is omitted entirely — the ring already skips
|
|
7168
|
+
* its arc, and a zero row would otherwise borrow the session's call count and
|
|
7169
|
+
* read as "no time, many calls". The donut and the rows sit side by side so
|
|
7170
|
+
* the head row stays half-height. Parallel tool calls each count, so the tools
|
|
7171
|
+
* figure can overlap — the ring clamps it into the post-model window while the
|
|
7172
|
+
* row numbers stay true.
|
|
7173
|
+
*/
|
|
7174
|
+
const COLOR = {
|
|
7175
|
+
ttft: "var(--color-blue-500)",
|
|
7176
|
+
reasoning: "var(--color-violet-500)",
|
|
7177
|
+
text: "var(--color-pink-500)",
|
|
7178
|
+
toolarg: "var(--color-amber-500)",
|
|
7179
|
+
tools: "var(--color-teal-500)",
|
|
7180
|
+
other: "var(--color-slate-400)"
|
|
7181
|
+
};
|
|
7182
|
+
/**
|
|
7183
|
+
* The decode-throughput figure, formatted exactly as the harness's own
|
|
7184
|
+
* session-stats seat formats it: whole tokens from ten up, one decimal below.
|
|
7185
|
+
*/
|
|
7186
|
+
function formatTokensPerSecond(tps) {
|
|
7187
|
+
const clamped = Math.max(0, tps);
|
|
7188
|
+
return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
|
|
7189
|
+
}
|
|
7190
|
+
function makeStatsTiming(kit, Donut) {
|
|
7191
|
+
const { t, fmt, fmtDuration, fmtShare } = kit;
|
|
7192
|
+
const SliceList = makeSliceList(kit);
|
|
7193
|
+
return function StatsTiming(props) {
|
|
7194
|
+
const [hoverKey, setHoverKey] = (0, react.useState)(null);
|
|
7195
|
+
const timing = props.timing;
|
|
7196
|
+
const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
|
|
7197
|
+
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;
|
|
7198
|
+
let segments = [];
|
|
7199
|
+
let rows = [];
|
|
7200
|
+
if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
|
|
7201
|
+
const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
|
|
7202
|
+
const modelSlices = [{
|
|
7203
|
+
key: "ttft",
|
|
7204
|
+
color: COLOR.ttft,
|
|
7205
|
+
label: t("timing.ttft"),
|
|
7206
|
+
ms: timing.ttftMs,
|
|
7207
|
+
times: callTimes
|
|
7208
|
+
}];
|
|
7209
|
+
const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
|
|
7210
|
+
const buckets = [
|
|
7211
|
+
[
|
|
7212
|
+
"reasoning",
|
|
7213
|
+
COLOR.reasoning,
|
|
7214
|
+
t("timing.reasoning"),
|
|
7215
|
+
timing.reasoningMs ?? 0,
|
|
7216
|
+
blockTimes(timing.reasoningBlocks)
|
|
7217
|
+
],
|
|
7218
|
+
[
|
|
7219
|
+
"text",
|
|
7220
|
+
COLOR.text,
|
|
7221
|
+
t("timing.text"),
|
|
7222
|
+
timing.textMs ?? 0,
|
|
7223
|
+
blockTimes(timing.textBlocks)
|
|
7224
|
+
],
|
|
7225
|
+
[
|
|
7226
|
+
"toolarg",
|
|
7227
|
+
COLOR.toolarg,
|
|
7228
|
+
t("timing.toolArgs"),
|
|
7229
|
+
timing.toolArgMs ?? 0,
|
|
7230
|
+
blockTimes(timing.toolArgBlocks)
|
|
7231
|
+
]
|
|
7232
|
+
];
|
|
7233
|
+
if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
|
|
7234
|
+
key,
|
|
7235
|
+
color,
|
|
7236
|
+
label,
|
|
7237
|
+
ms,
|
|
7238
|
+
...times !== void 0 ? { times } : {}
|
|
7239
|
+
});
|
|
7240
|
+
else modelSlices.push({
|
|
7241
|
+
key: "gen",
|
|
7242
|
+
color: COLOR.reasoning,
|
|
7243
|
+
label: t("timing.gen"),
|
|
7244
|
+
ms: timing.genMs
|
|
7245
|
+
});
|
|
7246
|
+
const ttftRing = Math.min(timing.ttftMs, wall);
|
|
7247
|
+
const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
|
|
7248
|
+
const modelRings = [ttftRing];
|
|
7249
|
+
let used = ttftRing;
|
|
7250
|
+
const genEnd = ttftRing + genRing;
|
|
7251
|
+
for (const slice of modelSlices.slice(1)) {
|
|
7252
|
+
const take = Math.max(0, Math.min(slice.ms, genEnd - used));
|
|
7253
|
+
modelRings.push(take);
|
|
7254
|
+
used += take;
|
|
7255
|
+
}
|
|
7256
|
+
const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
|
|
7257
|
+
const other = Math.max(0, wall - ttftRing - genRing - toolRing);
|
|
7258
|
+
const share = (ms) => wall > 0 ? ms / wall : 0;
|
|
7259
|
+
const countOf = (ms, times) => {
|
|
7260
|
+
const dur = fmtDuration(ms);
|
|
7261
|
+
if (times === void 0) return dur;
|
|
7262
|
+
return ms > 0 ? `${dur} · ${times}` : times;
|
|
7263
|
+
};
|
|
7264
|
+
const modelSegments = modelSlices.map((slice, index) => ({
|
|
7265
|
+
key: slice.key,
|
|
7266
|
+
color: slice.color,
|
|
7267
|
+
value: share(modelRings[index])
|
|
7268
|
+
}));
|
|
7269
|
+
const toolSlice = {
|
|
7270
|
+
key: "tools",
|
|
7271
|
+
color: COLOR.tools,
|
|
7272
|
+
label: t("timing.tools"),
|
|
7273
|
+
ms: timing.toolsMs,
|
|
7274
|
+
times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
|
|
7275
|
+
};
|
|
7276
|
+
const otherSlice = {
|
|
7277
|
+
key: "other",
|
|
7278
|
+
color: COLOR.other,
|
|
7279
|
+
label: t("timing.other"),
|
|
7280
|
+
ms: other
|
|
7281
|
+
};
|
|
7282
|
+
segments = [
|
|
7283
|
+
...modelSegments,
|
|
7284
|
+
{
|
|
7285
|
+
key: "tools",
|
|
7286
|
+
color: COLOR.tools,
|
|
7287
|
+
value: share(toolRing)
|
|
7288
|
+
},
|
|
7289
|
+
{
|
|
7290
|
+
key: "other",
|
|
7291
|
+
color: COLOR.other,
|
|
7292
|
+
value: share(other)
|
|
7293
|
+
}
|
|
7294
|
+
];
|
|
7295
|
+
const toRow = (slice) => ({
|
|
7296
|
+
key: slice.key,
|
|
7297
|
+
color: slice.color,
|
|
7298
|
+
label: slice.label,
|
|
7299
|
+
dim: slice.ms === 0,
|
|
7300
|
+
pct: fmtShare(slice.ms, wall),
|
|
7301
|
+
count: countOf(slice.ms, slice.times)
|
|
7302
|
+
});
|
|
7303
|
+
rows = [
|
|
7304
|
+
...modelSlices.map(toRow),
|
|
7305
|
+
toRow(toolSlice),
|
|
7306
|
+
toRow(otherSlice)
|
|
7307
|
+
].filter((row) => !row.dim);
|
|
7308
|
+
}
|
|
7309
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7310
|
+
className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
|
|
7311
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7312
|
+
className: "lc-card-title",
|
|
7313
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7314
|
+
className: "lc-card-title-text",
|
|
7315
|
+
children: t("timing.title")
|
|
7316
|
+
}), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7317
|
+
className: "lc-timing-tps",
|
|
7318
|
+
title: t("timing.tpsTip"),
|
|
7319
|
+
children: t("timing.tps", { tps })
|
|
7320
|
+
})]
|
|
7321
|
+
}), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7322
|
+
className: "lc-empty",
|
|
7323
|
+
children: t("timing.empty")
|
|
7324
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7325
|
+
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",
|
|
7326
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
7327
|
+
segments,
|
|
7328
|
+
size: 96,
|
|
7329
|
+
centerTop: wall > 0 ? fmtDuration(wall) : "—",
|
|
7330
|
+
centerSub: t("timing.total"),
|
|
7331
|
+
hoverKey,
|
|
7332
|
+
onHoverKey: setHoverKey
|
|
7333
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
|
|
7334
|
+
rows,
|
|
7335
|
+
hoverKey,
|
|
7336
|
+
onHoverKey: setHoverKey
|
|
7337
|
+
})]
|
|
7338
|
+
})]
|
|
7339
|
+
});
|
|
7340
|
+
};
|
|
7341
|
+
}
|
|
7342
|
+
//#endregion
|
|
7343
|
+
//#region src/client/components/overviewCard.tsx
|
|
7344
|
+
function makeOverviewCard(kit) {
|
|
7345
|
+
const { t } = kit;
|
|
7346
|
+
const Donut = makeDonut(kit);
|
|
7347
|
+
return function OverviewCard(props) {
|
|
7348
|
+
const { row } = props;
|
|
7349
|
+
const timeline = row.timeline;
|
|
7350
|
+
const billed = billedOf(timeline);
|
|
7351
|
+
const occupancy = timeline !== null && typeof timeline.contextWindow === "number" && timeline.contextWindow > 0 ? fmtShare(timeline.current.total, timeline.contextWindow) : null;
|
|
7352
|
+
const turns = turnsOf(timeline);
|
|
7353
|
+
const steps = timeline?.counts?.steps;
|
|
7354
|
+
const turnsLabel = steps === void 0 ? String(turns) : t("ov.card.turns", {
|
|
7355
|
+
n: turns,
|
|
7356
|
+
s: steps
|
|
7357
|
+
});
|
|
7358
|
+
const project = projectOf(row.cwd);
|
|
7359
|
+
const group = props.group !== void 0 && props.group !== project ? props.group : void 0;
|
|
7360
|
+
const created = createdDayOf(row.activity);
|
|
7361
|
+
const lastUser = timeline?.lastUser;
|
|
7362
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7363
|
+
type: "button",
|
|
7364
|
+
className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
|
|
7365
|
+
onClick: () => {
|
|
7366
|
+
props.onOpen(row.id);
|
|
7367
|
+
},
|
|
7368
|
+
children: [
|
|
7369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7370
|
+
className: "lc-ov-session-head",
|
|
7371
|
+
children: [
|
|
7372
|
+
row.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7373
|
+
className: "lc-ov-running",
|
|
7374
|
+
title: t("ov.running")
|
|
7375
|
+
}),
|
|
7376
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7377
|
+
className: "lc-ov-session-title",
|
|
7378
|
+
title: row.title,
|
|
7379
|
+
children: row.title
|
|
7380
|
+
}),
|
|
7381
|
+
row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7382
|
+
className: "lc-ov-current",
|
|
7383
|
+
children: t("ov.current")
|
|
7384
|
+
}),
|
|
7385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7386
|
+
className: "lc-ov-session-times",
|
|
7387
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7388
|
+
className: "lc-ov-session-time",
|
|
7389
|
+
children: relativeTime(t, row.updatedAt, props.now)
|
|
7390
|
+
}), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7391
|
+
className: "lc-ov-session-created",
|
|
7392
|
+
children: created
|
|
7393
|
+
})]
|
|
7394
|
+
})
|
|
7395
|
+
]
|
|
7396
|
+
}),
|
|
7397
|
+
(group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7398
|
+
className: "lc-ov-session-crumb",
|
|
7399
|
+
title: row.cwd,
|
|
7400
|
+
children: [
|
|
7401
|
+
group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7402
|
+
className: "lc-ov-crumb-group",
|
|
7403
|
+
children: group
|
|
7404
|
+
}),
|
|
7405
|
+
group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7406
|
+
className: "lc-ov-crumb-sep",
|
|
7407
|
+
children: "/"
|
|
7408
|
+
}),
|
|
7409
|
+
project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7410
|
+
className: "lc-ov-crumb-project",
|
|
7411
|
+
children: project
|
|
7412
|
+
})
|
|
7413
|
+
]
|
|
7414
|
+
}),
|
|
7415
|
+
timeline === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7416
|
+
className: "lc-ov-session-empty",
|
|
7417
|
+
children: t("ov.list.noData")
|
|
7418
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7419
|
+
className: "lc-ov-session-body",
|
|
7420
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
7421
|
+
size: 64,
|
|
7422
|
+
segments: partsOf(timeline.current),
|
|
7423
|
+
centerTop: fmt(timeline.current.total),
|
|
7424
|
+
centerSub: occupancy ?? void 0
|
|
7425
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7426
|
+
className: "lc-ov-mini-stats",
|
|
7427
|
+
children: [
|
|
7428
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7429
|
+
className: "lc-ov-mini-stat",
|
|
7430
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7431
|
+
className: "lc-ov-mini-label",
|
|
7432
|
+
children: t("stats.turns")
|
|
7433
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7434
|
+
className: "lc-ov-mini-value",
|
|
7435
|
+
children: turnsLabel
|
|
7436
|
+
})]
|
|
7437
|
+
}),
|
|
7438
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7439
|
+
className: "lc-ov-mini-stat",
|
|
7440
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7441
|
+
className: "lc-ov-mini-label",
|
|
7442
|
+
children: t("tokens.total")
|
|
7443
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7444
|
+
className: "lc-ov-mini-value",
|
|
7445
|
+
children: billed === null ? "—" : fmt(billed)
|
|
7446
|
+
})]
|
|
7447
|
+
}),
|
|
7448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7449
|
+
className: "lc-ov-mini-stat",
|
|
7450
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7451
|
+
className: "lc-ov-mini-label",
|
|
7452
|
+
children: t("stats.cost")
|
|
7453
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7454
|
+
className: "lc-ov-mini-value",
|
|
7455
|
+
children: props.costLabel
|
|
7456
|
+
})]
|
|
7457
|
+
})
|
|
7458
|
+
]
|
|
7459
|
+
})]
|
|
7460
|
+
}),
|
|
7461
|
+
lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7462
|
+
className: "lc-ov-session-preview",
|
|
7463
|
+
title: lastUser,
|
|
7464
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7465
|
+
className: "lc-ov-session-preview-label",
|
|
7466
|
+
children: t("ov.card.lastUser")
|
|
7467
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7468
|
+
className: "lc-ov-session-preview-text",
|
|
7469
|
+
children: lastUser
|
|
7470
|
+
})]
|
|
7471
|
+
})
|
|
7472
|
+
]
|
|
7473
|
+
});
|
|
7474
|
+
};
|
|
7475
|
+
}
|
|
7476
|
+
//#endregion
|
|
7477
|
+
//#region src/client/components/overviewPanel.tsx
|
|
7478
|
+
/**
|
|
7479
|
+
* The Context Dashboard panel — the cross-session insight surface opened
|
|
7480
|
+
* from the sidebar foot (overviewButton.tsx). Rendered from the frame-wide
|
|
7481
|
+
* `shell.overlay` slot behind the module store's flag; data rides the
|
|
7482
|
+
* root-scope `useSessions` standard kit (every list row's host-cached
|
|
7483
|
+
* projection values), so the panel draws every session's insight without
|
|
7484
|
+
* opening one log.
|
|
7485
|
+
*
|
|
7486
|
+
* The panel's first row is the KPI metrics band: the range's six figures
|
|
7487
|
+
* (sessions, billed tokens, cost, cache hit, tool calls, active time) in one
|
|
7488
|
+
* full-width line. Below it, the aggregate stats row folds the same range's
|
|
7489
|
+
* sessions into the two donut cards the per-session Context tab opens with —
|
|
7490
|
+
* Token Stats (the composition-split billed volume) and Timing Stats (the
|
|
7491
|
+
* summed totals). The body is then a 3:7 column pair: the insight column (the
|
|
7492
|
+
* activity heatmap, then the preferences entry row) beside the session column
|
|
7493
|
+
* (search, group chips, and the card grid); the heatmap keeps its own fixed
|
|
7494
|
+
* 8-week window and PINs the list to a picked day (the panel's drill-down
|
|
7495
|
+
* gesture). A session card click jumps to that session through the harness's
|
|
7496
|
+
* own selection verb (openSessionVia) and closes the panel.
|
|
7497
|
+
*/
|
|
7498
|
+
const RANGES = [
|
|
7499
|
+
"24h",
|
|
7500
|
+
"7d",
|
|
7501
|
+
"30d",
|
|
7502
|
+
"all"
|
|
7503
|
+
];
|
|
7504
|
+
const SORTS = [
|
|
7505
|
+
"recent",
|
|
7506
|
+
"tokens",
|
|
7507
|
+
"context"
|
|
7508
|
+
];
|
|
7509
|
+
/** The heatmap's depth metrics, in toggle order (steps is the default). */
|
|
7510
|
+
const METRICS = ["sessions", "steps"];
|
|
7511
|
+
function makeOverviewPanel(ctx, kit) {
|
|
7512
|
+
const { t, fmtDuration } = kit;
|
|
7513
|
+
const Heatmap = makeHeatmap(kit);
|
|
7514
|
+
const OverviewCard = makeOverviewCard(kit);
|
|
7515
|
+
const BalanceCapsule = makeBalanceCapsule(ctx, kit);
|
|
7516
|
+
const Donut = makeDonut(kit);
|
|
7517
|
+
const OverviewTokens = makeOverviewTokens(kit, Donut);
|
|
7518
|
+
const StatsTiming = makeStatsTiming(kit, Donut);
|
|
7519
|
+
const ErrorBoundary = makeErrorBoundary(t);
|
|
7520
|
+
/** The display currency follows the active locale (zh → CNY), read per render — the slot outlet re-renders on a locale switch. */
|
|
7521
|
+
function activeCurrency() {
|
|
7522
|
+
const locale = ctx.locale;
|
|
7523
|
+
return (typeof locale.getLocale === "function" ? locale.getLocale().active : "en") === "zh" ? "cny" : "usd";
|
|
7524
|
+
}
|
|
7525
|
+
function OverviewBody(props) {
|
|
7526
|
+
const open = (0, react.useSyncExternalStore)(overviewStore.subscribe, overviewStore.getSnapshot);
|
|
7527
|
+
const { prices } = useModelPrices();
|
|
7528
|
+
const snapshot = sessionsSnapshotOf(props);
|
|
7529
|
+
const wsSnapshot = workspacesSnapshotOf(props);
|
|
7530
|
+
const [range, setRange] = (0, react.useState)("30d");
|
|
7531
|
+
const [day, setDay] = (0, react.useState)(null);
|
|
7532
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
7533
|
+
const [group, setGroup] = (0, react.useState)(null);
|
|
7534
|
+
const [sort, setSort] = (0, react.useState)("recent");
|
|
7535
|
+
const [metric, setMetric] = (0, react.useState)("steps");
|
|
7536
|
+
const [page, setPage] = (0, react.useState)(0);
|
|
7537
|
+
const close = () => {
|
|
7538
|
+
overviewStore.set(false);
|
|
7539
|
+
};
|
|
7540
|
+
useEscapeClose(open, close);
|
|
7541
|
+
const rows = (0, react.useMemo)(() => rowsOfSnapshot(snapshot, wsSnapshot), [snapshot, wsSnapshot]);
|
|
7542
|
+
const groups = (0, react.useMemo)(() => sessionGroupsOf(wsSnapshot), [wsSnapshot]);
|
|
7543
|
+
(0, react.useEffect)(() => {
|
|
7544
|
+
if (open) {
|
|
7545
|
+
requestActivityBackfill();
|
|
7546
|
+
refreshSessions(ctx);
|
|
7547
|
+
}
|
|
7548
|
+
}, [open]);
|
|
7549
|
+
(0, react.useEffect)(() => {
|
|
7550
|
+
setPage(0);
|
|
7129
7551
|
}, [
|
|
7130
7552
|
range,
|
|
7131
7553
|
day,
|
|
@@ -7157,7 +7579,7 @@ window.__ModuleLoader__.load({
|
|
|
7157
7579
|
const kpi = kpisOf(ranged, allRows.length, prices, currency);
|
|
7158
7580
|
const days = aggregateDays(allRows);
|
|
7159
7581
|
const openOne = (id) => {
|
|
7160
|
-
|
|
7582
|
+
openSessionVia(ctx, id);
|
|
7161
7583
|
overviewStore.set(false);
|
|
7162
7584
|
};
|
|
7163
7585
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -7204,123 +7626,127 @@ window.__ModuleLoader__.load({
|
|
|
7204
7626
|
}), rows === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7205
7627
|
className: "lc-empty",
|
|
7206
7628
|
children: t("ov.unavailable")
|
|
7207
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
className: "lc-ov-left",
|
|
7629
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7630
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7631
|
+
className: "lc-ov-kpis",
|
|
7211
7632
|
children: [
|
|
7212
7633
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7213
|
-
className: "lc-ov-
|
|
7634
|
+
className: "lc-stat lc-ov-kpi",
|
|
7214
7635
|
children: [
|
|
7215
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7216
|
-
className: "lc-stat
|
|
7217
|
-
children:
|
|
7218
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7219
|
-
className: "lc-stat-label",
|
|
7220
|
-
children: t("ov.kpi.sessions")
|
|
7221
|
-
}),
|
|
7222
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7223
|
-
className: "lc-stat-value",
|
|
7224
|
-
children: kpi.sessions
|
|
7225
|
-
}),
|
|
7226
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7227
|
-
className: "lc-stat-sub",
|
|
7228
|
-
children: t("ov.kpi.ofTotal", { n: kpi.listed })
|
|
7229
|
-
})
|
|
7230
|
-
]
|
|
7636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7637
|
+
className: "lc-stat-label",
|
|
7638
|
+
children: t("ov.kpi.sessions")
|
|
7231
7639
|
}),
|
|
7232
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7233
|
-
className: "lc-stat
|
|
7234
|
-
children:
|
|
7235
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7236
|
-
className: "lc-stat-label",
|
|
7237
|
-
children: t("ov.kpi.tokens")
|
|
7238
|
-
}),
|
|
7239
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7240
|
-
className: "lc-stat-value",
|
|
7241
|
-
children: fmt(kpi.tokens)
|
|
7242
|
-
}),
|
|
7243
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7244
|
-
className: "lc-stat-sub",
|
|
7245
|
-
children: [
|
|
7246
|
-
t("stats.turns"),
|
|
7247
|
-
" ",
|
|
7248
|
-
fmt(kpi.turns)
|
|
7249
|
-
]
|
|
7250
|
-
})
|
|
7251
|
-
]
|
|
7640
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7641
|
+
className: "lc-stat-value",
|
|
7642
|
+
children: kpi.sessions
|
|
7252
7643
|
}),
|
|
7253
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7254
|
-
className: "lc-stat
|
|
7255
|
-
children:
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
className: "lc-stat-sub",
|
|
7266
|
-
children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
|
|
7267
|
-
})
|
|
7268
|
-
]
|
|
7644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7645
|
+
className: "lc-stat-sub",
|
|
7646
|
+
children: t("ov.kpi.ofTotal", { n: kpi.listed })
|
|
7647
|
+
})
|
|
7648
|
+
]
|
|
7649
|
+
}),
|
|
7650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7651
|
+
className: "lc-stat lc-ov-kpi",
|
|
7652
|
+
children: [
|
|
7653
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7654
|
+
className: "lc-stat-label",
|
|
7655
|
+
children: t("ov.kpi.tokens")
|
|
7269
7656
|
}),
|
|
7270
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7271
|
-
className: "lc-stat
|
|
7272
|
-
children:
|
|
7273
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7274
|
-
className: "lc-stat-label",
|
|
7275
|
-
children: t("stats.cacheHit")
|
|
7276
|
-
}),
|
|
7277
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7278
|
-
className: "lc-stat-value",
|
|
7279
|
-
children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
|
|
7280
|
-
}),
|
|
7281
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7282
|
-
className: "lc-stat-sub",
|
|
7283
|
-
children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
|
|
7284
|
-
})
|
|
7285
|
-
]
|
|
7657
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7658
|
+
className: "lc-stat-value",
|
|
7659
|
+
children: fmt(kpi.tokens)
|
|
7286
7660
|
}),
|
|
7287
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
7288
|
-
className: "lc-stat
|
|
7661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7662
|
+
className: "lc-stat-sub",
|
|
7289
7663
|
children: [
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
}),
|
|
7294
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7295
|
-
className: "lc-stat-value",
|
|
7296
|
-
children: fmt(kpi.toolCalls)
|
|
7297
|
-
}),
|
|
7298
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7299
|
-
className: "lc-stat-sub",
|
|
7300
|
-
children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
|
|
7301
|
-
})
|
|
7664
|
+
t("stats.turns"),
|
|
7665
|
+
" ",
|
|
7666
|
+
fmt(kpi.turns)
|
|
7302
7667
|
]
|
|
7668
|
+
})
|
|
7669
|
+
]
|
|
7670
|
+
}),
|
|
7671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7672
|
+
className: "lc-stat lc-ov-kpi",
|
|
7673
|
+
children: [
|
|
7674
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7675
|
+
className: "lc-stat-label",
|
|
7676
|
+
children: t("stats.cost")
|
|
7303
7677
|
}),
|
|
7304
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7305
|
-
className: "lc-stat
|
|
7306
|
-
children:
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7678
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7679
|
+
className: "lc-stat-value",
|
|
7680
|
+
children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
|
|
7681
|
+
}),
|
|
7682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7683
|
+
className: "lc-stat-sub",
|
|
7684
|
+
children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
|
|
7685
|
+
})
|
|
7686
|
+
]
|
|
7687
|
+
}),
|
|
7688
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7689
|
+
className: "lc-stat lc-ov-kpi",
|
|
7690
|
+
children: [
|
|
7691
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7692
|
+
className: "lc-stat-label",
|
|
7693
|
+
children: t("stats.cacheHit")
|
|
7694
|
+
}),
|
|
7695
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7696
|
+
className: "lc-stat-value",
|
|
7697
|
+
children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
|
|
7698
|
+
}),
|
|
7699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7700
|
+
className: "lc-stat-sub",
|
|
7701
|
+
children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
|
|
7702
|
+
})
|
|
7703
|
+
]
|
|
7704
|
+
}),
|
|
7705
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7706
|
+
className: "lc-stat lc-ov-kpi",
|
|
7707
|
+
children: [
|
|
7708
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7709
|
+
className: "lc-stat-label",
|
|
7710
|
+
children: t("stats.toolCalls")
|
|
7711
|
+
}),
|
|
7712
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7713
|
+
className: "lc-stat-value",
|
|
7714
|
+
children: fmt(kpi.toolCalls)
|
|
7715
|
+
}),
|
|
7716
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7717
|
+
className: "lc-stat-sub",
|
|
7718
|
+
children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
|
|
7320
7719
|
})
|
|
7321
7720
|
]
|
|
7322
7721
|
}),
|
|
7323
7722
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7723
|
+
className: "lc-stat lc-ov-kpi",
|
|
7724
|
+
children: [
|
|
7725
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7726
|
+
className: "lc-stat-label",
|
|
7727
|
+
children: t("timing.total")
|
|
7728
|
+
}),
|
|
7729
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7730
|
+
className: "lc-stat-value",
|
|
7731
|
+
children: fmtDuration(kpi.wallMs)
|
|
7732
|
+
}),
|
|
7733
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7734
|
+
className: "lc-stat-sub",
|
|
7735
|
+
children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
|
|
7736
|
+
})
|
|
7737
|
+
]
|
|
7738
|
+
})
|
|
7739
|
+
]
|
|
7740
|
+
}),
|
|
7741
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7742
|
+
className: "lc-ov-stats",
|
|
7743
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewTokens, { tokens: kpi.tokenParts }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: kpi.timing })]
|
|
7744
|
+
}),
|
|
7745
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7746
|
+
className: "lc-ov-body",
|
|
7747
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7748
|
+
className: "lc-ov-left",
|
|
7749
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7324
7750
|
className: "lc-card lc-ov-heat-card",
|
|
7325
7751
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7326
7752
|
className: "lc-card-title",
|
|
@@ -7353,8 +7779,7 @@ window.__ModuleLoader__.load({
|
|
|
7353
7779
|
onSelect: setDay,
|
|
7354
7780
|
today: todayKey()
|
|
7355
7781
|
})]
|
|
7356
|
-
}),
|
|
7357
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7782
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7358
7783
|
type: "button",
|
|
7359
7784
|
className: "lc-ov-settings",
|
|
7360
7785
|
onClick: () => {
|
|
@@ -7368,131 +7793,131 @@ window.__ModuleLoader__.load({
|
|
|
7368
7793
|
className: "lc-ov-settings-hint",
|
|
7369
7794
|
children: t("plugin.settingsOpen")
|
|
7370
7795
|
})]
|
|
7371
|
-
})
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7388
|
-
type: "button",
|
|
7389
|
-
className: "lc-ov-day-chip",
|
|
7390
|
-
title: t("ov.list.dayClear"),
|
|
7391
|
-
onClick: () => {
|
|
7392
|
-
setDay(null);
|
|
7393
|
-
},
|
|
7394
|
-
children: [t("ov.list.dayFilter", { day }), " ×"]
|
|
7395
|
-
}),
|
|
7396
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7397
|
-
className: "lc-ov-search",
|
|
7398
|
-
type: "search",
|
|
7399
|
-
value: query,
|
|
7400
|
-
placeholder: t("ov.list.search"),
|
|
7401
|
-
"aria-label": t("ov.list.search"),
|
|
7402
|
-
onChange: (ev) => {
|
|
7403
|
-
setQuery(ev.target.value);
|
|
7404
|
-
}
|
|
7405
|
-
}),
|
|
7406
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7407
|
-
className: "lc-gran",
|
|
7408
|
-
role: "group",
|
|
7409
|
-
"aria-label": t("ov.list.sortLabel"),
|
|
7410
|
-
children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7796
|
+
})]
|
|
7797
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7798
|
+
className: "lc-ov-right",
|
|
7799
|
+
children: [
|
|
7800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7801
|
+
className: "lc-ov-list-head",
|
|
7802
|
+
children: [
|
|
7803
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7804
|
+
className: "lc-ov-list-title",
|
|
7805
|
+
children: t("ov.list.title")
|
|
7806
|
+
}),
|
|
7807
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7808
|
+
className: "lc-ov-list-count",
|
|
7809
|
+
children: visible.length
|
|
7810
|
+
}),
|
|
7811
|
+
day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7411
7812
|
type: "button",
|
|
7412
|
-
className: "lc-
|
|
7813
|
+
className: "lc-ov-day-chip",
|
|
7814
|
+
title: t("ov.list.dayClear"),
|
|
7413
7815
|
onClick: () => {
|
|
7414
|
-
|
|
7816
|
+
setDay(null);
|
|
7415
7817
|
},
|
|
7416
|
-
children: t("ov.list.
|
|
7417
|
-
},
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
className: "lc-empty",
|
|
7449
|
-
children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
|
|
7450
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7451
|
-
className: "lc-ov-grid",
|
|
7452
|
-
children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
|
|
7453
|
-
row,
|
|
7454
|
-
...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
|
|
7455
|
-
costLabel: cardCostOf(row, prices, currency),
|
|
7456
|
-
now,
|
|
7457
|
-
onOpen: openOne
|
|
7458
|
-
}, row.id))
|
|
7459
|
-
}), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7460
|
-
className: "lc-ov-pager",
|
|
7461
|
-
role: "navigation",
|
|
7462
|
-
"aria-label": t("ov.list.pager"),
|
|
7463
|
-
children: [
|
|
7464
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7818
|
+
children: [t("ov.list.dayFilter", { day }), " ×"]
|
|
7819
|
+
}),
|
|
7820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7821
|
+
className: "lc-ov-search",
|
|
7822
|
+
type: "search",
|
|
7823
|
+
value: query,
|
|
7824
|
+
placeholder: t("ov.list.search"),
|
|
7825
|
+
"aria-label": t("ov.list.search"),
|
|
7826
|
+
onChange: (ev) => {
|
|
7827
|
+
setQuery(ev.target.value);
|
|
7828
|
+
}
|
|
7829
|
+
}),
|
|
7830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7831
|
+
className: "lc-gran",
|
|
7832
|
+
role: "group",
|
|
7833
|
+
"aria-label": t("ov.list.sortLabel"),
|
|
7834
|
+
children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7835
|
+
type: "button",
|
|
7836
|
+
className: "lc-gran-btn" + (sort === s ? " lc-gran-on" : ""),
|
|
7837
|
+
onClick: () => {
|
|
7838
|
+
setSort(s);
|
|
7839
|
+
},
|
|
7840
|
+
children: t("ov.list.sort." + s)
|
|
7841
|
+
}, s))
|
|
7842
|
+
})
|
|
7843
|
+
]
|
|
7844
|
+
}),
|
|
7845
|
+
chips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7846
|
+
className: "lc-ov-groups",
|
|
7847
|
+
role: "group",
|
|
7848
|
+
"aria-label": t("ov.group.label"),
|
|
7849
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7465
7850
|
type: "button",
|
|
7466
|
-
className: "lc-ov-
|
|
7467
|
-
disabled: paged.index === 0,
|
|
7468
|
-
"aria-label": t("ov.list.prev"),
|
|
7851
|
+
className: "lc-ov-chip" + (group === null ? " lc-ov-chip-on" : ""),
|
|
7469
7852
|
onClick: () => {
|
|
7470
|
-
|
|
7853
|
+
setGroup(null);
|
|
7471
7854
|
},
|
|
7472
|
-
children: "
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7855
|
+
children: [t("ov.range.all"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7856
|
+
className: "lc-ov-chip-n",
|
|
7857
|
+
children: scoped.length
|
|
7858
|
+
})]
|
|
7859
|
+
}), chips.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7860
|
+
type: "button",
|
|
7861
|
+
className: "lc-ov-chip" + (group === c.key ? " lc-ov-chip-on" : ""),
|
|
7862
|
+
onClick: () => {
|
|
7863
|
+
setGroup(group === c.key ? null : c.key);
|
|
7864
|
+
},
|
|
7865
|
+
children: [c.key === "__ungrouped__" ? t("ov.group.ungrouped") : c.key, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7866
|
+
className: "lc-ov-chip-n",
|
|
7867
|
+
children: c.count
|
|
7868
|
+
})]
|
|
7869
|
+
}, c.key))]
|
|
7870
|
+
}),
|
|
7871
|
+
visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7872
|
+
className: "lc-empty",
|
|
7873
|
+
children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
|
|
7874
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7875
|
+
className: "lc-ov-grid",
|
|
7876
|
+
children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
|
|
7877
|
+
row,
|
|
7878
|
+
...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
|
|
7879
|
+
costLabel: cardCostOf(row, prices, currency),
|
|
7880
|
+
now,
|
|
7881
|
+
onOpen: openOne
|
|
7882
|
+
}, row.id))
|
|
7883
|
+
}), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7884
|
+
className: "lc-ov-pager",
|
|
7885
|
+
role: "navigation",
|
|
7886
|
+
"aria-label": t("ov.list.pager"),
|
|
7887
|
+
children: [
|
|
7888
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7889
|
+
type: "button",
|
|
7890
|
+
className: "lc-ov-pager-btn",
|
|
7891
|
+
disabled: paged.index === 0,
|
|
7892
|
+
"aria-label": t("ov.list.prev"),
|
|
7893
|
+
onClick: () => {
|
|
7894
|
+
setPage(paged.index - 1);
|
|
7895
|
+
},
|
|
7896
|
+
children: "‹"
|
|
7897
|
+
}),
|
|
7898
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7899
|
+
className: "lc-ov-pager-n",
|
|
7900
|
+
children: t("ov.list.page", {
|
|
7901
|
+
n: paged.index + 1,
|
|
7902
|
+
total: paged.count
|
|
7903
|
+
})
|
|
7904
|
+
}),
|
|
7905
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7906
|
+
type: "button",
|
|
7907
|
+
className: "lc-ov-pager-btn",
|
|
7908
|
+
disabled: paged.index === paged.count - 1,
|
|
7909
|
+
"aria-label": t("ov.list.next"),
|
|
7910
|
+
onClick: () => {
|
|
7911
|
+
setPage(paged.index + 1);
|
|
7912
|
+
},
|
|
7913
|
+
children: "›"
|
|
7479
7914
|
})
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
onClick: () => {
|
|
7487
|
-
setPage(paged.index + 1);
|
|
7488
|
-
},
|
|
7489
|
-
children: "›"
|
|
7490
|
-
})
|
|
7491
|
-
]
|
|
7492
|
-
})] })
|
|
7493
|
-
]
|
|
7494
|
-
})]
|
|
7495
|
-
})]
|
|
7915
|
+
]
|
|
7916
|
+
})] })
|
|
7917
|
+
]
|
|
7918
|
+
})]
|
|
7919
|
+
})
|
|
7920
|
+
] })]
|
|
7496
7921
|
})
|
|
7497
7922
|
});
|
|
7498
7923
|
}
|
|
@@ -9077,13 +9502,6 @@ window.__ModuleLoader__.load({
|
|
|
9077
9502
|
});
|
|
9078
9503
|
return segs;
|
|
9079
9504
|
}
|
|
9080
|
-
/** Session-switch navigation, fail-soft: a stale row (list rebuilt between snapshot and click) loses its open() race and is ignored. */
|
|
9081
|
-
function openAgentSession(face, id) {
|
|
9082
|
-
if (face === null || typeof face.open !== "function") return;
|
|
9083
|
-
try {
|
|
9084
|
-
face.open(id);
|
|
9085
|
-
} catch {}
|
|
9086
|
-
}
|
|
9087
9505
|
/** Narrow `ctx.get('sessions')` to the card's face (null = harness without the outward sessions service). */
|
|
9088
9506
|
function sessionsFaceOf(ctx) {
|
|
9089
9507
|
const rec = asRecord(ctx.get("sessions"));
|
|
@@ -9207,7 +9625,7 @@ window.__ModuleLoader__.load({
|
|
|
9207
9625
|
for (const n of forest.nodes) totalTokens += n.head !== null ? n.head.tokens : 0;
|
|
9208
9626
|
const open = (id) => {
|
|
9209
9627
|
if (id === current.id) return;
|
|
9210
|
-
|
|
9628
|
+
openSessionVia(ctx, id);
|
|
9211
9629
|
};
|
|
9212
9630
|
const keyOpen = (id) => (ev) => {
|
|
9213
9631
|
if (ev.key !== "Enter" && ev.key !== " ") return;
|
|
@@ -9986,7 +10404,7 @@ window.__ModuleLoader__.load({
|
|
|
9986
10404
|
return function PluginInfo() {
|
|
9987
10405
|
const [latest, setLatest] = (0, react.useState)(null);
|
|
9988
10406
|
(0, react.useEffect)(() => {
|
|
9989
|
-
if ("0.
|
|
10407
|
+
if ("0.57.0".includes("-dev")) return;
|
|
9990
10408
|
let on = true;
|
|
9991
10409
|
fetchLatestVersion().then((v) => {
|
|
9992
10410
|
if (on && v) setLatest(v);
|
|
@@ -9995,8 +10413,8 @@ window.__ModuleLoader__.load({
|
|
|
9995
10413
|
on = false;
|
|
9996
10414
|
};
|
|
9997
10415
|
}, []);
|
|
9998
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
9999
|
-
const nameText = "dsh-context (v0.
|
|
10416
|
+
const update = latest !== null && isNewerVersion(latest, "0.57.0") ? latest : null;
|
|
10417
|
+
const nameText = "dsh-context (v0.57.0)";
|
|
10000
10418
|
const nameValue = [nameText];
|
|
10001
10419
|
if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10002
10420
|
className: "lc-pi-update",
|
|
@@ -10652,245 +11070,6 @@ window.__ModuleLoader__.load({
|
|
|
10652
11070
|
};
|
|
10653
11071
|
}
|
|
10654
11072
|
//#endregion
|
|
10655
|
-
//#region src/client/components/sliceList.tsx
|
|
10656
|
-
/**
|
|
10657
|
-
* The stats cards' slice rows: the donut's legend with numbers, two tiers
|
|
10658
|
-
* per slice — the primary line pairs the color dot and the name with the
|
|
10659
|
-
* bold share closing a fixed right column, and the secondary line drops the
|
|
10660
|
-
* absolute quantity (plus a qualifier like the call count) under the name in
|
|
10661
|
-
* muted small print. Hovering a row lights it and its donut segment together
|
|
10662
|
-
* (the shared hover key), and vice versa. No tracks or bars — the donut IS
|
|
10663
|
-
* the proportion chart; the rows are its legend. Preformatted strings in,
|
|
10664
|
-
* dumb markup out.
|
|
10665
|
-
*/
|
|
10666
|
-
function makeSliceList(kit) {
|
|
10667
|
-
return function SliceList(props) {
|
|
10668
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10669
|
-
className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
|
|
10670
|
-
onMouseLeave: () => {
|
|
10671
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
10672
|
-
},
|
|
10673
|
-
children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10674
|
-
className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
|
|
10675
|
-
onMouseEnter: () => {
|
|
10676
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
|
|
10677
|
-
},
|
|
10678
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10679
|
-
className: "lc-sl-main",
|
|
10680
|
-
children: [
|
|
10681
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
10682
|
-
className: "lc-sl-dot",
|
|
10683
|
-
style: { background: r.color }
|
|
10684
|
-
}),
|
|
10685
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10686
|
-
className: "lc-sl-label",
|
|
10687
|
-
title: r.label,
|
|
10688
|
-
children: r.label
|
|
10689
|
-
}),
|
|
10690
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10691
|
-
className: "lc-sl-pct",
|
|
10692
|
-
children: r.pct
|
|
10693
|
-
})
|
|
10694
|
-
]
|
|
10695
|
-
}), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10696
|
-
className: "lc-sl-sub",
|
|
10697
|
-
title: r.count,
|
|
10698
|
-
children: r.count
|
|
10699
|
-
}) : null]
|
|
10700
|
-
}, r.key))
|
|
10701
|
-
});
|
|
10702
|
-
};
|
|
10703
|
-
}
|
|
10704
|
-
//#endregion
|
|
10705
|
-
//#region src/client/components/statsTiming.tsx
|
|
10706
|
-
/**
|
|
10707
|
-
* The Timing card: where the session's ACTIVE time went. The donut splits
|
|
10708
|
-
* whole-step wall time into the model-call slices — TTFT (step start → first
|
|
10709
|
-
* token, the wait) and LLM generation (first token → assistant message) —
|
|
10710
|
-
* and the tool-execution slice, with the residue as overhead; a call whose
|
|
10711
|
-
* stream recorded no token delta (legacy or aborted) stays unattributed and
|
|
10712
|
-
* lands in the residue. When the host folded the generation split (see
|
|
10713
|
-
* TimingTotals), the generation slice expands into what was being decoded:
|
|
10714
|
-
* thinking, answer text, and tool-call arguments. The slice rows lead with
|
|
10715
|
-
* the true duration and qualify it with a tally on the secondary line — the
|
|
10716
|
-
* model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
|
|
10717
|
-
* (zero to many per call, so a call count would be a false tally there). A
|
|
10718
|
-
* slice that never happened (zero time) is omitted entirely — the ring already skips
|
|
10719
|
-
* its arc, and a zero row would otherwise borrow the session's call count and
|
|
10720
|
-
* read as "no time, many calls". The donut and the rows sit side by side so
|
|
10721
|
-
* the head row stays half-height. Parallel tool calls each count, so the tools
|
|
10722
|
-
* figure can overlap — the ring clamps it into the post-model window while the
|
|
10723
|
-
* row numbers stay true.
|
|
10724
|
-
*/
|
|
10725
|
-
const COLOR = {
|
|
10726
|
-
ttft: "var(--color-blue-500)",
|
|
10727
|
-
reasoning: "var(--color-violet-500)",
|
|
10728
|
-
text: "var(--color-pink-500)",
|
|
10729
|
-
toolarg: "var(--color-amber-500)",
|
|
10730
|
-
tools: "var(--color-teal-500)",
|
|
10731
|
-
other: "var(--color-slate-400)"
|
|
10732
|
-
};
|
|
10733
|
-
/**
|
|
10734
|
-
* The decode-throughput figure, formatted exactly as the harness's own
|
|
10735
|
-
* session-stats seat formats it: whole tokens from ten up, one decimal below.
|
|
10736
|
-
*/
|
|
10737
|
-
function formatTokensPerSecond(tps) {
|
|
10738
|
-
const clamped = Math.max(0, tps);
|
|
10739
|
-
return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
|
|
10740
|
-
}
|
|
10741
|
-
function makeStatsTiming(kit, Donut) {
|
|
10742
|
-
const { t, fmt, fmtDuration, fmtShare } = kit;
|
|
10743
|
-
const SliceList = makeSliceList(kit);
|
|
10744
|
-
return function StatsTiming(props) {
|
|
10745
|
-
const [hoverKey, setHoverKey] = (0, react.useState)(null);
|
|
10746
|
-
const timing = props.timing;
|
|
10747
|
-
const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
|
|
10748
|
-
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;
|
|
10749
|
-
let segments = [];
|
|
10750
|
-
let rows = [];
|
|
10751
|
-
if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
|
|
10752
|
-
const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
|
|
10753
|
-
const modelSlices = [{
|
|
10754
|
-
key: "ttft",
|
|
10755
|
-
color: COLOR.ttft,
|
|
10756
|
-
label: t("timing.ttft"),
|
|
10757
|
-
ms: timing.ttftMs,
|
|
10758
|
-
times: callTimes
|
|
10759
|
-
}];
|
|
10760
|
-
const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
|
|
10761
|
-
const buckets = [
|
|
10762
|
-
[
|
|
10763
|
-
"reasoning",
|
|
10764
|
-
COLOR.reasoning,
|
|
10765
|
-
t("timing.reasoning"),
|
|
10766
|
-
timing.reasoningMs ?? 0,
|
|
10767
|
-
blockTimes(timing.reasoningBlocks)
|
|
10768
|
-
],
|
|
10769
|
-
[
|
|
10770
|
-
"text",
|
|
10771
|
-
COLOR.text,
|
|
10772
|
-
t("timing.text"),
|
|
10773
|
-
timing.textMs ?? 0,
|
|
10774
|
-
blockTimes(timing.textBlocks)
|
|
10775
|
-
],
|
|
10776
|
-
[
|
|
10777
|
-
"toolarg",
|
|
10778
|
-
COLOR.toolarg,
|
|
10779
|
-
t("timing.toolArgs"),
|
|
10780
|
-
timing.toolArgMs ?? 0,
|
|
10781
|
-
blockTimes(timing.toolArgBlocks)
|
|
10782
|
-
]
|
|
10783
|
-
];
|
|
10784
|
-
if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
|
|
10785
|
-
key,
|
|
10786
|
-
color,
|
|
10787
|
-
label,
|
|
10788
|
-
ms,
|
|
10789
|
-
...times !== void 0 ? { times } : {}
|
|
10790
|
-
});
|
|
10791
|
-
else modelSlices.push({
|
|
10792
|
-
key: "gen",
|
|
10793
|
-
color: COLOR.reasoning,
|
|
10794
|
-
label: t("timing.gen"),
|
|
10795
|
-
ms: timing.genMs
|
|
10796
|
-
});
|
|
10797
|
-
const ttftRing = Math.min(timing.ttftMs, wall);
|
|
10798
|
-
const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
|
|
10799
|
-
const modelRings = [ttftRing];
|
|
10800
|
-
let used = ttftRing;
|
|
10801
|
-
const genEnd = ttftRing + genRing;
|
|
10802
|
-
for (const slice of modelSlices.slice(1)) {
|
|
10803
|
-
const take = Math.max(0, Math.min(slice.ms, genEnd - used));
|
|
10804
|
-
modelRings.push(take);
|
|
10805
|
-
used += take;
|
|
10806
|
-
}
|
|
10807
|
-
const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
|
|
10808
|
-
const other = Math.max(0, wall - ttftRing - genRing - toolRing);
|
|
10809
|
-
const share = (ms) => wall > 0 ? ms / wall : 0;
|
|
10810
|
-
const countOf = (ms, times) => {
|
|
10811
|
-
const dur = fmtDuration(ms);
|
|
10812
|
-
if (times === void 0) return dur;
|
|
10813
|
-
return ms > 0 ? `${dur} · ${times}` : times;
|
|
10814
|
-
};
|
|
10815
|
-
const modelSegments = modelSlices.map((slice, index) => ({
|
|
10816
|
-
key: slice.key,
|
|
10817
|
-
color: slice.color,
|
|
10818
|
-
value: share(modelRings[index])
|
|
10819
|
-
}));
|
|
10820
|
-
const toolSlice = {
|
|
10821
|
-
key: "tools",
|
|
10822
|
-
color: COLOR.tools,
|
|
10823
|
-
label: t("timing.tools"),
|
|
10824
|
-
ms: timing.toolsMs,
|
|
10825
|
-
times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
|
|
10826
|
-
};
|
|
10827
|
-
const otherSlice = {
|
|
10828
|
-
key: "other",
|
|
10829
|
-
color: COLOR.other,
|
|
10830
|
-
label: t("timing.other"),
|
|
10831
|
-
ms: other
|
|
10832
|
-
};
|
|
10833
|
-
segments = [
|
|
10834
|
-
...modelSegments,
|
|
10835
|
-
{
|
|
10836
|
-
key: "tools",
|
|
10837
|
-
color: COLOR.tools,
|
|
10838
|
-
value: share(toolRing)
|
|
10839
|
-
},
|
|
10840
|
-
{
|
|
10841
|
-
key: "other",
|
|
10842
|
-
color: COLOR.other,
|
|
10843
|
-
value: share(other)
|
|
10844
|
-
}
|
|
10845
|
-
];
|
|
10846
|
-
const toRow = (slice) => ({
|
|
10847
|
-
key: slice.key,
|
|
10848
|
-
color: slice.color,
|
|
10849
|
-
label: slice.label,
|
|
10850
|
-
dim: slice.ms === 0,
|
|
10851
|
-
pct: fmtShare(slice.ms, wall),
|
|
10852
|
-
count: countOf(slice.ms, slice.times)
|
|
10853
|
-
});
|
|
10854
|
-
rows = [
|
|
10855
|
-
...modelSlices.map(toRow),
|
|
10856
|
-
toRow(toolSlice),
|
|
10857
|
-
toRow(otherSlice)
|
|
10858
|
-
].filter((row) => !row.dim);
|
|
10859
|
-
}
|
|
10860
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10861
|
-
className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
|
|
10862
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10863
|
-
className: "lc-card-title",
|
|
10864
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10865
|
-
className: "lc-card-title-text",
|
|
10866
|
-
children: t("timing.title")
|
|
10867
|
-
}), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10868
|
-
className: "lc-timing-tps",
|
|
10869
|
-
title: t("timing.tpsTip"),
|
|
10870
|
-
children: t("timing.tps", { tps })
|
|
10871
|
-
})]
|
|
10872
|
-
}), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10873
|
-
className: "lc-empty",
|
|
10874
|
-
children: t("timing.empty")
|
|
10875
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10876
|
-
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",
|
|
10877
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
10878
|
-
segments,
|
|
10879
|
-
size: 96,
|
|
10880
|
-
centerTop: wall > 0 ? fmtDuration(wall) : "—",
|
|
10881
|
-
centerSub: t("timing.total"),
|
|
10882
|
-
hoverKey,
|
|
10883
|
-
onHoverKey: setHoverKey
|
|
10884
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
|
|
10885
|
-
rows,
|
|
10886
|
-
hoverKey,
|
|
10887
|
-
onHoverKey: setHoverKey
|
|
10888
|
-
})]
|
|
10889
|
-
})]
|
|
10890
|
-
});
|
|
10891
|
-
};
|
|
10892
|
-
}
|
|
10893
|
-
//#endregion
|
|
10894
11073
|
//#region src/client/components/statsTokens.tsx
|
|
10895
11074
|
/**
|
|
10896
11075
|
* The Token card: the session's whole billed token usage — the SAME total
|
|
@@ -11941,7 +12120,7 @@ window.__ModuleLoader__.load({
|
|
|
11941
12120
|
}
|
|
11942
12121
|
//#endregion
|
|
11943
12122
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
|
|
11944
|
-
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}";
|
|
12123
|
+
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}";
|
|
11945
12124
|
const tagId = "dsh-context/overview.css";
|
|
11946
12125
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
11947
12126
|
const tag = document.createElement("style");
|