dsh-context 0.9.0 → 0.10.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/README.md +19 -8
- package/lib/client.js +606 -428
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-context)
|
|
6
6
|
[](https://github.com/bowenliang123/dsh-context)
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**A DeepSeek Harness plugin for context dashboard and context command, for understanding how the context is made of, and how it evolves.**
|
|
9
9
|
|
|
10
|
-
`dsh-context` is a [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) plugin
|
|
10
|
+
`dsh-context` is a [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) plugin for context insight.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- **The Context tab** — a full insight panel for context composition, per-turn context history, context compactions, and the message surface, etc.
|
|
13
|
+
- **The `/context` command** — the same headline and recent trend as a centered dialog straight from the composer (the screenshot above).
|
|
13
14
|
|
|
14
15
|
## Install
|
|
15
16
|
|
|
@@ -19,7 +20,21 @@ One command, from any DeepSeek Harness installation:
|
|
|
19
20
|
dsh plugin --profile web add dsh-context
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
Then start the web UI with `dsh web
|
|
23
|
+
Then start the web UI with `dsh web`. No build step, no restart.
|
|
24
|
+
|
|
25
|
+
## Use it
|
|
26
|
+
|
|
27
|
+
### Context tab
|
|
28
|
+
|
|
29
|
+
Open any session and click the **Context / 上下文** tab:
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
### ⌨️ `/context` command — In-session Context Insight modal
|
|
34
|
+
|
|
35
|
+
Type `/context` (or pick it from the `/` menu) and press Enter: a centered dialog shows the provider-anchored occupancy headline, the six-category composition bar, and the last-10-turn trend chart — hover or click a bar for its full breakdown, exactly like the tab.
|
|
36
|
+
|
|
37
|
+

|
|
23
38
|
|
|
24
39
|
## What you'll see
|
|
25
40
|
|
|
@@ -53,10 +68,6 @@ Every compaction, tool-output prune, skill or plugin context injection, and mode
|
|
|
53
68
|
|
|
54
69
|
The exact message list the model sees right now, newest first, with a per-message token cost.
|
|
55
70
|
|
|
56
|
-
## Releasing
|
|
57
|
-
|
|
58
|
-
Releases are cut by tagging: `git tag vX.Y.Z && gh release create vX.Y.Z`. A [GitHub Actions workflow](.github/workflows/release.yml) then builds, tests, and publishes the package to npm automatically via [npm Trusted Publishing (OIDC)](https://docs.npmjs.com/trusted-publishers) — no long-lived token needed, provenance included.
|
|
59
|
-
|
|
60
71
|
## Like it?
|
|
61
72
|
|
|
62
73
|
If `dsh-context` helped you understand what your agent is carrying around, a ⭐ on [GitHub](https://github.com/bowenliang123/dsh-context) is much appreciated — and issues/PRs are welcome!
|
package/lib/client.js
CHANGED
|
@@ -76,7 +76,9 @@ var DICT_ZH = {
|
|
|
76
76
|
"node.calls": "\u8C03\u7528 ",
|
|
77
77
|
"node.empty": "(\u7A7A\u56DE\u590D)",
|
|
78
78
|
"node.nonText": "(\u975E\u6587\u672C\u6D88\u606F)",
|
|
79
|
-
"node.snapshot": "\u5FEB\u7167: "
|
|
79
|
+
"node.snapshot": "\u5FEB\u7167: ",
|
|
80
|
+
"cmd.desc": "\u67E5\u770B\u4E0A\u4E0B\u6587\u7684\u6784\u6210\u548C\u53D8\u5316",
|
|
81
|
+
"cmd.close": "\u5173\u95ED"
|
|
80
82
|
};
|
|
81
83
|
var DICT_EN = {
|
|
82
84
|
"tab": "Context",
|
|
@@ -147,9 +149,502 @@ var DICT_EN = {
|
|
|
147
149
|
"node.calls": "calls ",
|
|
148
150
|
"node.empty": "(empty reply)",
|
|
149
151
|
"node.nonText": "(non-text message)",
|
|
150
|
-
"node.snapshot": "snapshot: "
|
|
152
|
+
"node.snapshot": "snapshot: ",
|
|
153
|
+
"cmd.desc": "View context makeup and how it evolves",
|
|
154
|
+
"cmd.close": "Close"
|
|
151
155
|
};
|
|
152
156
|
|
|
157
|
+
// src/client/modalStore.ts
|
|
158
|
+
var stores = /* @__PURE__ */ new Map();
|
|
159
|
+
function modalStoreOf(sessionId) {
|
|
160
|
+
const existing = stores.get(sessionId);
|
|
161
|
+
if (existing !== void 0) return existing;
|
|
162
|
+
let open = false;
|
|
163
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
164
|
+
const store = {
|
|
165
|
+
subscribe(listener) {
|
|
166
|
+
listeners.add(listener);
|
|
167
|
+
return () => {
|
|
168
|
+
listeners.delete(listener);
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
getSnapshot: () => open,
|
|
172
|
+
set(next) {
|
|
173
|
+
if (next === open) return;
|
|
174
|
+
open = next;
|
|
175
|
+
for (const listener of listeners) listener();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
stores.set(sessionId, store);
|
|
179
|
+
return store;
|
|
180
|
+
}
|
|
181
|
+
var pendingConsume = /* @__PURE__ */ new Map();
|
|
182
|
+
function setPendingConsume(sessionId, guard) {
|
|
183
|
+
pendingConsume.set(sessionId, guard);
|
|
184
|
+
}
|
|
185
|
+
function takePendingConsume(sessionId) {
|
|
186
|
+
const guard = pendingConsume.get(sessionId);
|
|
187
|
+
if (guard !== void 0) pendingConsume.delete(sessionId);
|
|
188
|
+
return guard;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/client/command.ts
|
|
192
|
+
var COMMAND = "context";
|
|
193
|
+
var LINE = "/" + COMMAND;
|
|
194
|
+
function registerContextCommand(ctx, kit) {
|
|
195
|
+
ctx.effect(() => {
|
|
196
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
197
|
+
if (inputTriggers === void 0) return () => {
|
|
198
|
+
};
|
|
199
|
+
return inputTriggers.registerSource({
|
|
200
|
+
trigger: "/",
|
|
201
|
+
name: COMMAND,
|
|
202
|
+
order: 1,
|
|
203
|
+
candidates: (_session, req) => {
|
|
204
|
+
if (req.position !== "leading") return Promise.resolve([]);
|
|
205
|
+
const query = req.query.trim().toLowerCase();
|
|
206
|
+
if (query !== "" && !COMMAND.startsWith(query)) return Promise.resolve([]);
|
|
207
|
+
return Promise.resolve([{ name: COMMAND, description: kit.t("cmd.desc") }]);
|
|
208
|
+
},
|
|
209
|
+
onPick: (pick) => {
|
|
210
|
+
setPendingConsume(pick.session.sessionId, { kind: "span", span: pick.span });
|
|
211
|
+
modalStoreOf(pick.session.sessionId).set(true);
|
|
212
|
+
return "handled";
|
|
213
|
+
},
|
|
214
|
+
matchEnter: (session, line) => {
|
|
215
|
+
if (line !== LINE) return Promise.resolve(void 0);
|
|
216
|
+
setPendingConsume(session.sessionId, { kind: "bare-token", token: LINE });
|
|
217
|
+
modalStoreOf(session.sessionId).set(true);
|
|
218
|
+
return Promise.resolve("handled");
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}, "dsh-context: /context command");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/client/categories.ts
|
|
225
|
+
var CATS = [
|
|
226
|
+
{ key: "system", color: "#6366f1" },
|
|
227
|
+
{ key: "tools", color: "#f59e0b" },
|
|
228
|
+
{ key: "user", color: "#22c55e" },
|
|
229
|
+
{ key: "inject", color: "#a855f7" },
|
|
230
|
+
{ key: "assistant", color: "#3b82f6" },
|
|
231
|
+
{ key: "tool", color: "#14b8a6" }
|
|
232
|
+
];
|
|
233
|
+
function partsOf(breakdown) {
|
|
234
|
+
return CATS.map((c) => {
|
|
235
|
+
return { key: c.key, color: c.color, value: breakdown[c.key] || 0 };
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function anchoredParts(parts, target) {
|
|
239
|
+
if (target === null || target <= 0) return parts;
|
|
240
|
+
let total = 0;
|
|
241
|
+
for (const p of parts) total += p.value;
|
|
242
|
+
if (total <= 0 || total === target) return parts;
|
|
243
|
+
const scale = target / total;
|
|
244
|
+
return parts.map((p) => ({ ...p, value: Math.round(p.value * scale) }));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/client/headline.ts
|
|
248
|
+
function headlineOf(data) {
|
|
249
|
+
const current = data.current;
|
|
250
|
+
const occ = data.occupancy;
|
|
251
|
+
const projected = occ !== void 0 && typeof occ.projectedTokens === "number" ? occ.projectedTokens : void 0;
|
|
252
|
+
const requests = data.requests || [];
|
|
253
|
+
const lastReq = requests.length > 0 ? requests[requests.length - 1] : null;
|
|
254
|
+
const derived = lastReq !== null && typeof lastReq.prompt === "number" ? lastReq.prompt + (current.total - lastReq.total) : void 0;
|
|
255
|
+
const occupancyTokens = projected ?? derived ?? null;
|
|
256
|
+
const window2 = occ !== void 0 && typeof occ.contextWindow === "number" ? occ.contextWindow : data.contextWindow;
|
|
257
|
+
const tokens = occupancyTokens ?? current.total;
|
|
258
|
+
const pct = window2 !== void 0 && window2 > 0 ? Math.min(100, Math.round(tokens / window2 * 100)) : null;
|
|
259
|
+
const parts = anchoredParts(partsOf(current), occupancyTokens !== null && tokens > 0 ? tokens : null);
|
|
260
|
+
return { tokens, window: window2, pct, parts, estimated: occupancyTokens === null };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/client/services.ts
|
|
264
|
+
function timelineOf(value) {
|
|
265
|
+
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/client/react.ts
|
|
270
|
+
var React = require("react");
|
|
271
|
+
var h = React.createElement;
|
|
272
|
+
|
|
273
|
+
// src/client/components/requestDetail.tsx
|
|
274
|
+
function makeRequestDetail(kit, StackedBar) {
|
|
275
|
+
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
276
|
+
return function RequestDetail(props) {
|
|
277
|
+
const req = props.request;
|
|
278
|
+
if (!req) return null;
|
|
279
|
+
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
280
|
+
const head = isTurn ? tr("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
281
|
+
const marker = props.marker ?? null;
|
|
282
|
+
const markerAt = marker !== null ? eventAt(marker) : null;
|
|
283
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-detail" }, /* @__PURE__ */ React.createElement("div", { className: "lc-detail-head" }, /* @__PURE__ */ React.createElement("b", null, head), marker !== null && markerAt !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-marker", title: eventLabel(marker) }, "\u2702 " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)), /* @__PURE__ */ React.createElement("span", null, tr("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, tr("detail.output", { n: fmt3(req.output) })) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: partsOf(req), height: 10 }), /* @__PURE__ */ React.createElement("div", { className: "lc-detail-rows" }, CATS.map((c) => {
|
|
284
|
+
const v = req[c.key] || 0;
|
|
285
|
+
return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-detail-row" }, /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-label" }, catLabel(c.key)), /* @__PURE__ */ React.createElement("span", { className: "lc-bar-track" }, /* @__PURE__ */ React.createElement("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-num" }, "\u2248" + fmt3(v)), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : ""));
|
|
286
|
+
})));
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/client/components/stackedBar.tsx
|
|
291
|
+
function makeStackedBar(kit) {
|
|
292
|
+
const { t, tr, fmt: fmt3, catLabel } = kit;
|
|
293
|
+
return function StackedBar(props) {
|
|
294
|
+
let total = 0;
|
|
295
|
+
for (const p of props.parts) total += p.value;
|
|
296
|
+
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
297
|
+
const free = props.max !== void 0 && props.max > total ? props.max - total : 0;
|
|
298
|
+
const usedPct = scale > 0 ? total / scale * 100 : 0;
|
|
299
|
+
const hovering = props.hoverKey !== null && props.hoverKey !== void 0;
|
|
300
|
+
const showBox = free > 0 && hovering;
|
|
301
|
+
let tip = null;
|
|
302
|
+
if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
303
|
+
if (props.hoverKey === "free" && free > 0) {
|
|
304
|
+
const pct = scale > 0 ? free / scale * 100 : 0;
|
|
305
|
+
tip = {
|
|
306
|
+
text: t("overview.free") + " " + fmt3(free) + " (" + Math.round(pct) + "%)",
|
|
307
|
+
leftPct: Math.max(12, Math.min(total / scale * 100 + pct / 2, 88))
|
|
308
|
+
};
|
|
309
|
+
} else {
|
|
310
|
+
let acc = 0;
|
|
311
|
+
for (const p of props.parts) {
|
|
312
|
+
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
313
|
+
if (p.key === props.hoverKey && p.value > 0) {
|
|
314
|
+
tip = {
|
|
315
|
+
// "(pct%)" is a share of the OCCUPIED total — the dashed box
|
|
316
|
+
// that appears on hover frames exactly this reference region.
|
|
317
|
+
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + tr("overview.ofUsed"),
|
|
318
|
+
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
319
|
+
};
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
acc += pct;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-stacked-wrap" }, /* @__PURE__ */ React.createElement(
|
|
327
|
+
"div",
|
|
328
|
+
{
|
|
329
|
+
className: "lc-stacked" + (hovering ? " lc-stacked-dim" : ""),
|
|
330
|
+
style: { height: (props.height || 14) + "px" },
|
|
331
|
+
onMouseLeave: () => {
|
|
332
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
total > 0 ? props.parts.map((p) => {
|
|
336
|
+
if (!p.value) return null;
|
|
337
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
338
|
+
return /* @__PURE__ */ React.createElement(
|
|
339
|
+
"div",
|
|
340
|
+
{
|
|
341
|
+
key: p.key,
|
|
342
|
+
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
343
|
+
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
344
|
+
onMouseEnter: () => {
|
|
345
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}) : null,
|
|
350
|
+
free > 0 ? /* @__PURE__ */ React.createElement(
|
|
351
|
+
"div",
|
|
352
|
+
{
|
|
353
|
+
key: "free",
|
|
354
|
+
className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
|
|
355
|
+
style: { width: free / scale * 100 + "%" },
|
|
356
|
+
onMouseEnter: () => {
|
|
357
|
+
if (props.onHoverKey !== void 0) props.onHoverKey("free");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
) : null,
|
|
361
|
+
showBox ? /* @__PURE__ */ React.createElement("div", { className: "lc-occupied-box", style: { width: usedPct + "%" } }) : null
|
|
362
|
+
), tip ? /* @__PURE__ */ React.createElement("div", { className: "lc-bar-tip", style: { left: tip.leftPct + "%" } }, tip.text) : null);
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function makeLegend(kit) {
|
|
366
|
+
const { tr, fmt: fmt3, catLabel } = kit;
|
|
367
|
+
return function Legend(props) {
|
|
368
|
+
let total = 0;
|
|
369
|
+
for (const p of props.parts) total += p.value;
|
|
370
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-legend" }, props.parts.map((p) => {
|
|
371
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
372
|
+
return /* @__PURE__ */ React.createElement(
|
|
373
|
+
"span",
|
|
374
|
+
{
|
|
375
|
+
key: p.key,
|
|
376
|
+
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
377
|
+
title: tr("overview.ofUsed"),
|
|
378
|
+
onMouseEnter: () => {
|
|
379
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
380
|
+
},
|
|
381
|
+
onMouseLeave: () => {
|
|
382
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
/* @__PURE__ */ React.createElement("i", { style: { background: p.color } }),
|
|
386
|
+
catLabel(p.key) + " \u2248" + fmt3(p.value),
|
|
387
|
+
total > 0 ? /* @__PURE__ */ React.createElement("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
388
|
+
);
|
|
389
|
+
}));
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/client/components/trendChart.tsx
|
|
394
|
+
function aggregateByTurn(requests) {
|
|
395
|
+
const out = [];
|
|
396
|
+
let runSteps = 0;
|
|
397
|
+
for (const req of requests) {
|
|
398
|
+
const last = out.length > 0 ? out[out.length - 1] : null;
|
|
399
|
+
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0)) {
|
|
400
|
+
runSteps++;
|
|
401
|
+
out[out.length - 1] = { ...req, stepCount: runSteps };
|
|
402
|
+
} else {
|
|
403
|
+
runSteps = 1;
|
|
404
|
+
out.push({ ...req, stepCount: 1 });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
function attachMarkers(requests, events) {
|
|
410
|
+
const markers = new Array(requests.length);
|
|
411
|
+
for (const ev of events) {
|
|
412
|
+
if (ev.kind !== "compaction" && ev.kind !== "prune") continue;
|
|
413
|
+
for (let r = 0; r < requests.length; r++) {
|
|
414
|
+
if (requests[r].seq >= ev.seq) {
|
|
415
|
+
if (markers[r] === void 0) markers[r] = ev;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return markers;
|
|
421
|
+
}
|
|
422
|
+
function makeTrendChart(kit) {
|
|
423
|
+
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
424
|
+
const CHART_H = 112;
|
|
425
|
+
const BAR_W = 14;
|
|
426
|
+
const BAR_GAP = 2;
|
|
427
|
+
const TURN_COLORS = ["#6366f1", "#f59e0b", "#22c55e", "#a855f7", "#3b82f6", "#14b8a6", "#ef4444", "#ec4899"];
|
|
428
|
+
return function TrendChart(props) {
|
|
429
|
+
const requests = props.requests;
|
|
430
|
+
const markers = props.markers;
|
|
431
|
+
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
432
|
+
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
433
|
+
let maxTotal = 1;
|
|
434
|
+
for (const req of requests) {
|
|
435
|
+
const bt = barTotalOf(req);
|
|
436
|
+
if (bt > maxTotal) maxTotal = bt;
|
|
437
|
+
}
|
|
438
|
+
const groups = [];
|
|
439
|
+
for (const req of requests) {
|
|
440
|
+
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
441
|
+
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
442
|
+
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
443
|
+
groups.push(grp);
|
|
444
|
+
}
|
|
445
|
+
grp.count++;
|
|
446
|
+
grp.span += req.stepCount ?? 1;
|
|
447
|
+
}
|
|
448
|
+
const scrollRef = React.useRef(null);
|
|
449
|
+
const scrolledOnce = React.useRef(false);
|
|
450
|
+
const lastGranRef = React.useRef(props.granularity);
|
|
451
|
+
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
452
|
+
const edgesRef = React.useRef(edges);
|
|
453
|
+
const updateEdges = (el) => {
|
|
454
|
+
const left = el.scrollLeft > 4;
|
|
455
|
+
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
456
|
+
const prev = edgesRef.current;
|
|
457
|
+
if (prev.left === left && prev.right === right) return;
|
|
458
|
+
edgesRef.current = { left, right };
|
|
459
|
+
setEdges({ left, right });
|
|
460
|
+
};
|
|
461
|
+
React.useLayoutEffect(() => {
|
|
462
|
+
const el = scrollRef.current;
|
|
463
|
+
if (el === null) return;
|
|
464
|
+
if (props.granularity !== lastGranRef.current) {
|
|
465
|
+
lastGranRef.current = props.granularity;
|
|
466
|
+
scrolledOnce.current = false;
|
|
467
|
+
}
|
|
468
|
+
if (!scrolledOnce.current) {
|
|
469
|
+
scrolledOnce.current = true;
|
|
470
|
+
el.scrollLeft = el.scrollWidth;
|
|
471
|
+
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
472
|
+
el.scrollLeft = el.scrollWidth;
|
|
473
|
+
}
|
|
474
|
+
updateEdges(el);
|
|
475
|
+
});
|
|
476
|
+
const tipOf = (req) => {
|
|
477
|
+
const head = req.stepCount !== void 0 && req.stepCount > 1 ? tr("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
478
|
+
return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + tr("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + tr("tip.actual", { n: fmt3(req.prompt) }) : "");
|
|
479
|
+
};
|
|
480
|
+
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
481
|
+
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
482
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-chartrow" }, /* @__PURE__ */ React.createElement("div", { className: "lc-axis" }, /* @__PURE__ */ React.createElement("span", { className: "lc-axis-top" }, fmt3(maxTotal)), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-mid" }, fmt3(Math.round(maxTotal / 2))), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, "0")), /* @__PURE__ */ React.createElement(
|
|
483
|
+
"div",
|
|
484
|
+
{
|
|
485
|
+
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
486
|
+
ref: scrollRef,
|
|
487
|
+
onScroll: (e) => {
|
|
488
|
+
updateEdges(e.currentTarget);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
492
|
+
/* @__PURE__ */ React.createElement(
|
|
493
|
+
"div",
|
|
494
|
+
{
|
|
495
|
+
className: "lc-chart",
|
|
496
|
+
onMouseLeave: () => {
|
|
497
|
+
props.onHover(null);
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-top" }),
|
|
501
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-mid" }),
|
|
502
|
+
requests.map((req, i) => {
|
|
503
|
+
const marker = markers[i];
|
|
504
|
+
const markerAt = marker !== void 0 ? eventAt(marker) : null;
|
|
505
|
+
const selected = props.selectedSeq === req.seq;
|
|
506
|
+
const hovered = props.hoveredSeq === req.seq;
|
|
507
|
+
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
508
|
+
return /* @__PURE__ */ React.createElement(
|
|
509
|
+
"div",
|
|
510
|
+
{
|
|
511
|
+
key: req.seq,
|
|
512
|
+
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
513
|
+
style: { width: BAR_W + "px" },
|
|
514
|
+
onClick: () => {
|
|
515
|
+
props.onSelect(selected ? null : req.seq);
|
|
516
|
+
},
|
|
517
|
+
onMouseEnter: () => {
|
|
518
|
+
props.onHover(req.seq);
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
marker !== void 0 ? /* @__PURE__ */ React.createElement(
|
|
522
|
+
"span",
|
|
523
|
+
{
|
|
524
|
+
className: "lc-bar-marker",
|
|
525
|
+
title: "\u2702 " + (markerAt !== null ? markerAt + " \u2014 " : "") + eventLabel(marker)
|
|
526
|
+
},
|
|
527
|
+
"\u2702"
|
|
528
|
+
) : null,
|
|
529
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-bar-stack" }, CATS.map((c) => {
|
|
530
|
+
const v = (req[c.key] || 0) * anchorOf(req);
|
|
531
|
+
if (!v) return null;
|
|
532
|
+
return /* @__PURE__ */ React.createElement("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
533
|
+
}))
|
|
534
|
+
);
|
|
535
|
+
})
|
|
536
|
+
),
|
|
537
|
+
hoveredReq !== null ? /* @__PURE__ */ React.createElement(
|
|
538
|
+
"div",
|
|
539
|
+
{
|
|
540
|
+
className: "lc-chart-tip",
|
|
541
|
+
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
542
|
+
},
|
|
543
|
+
tipOf(hoveredReq)
|
|
544
|
+
) : null,
|
|
545
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-turns", onMouseLeave: () => {
|
|
546
|
+
props.onHoverTurn(null);
|
|
547
|
+
} }, groups.map((grp, gi) => {
|
|
548
|
+
const on = props.activeTurn === grp.turn;
|
|
549
|
+
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
550
|
+
return /* @__PURE__ */ React.createElement(
|
|
551
|
+
"span",
|
|
552
|
+
{
|
|
553
|
+
key: "turn-" + gi,
|
|
554
|
+
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
555
|
+
style: {
|
|
556
|
+
width: blockW + "px",
|
|
557
|
+
background: TURN_COLORS[gi % TURN_COLORS.length]
|
|
558
|
+
},
|
|
559
|
+
title: "T" + grp.turn,
|
|
560
|
+
onMouseEnter: () => {
|
|
561
|
+
props.onHoverTurn(grp.turn);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
"T" + grp.turn
|
|
565
|
+
);
|
|
566
|
+
}))
|
|
567
|
+
), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/client/components/contextModal.tsx
|
|
572
|
+
var TREND_TURNS = 10;
|
|
573
|
+
function makeContextModal(ctx, kit) {
|
|
574
|
+
const { t, tr, fmt: fmt3 } = kit;
|
|
575
|
+
const sessions = ctx.get("sessions");
|
|
576
|
+
const StackedBar = makeStackedBar(kit);
|
|
577
|
+
const Legend = makeLegend(kit);
|
|
578
|
+
const TrendChart = makeTrendChart(kit);
|
|
579
|
+
const RequestDetail = makeRequestDetail(kit, StackedBar);
|
|
580
|
+
return function ContextModal(props) {
|
|
581
|
+
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
582
|
+
const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
|
|
583
|
+
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
584
|
+
const [selectedSeq, setSelectedSeq] = React.useState(null);
|
|
585
|
+
const [hoveredSeq, setHoveredSeq] = React.useState(null);
|
|
586
|
+
const [hoverCat, setHoverCat] = React.useState(null);
|
|
587
|
+
const close = React.useCallback(() => {
|
|
588
|
+
if (sessionId === "") return;
|
|
589
|
+
modalStoreOf(sessionId).set(false);
|
|
590
|
+
const guard = takePendingConsume(sessionId);
|
|
591
|
+
if (guard === void 0 || sessions === void 0) return;
|
|
592
|
+
const scope = sessions.scope(sessionId);
|
|
593
|
+
if (scope !== void 0) scope.bail(scope, "slash/input-consume-token", { guard });
|
|
594
|
+
}, [sessionId]);
|
|
595
|
+
React.useEffect(() => {
|
|
596
|
+
if (!open) return void 0;
|
|
597
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
598
|
+
const onKey = (ev) => {
|
|
599
|
+
if (ev.key !== "Escape") return;
|
|
600
|
+
ev.preventDefault();
|
|
601
|
+
ev.stopPropagation();
|
|
602
|
+
close();
|
|
603
|
+
};
|
|
604
|
+
window.addEventListener("keydown", onKey, true);
|
|
605
|
+
return () => {
|
|
606
|
+
window.removeEventListener("keydown", onKey, true);
|
|
607
|
+
if (previous !== null && document.contains(previous)) previous.focus();
|
|
608
|
+
};
|
|
609
|
+
}, [open, close]);
|
|
610
|
+
if (!open) return null;
|
|
611
|
+
const requests = data !== null ? data.requests || [] : [];
|
|
612
|
+
const events = data !== null ? data.events || [] : [];
|
|
613
|
+
const turns = aggregateByTurn(requests).slice(-TREND_TURNS);
|
|
614
|
+
const markers = attachMarkers(turns, events);
|
|
615
|
+
let pinnedReq = null;
|
|
616
|
+
for (const req of turns) if (req.seq === selectedSeq) pinnedReq = req;
|
|
617
|
+
let activeReq = null;
|
|
618
|
+
if (hoveredSeq !== null) {
|
|
619
|
+
for (const req of turns) if (req.seq === hoveredSeq) activeReq = req;
|
|
620
|
+
}
|
|
621
|
+
if (activeReq === null) activeReq = pinnedReq;
|
|
622
|
+
if (activeReq === null && turns.length > 0) activeReq = turns[turns.length - 1];
|
|
623
|
+
const markerOf = (req) => {
|
|
624
|
+
const i = turns.indexOf(req);
|
|
625
|
+
return i >= 0 ? markers[i] : void 0;
|
|
626
|
+
};
|
|
627
|
+
const head = data !== null ? headlineOf(data) : null;
|
|
628
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-modal-backdrop", onClick: close }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-card", onClick: (ev) => {
|
|
629
|
+
ev.stopPropagation();
|
|
630
|
+
} }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("tab")), data !== null && (data.model || data.provider) ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "")) : null, /* @__PURE__ */ React.createElement("button", { className: "lc-modal-close", "aria-label": t("cmd.close"), onClick: close }, "\xD7")), data === null || head === null ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement("div", { className: "lc-card-title lc-modal-trend" }, t("trend.title")), turns.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
|
|
631
|
+
TrendChart,
|
|
632
|
+
{
|
|
633
|
+
requests: turns,
|
|
634
|
+
markers,
|
|
635
|
+
selectedSeq: pinnedReq !== null ? pinnedReq.seq : null,
|
|
636
|
+
hoveredSeq,
|
|
637
|
+
activeTurn: null,
|
|
638
|
+
granularity: "turn",
|
|
639
|
+
onSelect: setSelectedSeq,
|
|
640
|
+
onHover: setHoveredSeq,
|
|
641
|
+
onHoverTurn: () => {
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
), /* @__PURE__ */ React.createElement(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 })))));
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
153
648
|
// src/client/styles.ts
|
|
154
649
|
var STYLES = [
|
|
155
650
|
".lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }",
|
|
@@ -263,38 +758,17 @@ var STYLES = [
|
|
|
263
758
|
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
264
759
|
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
265
760
|
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
|
266
|
-
".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }"
|
|
761
|
+
".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }",
|
|
762
|
+
// ---- /context modal (centered dialog; escapes the composer anchor via fixed positioning) ----
|
|
763
|
+
".lc-modal-backdrop { position: fixed; inset: 0; z-index: 200; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; }",
|
|
764
|
+
".lc-modal-card { width: min(720px, calc(100vw - 48px)); max-height: min(82vh, 760px); overflow-y: auto; box-sizing: border-box; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; box-shadow: var(--dsw-shadow-lv3, 0 12px 32px rgba(0, 0, 0, 0.4)); padding: 16px 18px 18px; color: var(--dsw-alias-label-primary); font-size: 13px; }",
|
|
765
|
+
".lc-modal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }",
|
|
766
|
+
".lc-modal-title { font-weight: 600; font-size: 14px; }",
|
|
767
|
+
".lc-modal-close { margin-left: auto; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; padding: 2px 6px; border-radius: 6px; cursor: pointer; font-family: inherit; }",
|
|
768
|
+
".lc-modal-close:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); }",
|
|
769
|
+
".lc-modal-trend { margin-top: 14px; }"
|
|
267
770
|
].join("\n");
|
|
268
771
|
|
|
269
|
-
// src/client/categories.ts
|
|
270
|
-
var CATS = [
|
|
271
|
-
{ key: "system", color: "#6366f1" },
|
|
272
|
-
{ key: "tools", color: "#f59e0b" },
|
|
273
|
-
{ key: "user", color: "#22c55e" },
|
|
274
|
-
{ key: "inject", color: "#a855f7" },
|
|
275
|
-
{ key: "assistant", color: "#3b82f6" },
|
|
276
|
-
{ key: "tool", color: "#14b8a6" }
|
|
277
|
-
];
|
|
278
|
-
function partsOf(breakdown) {
|
|
279
|
-
return CATS.map((c) => {
|
|
280
|
-
return { key: c.key, color: c.color, value: breakdown[c.key] || 0 };
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
function anchoredParts(parts, target) {
|
|
284
|
-
if (target === null || target <= 0) return parts;
|
|
285
|
-
let total = 0;
|
|
286
|
-
for (const p of parts) total += p.value;
|
|
287
|
-
if (total <= 0 || total === target) return parts;
|
|
288
|
-
const scale = target / total;
|
|
289
|
-
return parts.map((p) => ({ ...p, value: Math.round(p.value * scale) }));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// src/client/services.ts
|
|
293
|
-
function timelineOf(value) {
|
|
294
|
-
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
295
|
-
return value;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
772
|
// src/client/components/events.tsx
|
|
299
773
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
300
774
|
|
|
@@ -313,10 +787,6 @@ function fmtTime(t) {
|
|
|
313
787
|
return p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
|
|
314
788
|
}
|
|
315
789
|
|
|
316
|
-
// src/client/react.ts
|
|
317
|
-
var React = require("react");
|
|
318
|
-
var h = React.createElement;
|
|
319
|
-
|
|
320
790
|
// src/client/components/events.tsx
|
|
321
791
|
var EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
|
|
322
792
|
function makeEventText(t, tr) {
|
|
@@ -331,396 +801,98 @@ function makeEventText(t, tr) {
|
|
|
331
801
|
}
|
|
332
802
|
return ev.kind;
|
|
333
803
|
}
|
|
334
|
-
function eventAt(ev) {
|
|
335
|
-
if (ev.kind === "compaction" || ev.kind === "prune") {
|
|
336
|
-
if (typeof ev.turn === "number" && typeof ev.step === "number") {
|
|
337
|
-
if (typeof ev.fromTurn === "number" && typeof ev.fromStep === "number") {
|
|
338
|
-
if (ev.fromTurn === ev.turn) return t("events.range", { t: ev.turn, a: ev.fromStep, b: ev.step });
|
|
339
|
-
return t("events.rangeTo", { a: ev.fromTurn, as: ev.fromStep, b: ev.turn, bs: ev.step });
|
|
340
|
-
}
|
|
341
|
-
return t("events.at", { t: ev.turn, s: ev.step });
|
|
342
|
-
}
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
if (typeof ev.turn === "number" && typeof ev.step === "number") {
|
|
346
|
-
return t("events.at", { t: ev.turn, s: ev.step });
|
|
347
|
-
}
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
return { eventLabel, eventAt };
|
|
351
|
-
}
|
|
352
|
-
function makeEventList(kit) {
|
|
353
|
-
const { t, fmt: fmt3, fmtTime: fmtTime2, eventLabel, eventAt } = kit;
|
|
354
|
-
return function EventList(props) {
|
|
355
|
-
if (props.events.length === 0) {
|
|
356
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
|
|
357
|
-
}
|
|
358
|
-
const sorted = props.events.slice().reverse();
|
|
359
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-events" }, sorted.map((ev, i) => {
|
|
360
|
-
const label = eventLabel(ev);
|
|
361
|
-
const at = eventAt(ev);
|
|
362
|
-
const glyph = ev.kind === "inject" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconPlusOutline16, null) : ev.kind === "model" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconBranchOutline16, null) : EVENT_ICONS[ev.kind] || "\u2022";
|
|
363
|
-
return /* @__PURE__ */ React.createElement("div", { key: ev.seq + "-" + i, className: "lc-event" }, /* @__PURE__ */ React.createElement("span", { className: "lc-event-icon lc-event-" + ev.kind }, glyph), /* @__PURE__ */ React.createElement("span", { className: "lc-event-label", title: label }, label), at !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-at" }, at) : null, ev.tokens ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") }, (ev.kind === "inject" ? "+" : "\u2212") + fmt3(ev.tokens)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-event-time" }, fmtTime2(ev.time)));
|
|
364
|
-
}));
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/client/components/nodes.tsx
|
|
369
|
-
function makeNodeList(kit) {
|
|
370
|
-
const { t, tr, fmt: fmt3, fmtTime: fmtTime2 } = kit;
|
|
371
|
-
function nodeText(n) {
|
|
372
|
-
if (n.cat === "tool") {
|
|
373
|
-
return t("node.toolResult") + (n.tool ? " \u2190 " + n.tool : "") + (n.err ? " \u26A0" : "");
|
|
374
|
-
}
|
|
375
|
-
if (n.skill) return "Skill: " + n.skill;
|
|
376
|
-
if (n.calls) return t("node.calls") + n.calls.join(", ");
|
|
377
|
-
if (n.text) return n.form === "snapshot" ? t("node.snapshot") + n.text : n.text;
|
|
378
|
-
if (n.cat === "assistant") return t("node.empty");
|
|
379
|
-
if (n.cat === "inject") return t("form." + (n.form || "context"));
|
|
380
|
-
return t("node.nonText");
|
|
381
|
-
}
|
|
382
|
-
return function NodeList(props) {
|
|
383
|
-
if (props.nodes.length === 0) {
|
|
384
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("nodes.empty"));
|
|
385
|
-
}
|
|
386
|
-
const catColor = {};
|
|
387
|
-
for (const c of CATS) catColor[c.key] = c.color;
|
|
388
|
-
const rows = props.nodes.slice().reverse();
|
|
389
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, tr("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
|
|
390
|
-
const text = nodeText(n);
|
|
391
|
-
return /* @__PURE__ */ React.createElement("div", { key: n.seq, className: "lc-node" }, /* @__PURE__ */ React.createElement("i", { style: { background: catColor[n.cat] || "#999" } }), /* @__PURE__ */ React.createElement("span", { className: "lc-node-preview", title: text }, text), typeof n.time === "number" ? /* @__PURE__ */ React.createElement("span", { className: "lc-node-time" }, fmtTime2(n.time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-node-tokens" }, fmt3(n.tokens)));
|
|
392
|
-
}));
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// src/client/components/requestDetail.tsx
|
|
397
|
-
function makeRequestDetail(kit, StackedBar) {
|
|
398
|
-
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
399
|
-
return function RequestDetail(props) {
|
|
400
|
-
const req = props.request;
|
|
401
|
-
if (!req) return null;
|
|
402
|
-
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
403
|
-
const head = isTurn ? tr("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
404
|
-
const marker = props.marker ?? null;
|
|
405
|
-
const markerAt = marker !== null ? eventAt(marker) : null;
|
|
406
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-detail" }, /* @__PURE__ */ React.createElement("div", { className: "lc-detail-head" }, /* @__PURE__ */ React.createElement("b", null, head), marker !== null && markerAt !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-marker", title: eventLabel(marker) }, "\u2702 " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)), /* @__PURE__ */ React.createElement("span", null, tr("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, tr("detail.output", { n: fmt3(req.output) })) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: partsOf(req), height: 10 }), /* @__PURE__ */ React.createElement("div", { className: "lc-detail-rows" }, CATS.map((c) => {
|
|
407
|
-
const v = req[c.key] || 0;
|
|
408
|
-
return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-detail-row" }, /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-label" }, catLabel(c.key)), /* @__PURE__ */ React.createElement("span", { className: "lc-bar-track" }, /* @__PURE__ */ React.createElement("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-num" }, "\u2248" + fmt3(v)), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : ""));
|
|
409
|
-
})));
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// src/client/components/statsBoard.tsx
|
|
414
|
-
function makeStatsBoard(kit) {
|
|
415
|
-
const { t, tr, fmt: fmt3 } = kit;
|
|
416
|
-
return function StatsBoard(props) {
|
|
417
|
-
const turns = /* @__PURE__ */ new Set();
|
|
418
|
-
let steps = 0, compactions = 0, prunes = 0, injects = 0, switches = 0;
|
|
419
|
-
let recycled = 0, est = 0, prompt = 0, output = 0;
|
|
420
|
-
for (const req of props.requests) {
|
|
421
|
-
turns.add(req.turn ?? 0);
|
|
422
|
-
steps++;
|
|
423
|
-
est += req.total;
|
|
424
|
-
if (typeof req.prompt === "number") prompt += req.prompt;
|
|
425
|
-
if (typeof req.output === "number") output += req.output;
|
|
426
|
-
}
|
|
427
|
-
for (const ev of props.events) {
|
|
428
|
-
if (ev.kind === "compaction") {
|
|
429
|
-
compactions++;
|
|
430
|
-
recycled += ev.tokens || 0;
|
|
431
|
-
} else if (ev.kind === "prune") {
|
|
432
|
-
prunes++;
|
|
433
|
-
recycled += ev.tokens || 0;
|
|
434
|
-
} else if (ev.kind === "inject") injects++;
|
|
435
|
-
else if (ev.kind === "model") switches++;
|
|
436
|
-
}
|
|
437
|
-
const cell = (label, value, sub) => /* @__PURE__ */ React.createElement("div", { className: "lc-stat" }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value" }, value), sub !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-stat-sub" }, sub) : null);
|
|
438
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("stats.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(
|
|
439
|
-
t("stats.recycled"),
|
|
440
|
-
recycled > 0 ? "\u2212" + fmt3(recycled) : "0",
|
|
441
|
-
tr("stats.recycleSub", { c: compactions, p: prunes })
|
|
442
|
-
), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.switches"), fmt3(switches)), cell(t("stats.est"), "\u2248 " + fmt3(est)), cell(t("stats.actualPrompt"), fmt3(prompt)), cell(t("stats.output"), fmt3(output))));
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// src/client/components/stackedBar.tsx
|
|
447
|
-
function makeStackedBar(kit) {
|
|
448
|
-
const { t, tr, fmt: fmt3, catLabel } = kit;
|
|
449
|
-
return function StackedBar(props) {
|
|
450
|
-
let total = 0;
|
|
451
|
-
for (const p of props.parts) total += p.value;
|
|
452
|
-
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
453
|
-
const free = props.max !== void 0 && props.max > total ? props.max - total : 0;
|
|
454
|
-
const usedPct = scale > 0 ? total / scale * 100 : 0;
|
|
455
|
-
const hovering = props.hoverKey !== null && props.hoverKey !== void 0;
|
|
456
|
-
const showBox = free > 0 && hovering;
|
|
457
|
-
let tip = null;
|
|
458
|
-
if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
459
|
-
if (props.hoverKey === "free" && free > 0) {
|
|
460
|
-
const pct = scale > 0 ? free / scale * 100 : 0;
|
|
461
|
-
tip = {
|
|
462
|
-
text: t("overview.free") + " " + fmt3(free) + " (" + Math.round(pct) + "%)",
|
|
463
|
-
leftPct: Math.max(12, Math.min(total / scale * 100 + pct / 2, 88))
|
|
464
|
-
};
|
|
465
|
-
} else {
|
|
466
|
-
let acc = 0;
|
|
467
|
-
for (const p of props.parts) {
|
|
468
|
-
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
469
|
-
if (p.key === props.hoverKey && p.value > 0) {
|
|
470
|
-
tip = {
|
|
471
|
-
// "(pct%)" is a share of the OCCUPIED total — the dashed box
|
|
472
|
-
// that appears on hover frames exactly this reference region.
|
|
473
|
-
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + tr("overview.ofUsed"),
|
|
474
|
-
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
475
|
-
};
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
acc += pct;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-stacked-wrap" }, /* @__PURE__ */ React.createElement(
|
|
483
|
-
"div",
|
|
484
|
-
{
|
|
485
|
-
className: "lc-stacked" + (hovering ? " lc-stacked-dim" : ""),
|
|
486
|
-
style: { height: (props.height || 14) + "px" },
|
|
487
|
-
onMouseLeave: () => {
|
|
488
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
489
|
-
}
|
|
490
|
-
},
|
|
491
|
-
total > 0 ? props.parts.map((p) => {
|
|
492
|
-
if (!p.value) return null;
|
|
493
|
-
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
494
|
-
return /* @__PURE__ */ React.createElement(
|
|
495
|
-
"div",
|
|
496
|
-
{
|
|
497
|
-
key: p.key,
|
|
498
|
-
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
499
|
-
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
500
|
-
onMouseEnter: () => {
|
|
501
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
);
|
|
505
|
-
}) : null,
|
|
506
|
-
free > 0 ? /* @__PURE__ */ React.createElement(
|
|
507
|
-
"div",
|
|
508
|
-
{
|
|
509
|
-
key: "free",
|
|
510
|
-
className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
|
|
511
|
-
style: { width: free / scale * 100 + "%" },
|
|
512
|
-
onMouseEnter: () => {
|
|
513
|
-
if (props.onHoverKey !== void 0) props.onHoverKey("free");
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
) : null,
|
|
517
|
-
showBox ? /* @__PURE__ */ React.createElement("div", { className: "lc-occupied-box", style: { width: usedPct + "%" } }) : null
|
|
518
|
-
), tip ? /* @__PURE__ */ React.createElement("div", { className: "lc-bar-tip", style: { left: tip.leftPct + "%" } }, tip.text) : null);
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
function makeLegend(kit) {
|
|
522
|
-
const { tr, fmt: fmt3, catLabel } = kit;
|
|
523
|
-
return function Legend(props) {
|
|
524
|
-
let total = 0;
|
|
525
|
-
for (const p of props.parts) total += p.value;
|
|
526
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-legend" }, props.parts.map((p) => {
|
|
527
|
-
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
528
|
-
return /* @__PURE__ */ React.createElement(
|
|
529
|
-
"span",
|
|
530
|
-
{
|
|
531
|
-
key: p.key,
|
|
532
|
-
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
533
|
-
title: tr("overview.ofUsed"),
|
|
534
|
-
onMouseEnter: () => {
|
|
535
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
536
|
-
},
|
|
537
|
-
onMouseLeave: () => {
|
|
538
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
539
|
-
}
|
|
540
|
-
},
|
|
541
|
-
/* @__PURE__ */ React.createElement("i", { style: { background: p.color } }),
|
|
542
|
-
catLabel(p.key) + " \u2248" + fmt3(p.value),
|
|
543
|
-
total > 0 ? /* @__PURE__ */ React.createElement("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
544
|
-
);
|
|
804
|
+
function eventAt(ev) {
|
|
805
|
+
if (ev.kind === "compaction" || ev.kind === "prune") {
|
|
806
|
+
if (typeof ev.turn === "number" && typeof ev.step === "number") {
|
|
807
|
+
if (typeof ev.fromTurn === "number" && typeof ev.fromStep === "number") {
|
|
808
|
+
if (ev.fromTurn === ev.turn) return t("events.range", { t: ev.turn, a: ev.fromStep, b: ev.step });
|
|
809
|
+
return t("events.rangeTo", { a: ev.fromTurn, as: ev.fromStep, b: ev.turn, bs: ev.step });
|
|
810
|
+
}
|
|
811
|
+
return t("events.at", { t: ev.turn, s: ev.step });
|
|
812
|
+
}
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
if (typeof ev.turn === "number" && typeof ev.step === "number") {
|
|
816
|
+
return t("events.at", { t: ev.turn, s: ev.step });
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
return { eventLabel, eventAt };
|
|
821
|
+
}
|
|
822
|
+
function makeEventList(kit) {
|
|
823
|
+
const { t, fmt: fmt3, fmtTime: fmtTime2, eventLabel, eventAt } = kit;
|
|
824
|
+
return function EventList(props) {
|
|
825
|
+
if (props.events.length === 0) {
|
|
826
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
|
|
827
|
+
}
|
|
828
|
+
const sorted = props.events.slice().reverse();
|
|
829
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-events" }, sorted.map((ev, i) => {
|
|
830
|
+
const label = eventLabel(ev);
|
|
831
|
+
const at = eventAt(ev);
|
|
832
|
+
const glyph = ev.kind === "inject" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconPlusOutline16, null) : ev.kind === "model" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconBranchOutline16, null) : EVENT_ICONS[ev.kind] || "\u2022";
|
|
833
|
+
return /* @__PURE__ */ React.createElement("div", { key: ev.seq + "-" + i, className: "lc-event" }, /* @__PURE__ */ React.createElement("span", { className: "lc-event-icon lc-event-" + ev.kind }, glyph), /* @__PURE__ */ React.createElement("span", { className: "lc-event-label", title: label }, label), at !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-at" }, at) : null, ev.tokens ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") }, (ev.kind === "inject" ? "+" : "\u2212") + fmt3(ev.tokens)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-event-time" }, fmtTime2(ev.time)));
|
|
545
834
|
}));
|
|
546
835
|
};
|
|
547
836
|
}
|
|
548
837
|
|
|
549
|
-
// src/client/components/
|
|
550
|
-
function
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0)) {
|
|
556
|
-
runSteps++;
|
|
557
|
-
out[out.length - 1] = { ...req, stepCount: runSteps };
|
|
558
|
-
} else {
|
|
559
|
-
runSteps = 1;
|
|
560
|
-
out.push({ ...req, stepCount: 1 });
|
|
838
|
+
// src/client/components/nodes.tsx
|
|
839
|
+
function makeNodeList(kit) {
|
|
840
|
+
const { t, tr, fmt: fmt3, fmtTime: fmtTime2 } = kit;
|
|
841
|
+
function nodeText(n) {
|
|
842
|
+
if (n.cat === "tool") {
|
|
843
|
+
return t("node.toolResult") + (n.tool ? " \u2190 " + n.tool : "") + (n.err ? " \u26A0" : "");
|
|
561
844
|
}
|
|
845
|
+
if (n.skill) return "Skill: " + n.skill;
|
|
846
|
+
if (n.calls) return t("node.calls") + n.calls.join(", ");
|
|
847
|
+
if (n.text) return n.form === "snapshot" ? t("node.snapshot") + n.text : n.text;
|
|
848
|
+
if (n.cat === "assistant") return t("node.empty");
|
|
849
|
+
if (n.cat === "inject") return t("form." + (n.form || "context"));
|
|
850
|
+
return t("node.nonText");
|
|
562
851
|
}
|
|
563
|
-
return
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const markers = new Array(requests.length);
|
|
567
|
-
for (const ev of events) {
|
|
568
|
-
if (ev.kind !== "compaction" && ev.kind !== "prune") continue;
|
|
569
|
-
for (let r = 0; r < requests.length; r++) {
|
|
570
|
-
if (requests[r].seq >= ev.seq) {
|
|
571
|
-
if (markers[r] === void 0) markers[r] = ev;
|
|
572
|
-
break;
|
|
573
|
-
}
|
|
852
|
+
return function NodeList(props) {
|
|
853
|
+
if (props.nodes.length === 0) {
|
|
854
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("nodes.empty"));
|
|
574
855
|
}
|
|
575
|
-
|
|
576
|
-
|
|
856
|
+
const catColor = {};
|
|
857
|
+
for (const c of CATS) catColor[c.key] = c.color;
|
|
858
|
+
const rows = props.nodes.slice().reverse();
|
|
859
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, tr("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
|
|
860
|
+
const text = nodeText(n);
|
|
861
|
+
return /* @__PURE__ */ React.createElement("div", { key: n.seq, className: "lc-node" }, /* @__PURE__ */ React.createElement("i", { style: { background: catColor[n.cat] || "#999" } }), /* @__PURE__ */ React.createElement("span", { className: "lc-node-preview", title: text }, text), typeof n.time === "number" ? /* @__PURE__ */ React.createElement("span", { className: "lc-node-time" }, fmtTime2(n.time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-node-tokens" }, fmt3(n.tokens)));
|
|
862
|
+
}));
|
|
863
|
+
};
|
|
577
864
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (bt > maxTotal) maxTotal = bt;
|
|
865
|
+
|
|
866
|
+
// src/client/components/statsBoard.tsx
|
|
867
|
+
function makeStatsBoard(kit) {
|
|
868
|
+
const { t, tr, fmt: fmt3 } = kit;
|
|
869
|
+
return function StatsBoard(props) {
|
|
870
|
+
const turns = /* @__PURE__ */ new Set();
|
|
871
|
+
let steps = 0, compactions = 0, prunes = 0, injects = 0, switches = 0;
|
|
872
|
+
let recycled = 0, est = 0, prompt = 0, output = 0;
|
|
873
|
+
for (const req of props.requests) {
|
|
874
|
+
turns.add(req.turn ?? 0);
|
|
875
|
+
steps++;
|
|
876
|
+
est += req.total;
|
|
877
|
+
if (typeof req.prompt === "number") prompt += req.prompt;
|
|
878
|
+
if (typeof req.output === "number") output += req.output;
|
|
593
879
|
}
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
880
|
+
for (const ev of props.events) {
|
|
881
|
+
if (ev.kind === "compaction") {
|
|
882
|
+
compactions++;
|
|
883
|
+
recycled += ev.tokens || 0;
|
|
884
|
+
} else if (ev.kind === "prune") {
|
|
885
|
+
prunes++;
|
|
886
|
+
recycled += ev.tokens || 0;
|
|
887
|
+
} else if (ev.kind === "inject") injects++;
|
|
888
|
+
else if (ev.kind === "model") switches++;
|
|
603
889
|
}
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const left = el.scrollLeft > 4;
|
|
611
|
-
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
612
|
-
const prev = edgesRef.current;
|
|
613
|
-
if (prev.left === left && prev.right === right) return;
|
|
614
|
-
edgesRef.current = { left, right };
|
|
615
|
-
setEdges({ left, right });
|
|
616
|
-
};
|
|
617
|
-
React.useLayoutEffect(() => {
|
|
618
|
-
const el = scrollRef.current;
|
|
619
|
-
if (el === null) return;
|
|
620
|
-
if (props.granularity !== lastGranRef.current) {
|
|
621
|
-
lastGranRef.current = props.granularity;
|
|
622
|
-
scrolledOnce.current = false;
|
|
623
|
-
}
|
|
624
|
-
if (!scrolledOnce.current) {
|
|
625
|
-
scrolledOnce.current = true;
|
|
626
|
-
el.scrollLeft = el.scrollWidth;
|
|
627
|
-
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
628
|
-
el.scrollLeft = el.scrollWidth;
|
|
629
|
-
}
|
|
630
|
-
updateEdges(el);
|
|
631
|
-
});
|
|
632
|
-
const tipOf = (req) => {
|
|
633
|
-
const head = req.stepCount !== void 0 && req.stepCount > 1 ? tr("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
634
|
-
return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + tr("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + tr("tip.actual", { n: fmt3(req.prompt) }) : "");
|
|
635
|
-
};
|
|
636
|
-
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
637
|
-
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
638
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-chartrow" }, /* @__PURE__ */ React.createElement("div", { className: "lc-axis" }, /* @__PURE__ */ React.createElement("span", { className: "lc-axis-top" }, fmt3(maxTotal)), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-mid" }, fmt3(Math.round(maxTotal / 2))), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, "0")), /* @__PURE__ */ React.createElement(
|
|
639
|
-
"div",
|
|
640
|
-
{
|
|
641
|
-
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
642
|
-
ref: scrollRef,
|
|
643
|
-
onScroll: (e) => {
|
|
644
|
-
updateEdges(e.currentTarget);
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
648
|
-
/* @__PURE__ */ React.createElement(
|
|
649
|
-
"div",
|
|
650
|
-
{
|
|
651
|
-
className: "lc-chart",
|
|
652
|
-
onMouseLeave: () => {
|
|
653
|
-
props.onHover(null);
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-top" }),
|
|
657
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-mid" }),
|
|
658
|
-
requests.map((req, i) => {
|
|
659
|
-
const marker = markers[i];
|
|
660
|
-
const markerAt = marker !== void 0 ? eventAt(marker) : null;
|
|
661
|
-
const selected = props.selectedSeq === req.seq;
|
|
662
|
-
const hovered = props.hoveredSeq === req.seq;
|
|
663
|
-
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
664
|
-
return /* @__PURE__ */ React.createElement(
|
|
665
|
-
"div",
|
|
666
|
-
{
|
|
667
|
-
key: req.seq,
|
|
668
|
-
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
669
|
-
style: { width: BAR_W + "px" },
|
|
670
|
-
onClick: () => {
|
|
671
|
-
props.onSelect(selected ? null : req.seq);
|
|
672
|
-
},
|
|
673
|
-
onMouseEnter: () => {
|
|
674
|
-
props.onHover(req.seq);
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
marker !== void 0 ? /* @__PURE__ */ React.createElement(
|
|
678
|
-
"span",
|
|
679
|
-
{
|
|
680
|
-
className: "lc-bar-marker",
|
|
681
|
-
title: "\u2702 " + (markerAt !== null ? markerAt + " \u2014 " : "") + eventLabel(marker)
|
|
682
|
-
},
|
|
683
|
-
"\u2702"
|
|
684
|
-
) : null,
|
|
685
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-bar-stack" }, CATS.map((c) => {
|
|
686
|
-
const v = (req[c.key] || 0) * anchorOf(req);
|
|
687
|
-
if (!v) return null;
|
|
688
|
-
return /* @__PURE__ */ React.createElement("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
689
|
-
}))
|
|
690
|
-
);
|
|
691
|
-
})
|
|
692
|
-
),
|
|
693
|
-
hoveredReq !== null ? /* @__PURE__ */ React.createElement(
|
|
694
|
-
"div",
|
|
695
|
-
{
|
|
696
|
-
className: "lc-chart-tip",
|
|
697
|
-
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
698
|
-
},
|
|
699
|
-
tipOf(hoveredReq)
|
|
700
|
-
) : null,
|
|
701
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-turns", onMouseLeave: () => {
|
|
702
|
-
props.onHoverTurn(null);
|
|
703
|
-
} }, groups.map((grp, gi) => {
|
|
704
|
-
const on = props.activeTurn === grp.turn;
|
|
705
|
-
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
706
|
-
return /* @__PURE__ */ React.createElement(
|
|
707
|
-
"span",
|
|
708
|
-
{
|
|
709
|
-
key: "turn-" + gi,
|
|
710
|
-
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
711
|
-
style: {
|
|
712
|
-
width: blockW + "px",
|
|
713
|
-
background: TURN_COLORS[gi % TURN_COLORS.length]
|
|
714
|
-
},
|
|
715
|
-
title: "T" + grp.turn,
|
|
716
|
-
onMouseEnter: () => {
|
|
717
|
-
props.onHoverTurn(grp.turn);
|
|
718
|
-
}
|
|
719
|
-
},
|
|
720
|
-
"T" + grp.turn
|
|
721
|
-
);
|
|
722
|
-
}))
|
|
723
|
-
), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
|
|
890
|
+
const cell = (label, value, sub) => /* @__PURE__ */ React.createElement("div", { className: "lc-stat" }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value" }, value), sub !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-stat-sub" }, sub) : null);
|
|
891
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("stats.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(
|
|
892
|
+
t("stats.recycled"),
|
|
893
|
+
recycled > 0 ? "\u2212" + fmt3(recycled) : "0",
|
|
894
|
+
tr("stats.recycleSub", { c: compactions, p: prunes })
|
|
895
|
+
), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.switches"), fmt3(switches)), cell(t("stats.est"), "\u2248 " + fmt3(est)), cell(t("stats.actualPrompt"), fmt3(prompt)), cell(t("stats.output"), fmt3(output))));
|
|
724
896
|
};
|
|
725
897
|
}
|
|
726
898
|
|
|
@@ -773,7 +945,6 @@ function makeContextView(ctx, kit) {
|
|
|
773
945
|
if (!data) {
|
|
774
946
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")));
|
|
775
947
|
}
|
|
776
|
-
const current = data.current;
|
|
777
948
|
const requests = data.requests || [];
|
|
778
949
|
const events = data.events || [];
|
|
779
950
|
const nodes = data.nodes || [];
|
|
@@ -798,16 +969,8 @@ function makeContextView(ctx, kit) {
|
|
|
798
969
|
break;
|
|
799
970
|
}
|
|
800
971
|
}
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
const lastReq = displayRequests.length > 0 ? displayRequests[displayRequests.length - 1] : null;
|
|
804
|
-
const derived = lastReq !== null && typeof lastReq.prompt === "number" ? lastReq.prompt + (current.total - lastReq.total) : void 0;
|
|
805
|
-
const occupancyTokens = projected ?? derived ?? null;
|
|
806
|
-
const occupancyWindow = occ !== void 0 && typeof occ.contextWindow === "number" ? occ.contextWindow : data.contextWindow;
|
|
807
|
-
const headline = occupancyTokens ?? current.total;
|
|
808
|
-
const headlinePct = occupancyWindow ? Math.min(100, Math.round(headline / occupancyWindow * 100)) : null;
|
|
809
|
-
const parts = anchoredParts(partsOf(current), occupancyTokens !== null && headline > 0 ? headline : null);
|
|
810
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("overview.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(headline)), /* @__PURE__ */ React.createElement("span", null, occupancyWindow ? " / " + fmt3(occupancyWindow) + " " + tr("overview.ofWindow", { p: headlinePct ?? 0 }) : " " + t("overview.estimate")), occupancyTokens !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts, height: 16, max: occupancyWindow, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
|
|
972
|
+
const head = headlineOf(data);
|
|
973
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("overview.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
|
|
811
974
|
return /* @__PURE__ */ React.createElement("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
|
|
812
975
|
}), data.toolList.length > 5 ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, " " + tr("tools.more", { n: data.toolList.length })) : null) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("trend.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement(
|
|
813
976
|
"button",
|
|
@@ -875,7 +1038,8 @@ function apply(ctx) {
|
|
|
875
1038
|
if (tag.parentNode !== null) tag.parentNode.removeChild(tag);
|
|
876
1039
|
};
|
|
877
1040
|
}, "dsh-context: styles");
|
|
878
|
-
const
|
|
1041
|
+
const kit = makeViewKit(t);
|
|
1042
|
+
const ContextView = makeContextView(ctx, kit);
|
|
879
1043
|
ctx.slots.inject("conversation.view", () => {
|
|
880
1044
|
return ctx.slots.register(
|
|
881
1045
|
// order 20 renders right of Chat (0) and Trajectory (10); the locale
|
|
@@ -884,6 +1048,20 @@ function apply(ctx) {
|
|
|
884
1048
|
(props) => h(ContextView, props)
|
|
885
1049
|
);
|
|
886
1050
|
});
|
|
1051
|
+
registerContextCommand(ctx, kit);
|
|
1052
|
+
const ContextModal = makeContextModal(ctx, kit);
|
|
1053
|
+
ctx.slots.inject("conversation.input.overlay", () => {
|
|
1054
|
+
return ctx.slots.register(
|
|
1055
|
+
{
|
|
1056
|
+
name: "conversation.input.overlay",
|
|
1057
|
+
id: "context-modal",
|
|
1058
|
+
order: 10,
|
|
1059
|
+
locale: NS,
|
|
1060
|
+
inject: (sessionId) => ({ hooks: { contextModal: modalStoreOf(sessionId) } })
|
|
1061
|
+
},
|
|
1062
|
+
(props) => h(ContextModal, props)
|
|
1063
|
+
);
|
|
1064
|
+
});
|
|
887
1065
|
}
|
|
888
1066
|
module.exports = {
|
|
889
1067
|
name: "dsh-context",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "A DeepSeek Harness plugin for context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|