dsh-context 0.8.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 +611 -646
- package/lib/index.js +269 -210
- package/package.json +9 -2
package/lib/client.js
CHANGED
|
@@ -20,6 +20,7 @@ var DICT_ZH = {
|
|
|
20
20
|
"overview.estimate": "tokens\uFF08\u4F30\u7B97\uFF09",
|
|
21
21
|
"overview.free": "\u5269\u4F59\u7A97\u53E3",
|
|
22
22
|
"overview.splitEst": "\xB7 \u5206\u7C7B\u6309\u4F30\u7B97\u6BD4\u4F8B\u5206\u914D",
|
|
23
|
+
"overview.ofUsed": "\u5360\u5DF2\u7528\u4E0A\u4E0B\u6587",
|
|
23
24
|
"stats.title": "\u4E0A\u4E0B\u6587\u7EDF\u8BA1",
|
|
24
25
|
"stats.hint": "\u7EDF\u8BA1\u5F53\u524D\u4FDD\u7559\u7684\u5386\u53F2\u7A97\u53E3\uFF08\u4E0E\u8D8B\u52BF\u56FE\u4E00\u81F4\uFF09",
|
|
25
26
|
"stats.turns": "\u8F6E\u6B21",
|
|
@@ -75,7 +76,9 @@ var DICT_ZH = {
|
|
|
75
76
|
"node.calls": "\u8C03\u7528 ",
|
|
76
77
|
"node.empty": "(\u7A7A\u56DE\u590D)",
|
|
77
78
|
"node.nonText": "(\u975E\u6587\u672C\u6D88\u606F)",
|
|
78
|
-
"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"
|
|
79
82
|
};
|
|
80
83
|
var DICT_EN = {
|
|
81
84
|
"tab": "Context",
|
|
@@ -90,6 +93,7 @@ var DICT_EN = {
|
|
|
90
93
|
"overview.estimate": "tokens (estimated)",
|
|
91
94
|
"overview.free": "Free window",
|
|
92
95
|
"overview.splitEst": "\xB7 category split is estimated",
|
|
96
|
+
"overview.ofUsed": "of used context",
|
|
93
97
|
"stats.title": "Context stats",
|
|
94
98
|
"stats.hint": "Over the retained history window (same as the History chart)",
|
|
95
99
|
"stats.turns": "Turns",
|
|
@@ -145,9 +149,502 @@ var DICT_EN = {
|
|
|
145
149
|
"node.calls": "calls ",
|
|
146
150
|
"node.empty": "(empty reply)",
|
|
147
151
|
"node.nonText": "(non-text message)",
|
|
148
|
-
"node.snapshot": "snapshot: "
|
|
152
|
+
"node.snapshot": "snapshot: ",
|
|
153
|
+
"cmd.desc": "View context makeup and how it evolves",
|
|
154
|
+
"cmd.close": "Close"
|
|
149
155
|
};
|
|
150
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
|
+
|
|
151
648
|
// src/client/styles.ts
|
|
152
649
|
var STYLES = [
|
|
153
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; }",
|
|
@@ -167,11 +664,25 @@ var STYLES = [
|
|
|
167
664
|
".lc-overview-num b { font-size: 20px; }",
|
|
168
665
|
".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
|
|
169
666
|
".lc-stacked-wrap { position: relative; width: 100%; }",
|
|
170
|
-
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); }",
|
|
667
|
+
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); position: relative; }",
|
|
668
|
+
// Hover reference frame around the OCCUPIED region of the composition bar:
|
|
669
|
+
// dashes from the left edge to the used/window boundary, so the legend's
|
|
670
|
+
// "share of used" percentages visibly map to the boxed part (the free track
|
|
671
|
+
// sits outside it). pointer-events: none keeps hover on the segments/free.
|
|
672
|
+
// Deliberately high-contrast (label-primary) and thick so the frame reads at
|
|
673
|
+
// a glance, and the other parts dim underneath it (`.lc-stacked-dim`).
|
|
674
|
+
".lc-occupied-box { position: absolute; top: 0; bottom: 0; left: 0; border: 2px dashed var(--dsw-alias-label-primary); border-radius: 5px; box-sizing: border-box; pointer-events: none; opacity: 1; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-2); }",
|
|
171
675
|
".lc-bar-tip { position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%); z-index: 5; white-space: nowrap; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.18); pointer-events: none; }",
|
|
172
676
|
".lc-stacked > div { height: 100%; }",
|
|
173
677
|
".lc-stacked-seg-on { filter: brightness(1.18); }",
|
|
174
678
|
".lc-stacked-free-on { box-shadow: inset 0 0 0 1px var(--dsw-alias-label-secondary); border-radius: 3px; }",
|
|
679
|
+
// Hover focus: everything except the hovered part (segment, legend chip, or
|
|
680
|
+
// free track) recedes, so the composition highlight and the occupied-region
|
|
681
|
+
// frame read clearly. The selected segment/free keeps full opacity.
|
|
682
|
+
".lc-stacked-dim .lc-stacked-seg { opacity: 0.35; }",
|
|
683
|
+
".lc-stacked-dim .lc-stacked-seg-on { opacity: 1; }",
|
|
684
|
+
".lc-stacked-dim .lc-stacked-free { opacity: 0.35; }",
|
|
685
|
+
".lc-stacked-dim .lc-stacked-free-on { opacity: 1; }",
|
|
175
686
|
".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
|
|
176
687
|
".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }",
|
|
177
688
|
".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
|
|
@@ -247,45 +758,19 @@ var STYLES = [
|
|
|
247
758
|
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
248
759
|
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
249
760
|
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
|
250
|
-
".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; }"
|
|
251
770
|
].join("\n");
|
|
252
771
|
|
|
253
|
-
// src/client/
|
|
254
|
-
var
|
|
255
|
-
var sessionCache = /* @__PURE__ */ new Map();
|
|
256
|
-
function cacheGet(sessionId) {
|
|
257
|
-
return sessionCache.get(sessionId);
|
|
258
|
-
}
|
|
259
|
-
function cachePut(sessionId, snapshot) {
|
|
260
|
-
sessionCache.set(sessionId, snapshot);
|
|
261
|
-
if (sessionCache.size > MAX_CACHED_SESSIONS) {
|
|
262
|
-
const oldest = sessionCache.keys().next().value;
|
|
263
|
-
if (oldest !== void 0) sessionCache.delete(oldest);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/client/categories.ts
|
|
268
|
-
var CATS = [
|
|
269
|
-
{ key: "system", color: "#6366f1" },
|
|
270
|
-
{ key: "tools", color: "#f59e0b" },
|
|
271
|
-
{ key: "user", color: "#22c55e" },
|
|
272
|
-
{ key: "inject", color: "#a855f7" },
|
|
273
|
-
{ key: "assistant", color: "#3b82f6" },
|
|
274
|
-
{ key: "tool", color: "#14b8a6" }
|
|
275
|
-
];
|
|
276
|
-
function partsOf(breakdown) {
|
|
277
|
-
return CATS.map((c) => {
|
|
278
|
-
return { key: c.key, color: c.color, value: breakdown[c.key] || 0 };
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
function anchoredParts(parts, target) {
|
|
282
|
-
if (target === null || target <= 0) return parts;
|
|
283
|
-
let total = 0;
|
|
284
|
-
for (const p of parts) total += p.value;
|
|
285
|
-
if (total <= 0 || total === target) return parts;
|
|
286
|
-
const scale = target / total;
|
|
287
|
-
return parts.map((p) => ({ ...p, value: Math.round(p.value * scale) }));
|
|
288
|
-
}
|
|
772
|
+
// src/client/components/events.tsx
|
|
773
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
289
774
|
|
|
290
775
|
// src/client/format.ts
|
|
291
776
|
function fmt(n) {
|
|
@@ -302,11 +787,7 @@ function fmtTime(t) {
|
|
|
302
787
|
return p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
|
|
303
788
|
}
|
|
304
789
|
|
|
305
|
-
// src/client/
|
|
306
|
-
var React = require("react");
|
|
307
|
-
var h = React.createElement;
|
|
308
|
-
|
|
309
|
-
// src/client/components/events.ts
|
|
790
|
+
// src/client/components/events.tsx
|
|
310
791
|
var EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
|
|
311
792
|
function makeEventText(t, tr) {
|
|
312
793
|
function eventLabel(ev) {
|
|
@@ -341,35 +822,20 @@ function makeEventText(t, tr) {
|
|
|
341
822
|
function makeEventList(kit) {
|
|
342
823
|
const { t, fmt: fmt3, fmtTime: fmtTime2, eventLabel, eventAt } = kit;
|
|
343
824
|
return function EventList(props) {
|
|
344
|
-
if (props.events.length === 0) {
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
const sorted = props.events.slice().reverse();
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return h(
|
|
355
|
-
"div",
|
|
356
|
-
{ key: ev.seq + "-" + i, className: "lc-event" },
|
|
357
|
-
h("span", { className: "lc-event-icon lc-event-" + ev.kind }, EVENT_ICONS[ev.kind] || "\u2022"),
|
|
358
|
-
h("span", { className: "lc-event-label", title: label }, label),
|
|
359
|
-
at !== null ? h("span", { className: "lc-event-at" }, at) : null,
|
|
360
|
-
ev.tokens ? h(
|
|
361
|
-
"span",
|
|
362
|
-
{ className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") },
|
|
363
|
-
(ev.kind === "inject" ? "+" : "\u2212") + fmt3(ev.tokens)
|
|
364
|
-
) : null,
|
|
365
|
-
h("span", { className: "lc-event-time" }, fmtTime2(ev.time))
|
|
366
|
-
);
|
|
367
|
-
})
|
|
368
|
-
);
|
|
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)));
|
|
834
|
+
}));
|
|
369
835
|
};
|
|
370
836
|
}
|
|
371
837
|
|
|
372
|
-
// src/client/components/nodes.
|
|
838
|
+
// src/client/components/nodes.tsx
|
|
373
839
|
function makeNodeList(kit) {
|
|
374
840
|
const { t, tr, fmt: fmt3, fmtTime: fmtTime2 } = kit;
|
|
375
841
|
function nodeText(n) {
|
|
@@ -385,81 +851,19 @@ function makeNodeList(kit) {
|
|
|
385
851
|
}
|
|
386
852
|
return function NodeList(props) {
|
|
387
853
|
if (props.nodes.length === 0) {
|
|
388
|
-
return
|
|
854
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("nodes.empty"));
|
|
389
855
|
}
|
|
390
856
|
const catColor = {};
|
|
391
857
|
for (const c of CATS) catColor[c.key] = c.color;
|
|
392
858
|
const rows = props.nodes.slice().reverse();
|
|
393
|
-
return
|
|
394
|
-
|
|
395
|
-
{ className: "lc-
|
|
396
|
-
|
|
397
|
-
rows.map((n) => {
|
|
398
|
-
const text = nodeText(n);
|
|
399
|
-
return h(
|
|
400
|
-
"div",
|
|
401
|
-
{ key: n.seq, className: "lc-node" },
|
|
402
|
-
h("i", { style: { background: catColor[n.cat] || "#999" } }),
|
|
403
|
-
h("span", { className: "lc-node-preview", title: text }, text),
|
|
404
|
-
// Timestamp when the host event carried one.
|
|
405
|
-
typeof n.time === "number" ? h("span", { className: "lc-node-time" }, fmtTime2(n.time)) : null,
|
|
406
|
-
h("span", { className: "lc-node-tokens" }, fmt3(n.tokens))
|
|
407
|
-
);
|
|
408
|
-
})
|
|
409
|
-
);
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// src/client/components/requestDetail.ts
|
|
414
|
-
function makeRequestDetail(kit, StackedBar) {
|
|
415
|
-
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
416
|
-
return function RequestDetail(props) {
|
|
417
|
-
const req = props.request;
|
|
418
|
-
if (!req) return null;
|
|
419
|
-
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
420
|
-
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 });
|
|
421
|
-
const marker = props.marker ?? null;
|
|
422
|
-
const markerAt = marker !== null ? eventAt(marker) : null;
|
|
423
|
-
return h(
|
|
424
|
-
"div",
|
|
425
|
-
{ className: "lc-detail" },
|
|
426
|
-
h(
|
|
427
|
-
"div",
|
|
428
|
-
{ className: "lc-detail-head" },
|
|
429
|
-
h("b", null, head),
|
|
430
|
-
marker !== null && markerAt !== null ? h("span", { className: "lc-detail-marker", title: eventLabel(marker) }, "\u2702 " + markerAt) : null,
|
|
431
|
-
isTurn ? h("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null,
|
|
432
|
-
h("span", null, fmtTime2(req.time)),
|
|
433
|
-
h("span", null, tr("detail.estTotal", { n: fmt3(req.total) })),
|
|
434
|
-
req.prompt !== void 0 ? h("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt3(req.prompt) })) : null,
|
|
435
|
-
req.output !== void 0 ? h("span", null, tr("detail.output", { n: fmt3(req.output) })) : null
|
|
436
|
-
),
|
|
437
|
-
h(StackedBar, { parts: partsOf(req), height: 10 }),
|
|
438
|
-
h(
|
|
439
|
-
"div",
|
|
440
|
-
{ className: "lc-detail-rows" },
|
|
441
|
-
CATS.map((c) => {
|
|
442
|
-
const v = req[c.key] || 0;
|
|
443
|
-
return h(
|
|
444
|
-
"div",
|
|
445
|
-
{ key: c.key, className: "lc-detail-row" },
|
|
446
|
-
h("i", { style: { background: c.color } }),
|
|
447
|
-
h("span", { className: "lc-detail-label" }, catLabel(c.key)),
|
|
448
|
-
h(
|
|
449
|
-
"span",
|
|
450
|
-
{ className: "lc-bar-track" },
|
|
451
|
-
h("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })
|
|
452
|
-
),
|
|
453
|
-
h("span", { className: "lc-detail-num" }, "\u2248" + fmt3(v)),
|
|
454
|
-
h("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : "")
|
|
455
|
-
);
|
|
456
|
-
})
|
|
457
|
-
)
|
|
458
|
-
);
|
|
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
|
+
}));
|
|
459
863
|
};
|
|
460
864
|
}
|
|
461
865
|
|
|
462
|
-
// src/client/components/statsBoard.
|
|
866
|
+
// src/client/components/statsBoard.tsx
|
|
463
867
|
function makeStatsBoard(kit) {
|
|
464
868
|
const { t, tr, fmt: fmt3 } = kit;
|
|
465
869
|
return function StatsBoard(props) {
|
|
@@ -483,345 +887,16 @@ function makeStatsBoard(kit) {
|
|
|
483
887
|
} else if (ev.kind === "inject") injects++;
|
|
484
888
|
else if (ev.kind === "model") switches++;
|
|
485
889
|
}
|
|
486
|
-
const cell = (label, value, sub) =>
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
);
|
|
493
|
-
return h(
|
|
494
|
-
"div",
|
|
495
|
-
{ className: "lc-card" },
|
|
496
|
-
h(
|
|
497
|
-
"div",
|
|
498
|
-
{ className: "lc-card-title" },
|
|
499
|
-
t("stats.title"),
|
|
500
|
-
h("span", { className: "lc-card-sub" }, t("stats.hint"))
|
|
501
|
-
),
|
|
502
|
-
h(
|
|
503
|
-
"div",
|
|
504
|
-
{ className: "lc-stats" },
|
|
505
|
-
cell(t("stats.turns"), fmt3(turns.size)),
|
|
506
|
-
cell(t("stats.steps"), fmt3(steps)),
|
|
507
|
-
cell(
|
|
508
|
-
t("stats.recycled"),
|
|
509
|
-
recycled > 0 ? "\u2212" + fmt3(recycled) : "0",
|
|
510
|
-
tr("stats.recycleSub", { c: compactions, p: prunes })
|
|
511
|
-
),
|
|
512
|
-
cell(t("stats.injects"), fmt3(injects)),
|
|
513
|
-
cell(t("stats.switches"), fmt3(switches)),
|
|
514
|
-
cell(t("stats.est"), "\u2248 " + fmt3(est)),
|
|
515
|
-
cell(t("stats.actualPrompt"), fmt3(prompt)),
|
|
516
|
-
cell(t("stats.output"), fmt3(output))
|
|
517
|
-
)
|
|
518
|
-
);
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// src/client/components/stackedBar.ts
|
|
523
|
-
function makeStackedBar(kit) {
|
|
524
|
-
const { t, fmt: fmt3, catLabel } = kit;
|
|
525
|
-
return function StackedBar(props) {
|
|
526
|
-
let total = 0;
|
|
527
|
-
for (const p of props.parts) total += p.value;
|
|
528
|
-
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
529
|
-
const free = props.max !== void 0 && props.max > total ? props.max - total : 0;
|
|
530
|
-
let tip = null;
|
|
531
|
-
if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
532
|
-
if (props.hoverKey === "free" && free > 0) {
|
|
533
|
-
const pct = scale > 0 ? free / scale * 100 : 0;
|
|
534
|
-
tip = {
|
|
535
|
-
text: t("overview.free") + " " + fmt3(free) + " (" + Math.round(pct) + "%)",
|
|
536
|
-
leftPct: Math.max(12, Math.min(total / scale * 100 + pct / 2, 88))
|
|
537
|
-
};
|
|
538
|
-
} else {
|
|
539
|
-
let acc = 0;
|
|
540
|
-
for (const p of props.parts) {
|
|
541
|
-
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
542
|
-
if (p.key === props.hoverKey && p.value > 0) {
|
|
543
|
-
tip = {
|
|
544
|
-
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
|
|
545
|
-
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
546
|
-
};
|
|
547
|
-
break;
|
|
548
|
-
}
|
|
549
|
-
acc += pct;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return h(
|
|
554
|
-
"div",
|
|
555
|
-
{ className: "lc-stacked-wrap" },
|
|
556
|
-
h(
|
|
557
|
-
"div",
|
|
558
|
-
{
|
|
559
|
-
className: "lc-stacked",
|
|
560
|
-
style: { height: (props.height || 14) + "px" },
|
|
561
|
-
onMouseLeave: () => {
|
|
562
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
total > 0 ? props.parts.map((p) => {
|
|
566
|
-
if (!p.value) return null;
|
|
567
|
-
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
568
|
-
return h("div", {
|
|
569
|
-
key: p.key,
|
|
570
|
-
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
571
|
-
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
572
|
-
onMouseEnter: () => {
|
|
573
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
}) : null,
|
|
577
|
-
free > 0 ? h("div", {
|
|
578
|
-
key: "free",
|
|
579
|
-
className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
|
|
580
|
-
style: { width: free / scale * 100 + "%" },
|
|
581
|
-
onMouseEnter: () => {
|
|
582
|
-
if (props.onHoverKey !== void 0) props.onHoverKey("free");
|
|
583
|
-
}
|
|
584
|
-
}) : null
|
|
585
|
-
),
|
|
586
|
-
tip ? h("div", { className: "lc-bar-tip", style: { left: tip.leftPct + "%" } }, tip.text) : null
|
|
587
|
-
);
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
function makeLegend(kit) {
|
|
591
|
-
const { fmt: fmt3, catLabel } = kit;
|
|
592
|
-
return function Legend(props) {
|
|
593
|
-
let total = 0;
|
|
594
|
-
for (const p of props.parts) total += p.value;
|
|
595
|
-
return h(
|
|
596
|
-
"div",
|
|
597
|
-
{ className: "lc-legend" },
|
|
598
|
-
props.parts.map((p) => {
|
|
599
|
-
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
600
|
-
return h(
|
|
601
|
-
"span",
|
|
602
|
-
{
|
|
603
|
-
key: p.key,
|
|
604
|
-
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
605
|
-
onMouseEnter: () => {
|
|
606
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
607
|
-
},
|
|
608
|
-
onMouseLeave: () => {
|
|
609
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
610
|
-
}
|
|
611
|
-
},
|
|
612
|
-
h("i", { style: { background: p.color } }),
|
|
613
|
-
catLabel(p.key) + " \u2248" + fmt3(p.value),
|
|
614
|
-
total > 0 ? h("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
615
|
-
);
|
|
616
|
-
})
|
|
617
|
-
);
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// src/client/components/trendChart.ts
|
|
622
|
-
function aggregateByTurn(requests) {
|
|
623
|
-
const out = [];
|
|
624
|
-
let runSteps = 0;
|
|
625
|
-
for (const req of requests) {
|
|
626
|
-
const last = out.length > 0 ? out[out.length - 1] : null;
|
|
627
|
-
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0)) {
|
|
628
|
-
runSteps++;
|
|
629
|
-
out[out.length - 1] = { ...req, stepCount: runSteps };
|
|
630
|
-
} else {
|
|
631
|
-
runSteps = 1;
|
|
632
|
-
out.push({ ...req, stepCount: 1 });
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return out;
|
|
636
|
-
}
|
|
637
|
-
function attachMarkers(requests, events) {
|
|
638
|
-
const markers = new Array(requests.length);
|
|
639
|
-
for (const ev of events) {
|
|
640
|
-
if (ev.kind !== "compaction" && ev.kind !== "prune") continue;
|
|
641
|
-
for (let r = 0; r < requests.length; r++) {
|
|
642
|
-
if (requests[r].seq >= ev.seq) {
|
|
643
|
-
if (markers[r] === void 0) markers[r] = ev;
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return markers;
|
|
649
|
-
}
|
|
650
|
-
function makeTrendChart(kit) {
|
|
651
|
-
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
652
|
-
const CHART_H = 112;
|
|
653
|
-
const BAR_W = 14;
|
|
654
|
-
const BAR_GAP = 2;
|
|
655
|
-
const TURN_COLORS = ["#6366f1", "#f59e0b", "#22c55e", "#a855f7", "#3b82f6", "#14b8a6", "#ef4444", "#ec4899"];
|
|
656
|
-
return function TrendChart(props) {
|
|
657
|
-
const requests = props.requests;
|
|
658
|
-
const markers = props.markers;
|
|
659
|
-
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
660
|
-
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
661
|
-
let maxTotal = 1;
|
|
662
|
-
for (const req of requests) {
|
|
663
|
-
const bt = barTotalOf(req);
|
|
664
|
-
if (bt > maxTotal) maxTotal = bt;
|
|
665
|
-
}
|
|
666
|
-
const groups = [];
|
|
667
|
-
for (const req of requests) {
|
|
668
|
-
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
669
|
-
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
670
|
-
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
671
|
-
groups.push(grp);
|
|
672
|
-
}
|
|
673
|
-
grp.count++;
|
|
674
|
-
grp.span += req.stepCount ?? 1;
|
|
675
|
-
}
|
|
676
|
-
const scrollRef = React.useRef(null);
|
|
677
|
-
const scrolledOnce = React.useRef(false);
|
|
678
|
-
const lastGranRef = React.useRef(props.granularity);
|
|
679
|
-
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
680
|
-
const edgesRef = React.useRef(edges);
|
|
681
|
-
const updateEdges = (el) => {
|
|
682
|
-
const left = el.scrollLeft > 4;
|
|
683
|
-
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
684
|
-
const prev = edgesRef.current;
|
|
685
|
-
if (prev.left === left && prev.right === right) return;
|
|
686
|
-
edgesRef.current = { left, right };
|
|
687
|
-
setEdges({ left, right });
|
|
688
|
-
};
|
|
689
|
-
React.useLayoutEffect(() => {
|
|
690
|
-
const el = scrollRef.current;
|
|
691
|
-
if (el === null) return;
|
|
692
|
-
if (props.granularity !== lastGranRef.current) {
|
|
693
|
-
lastGranRef.current = props.granularity;
|
|
694
|
-
scrolledOnce.current = false;
|
|
695
|
-
}
|
|
696
|
-
if (!scrolledOnce.current) {
|
|
697
|
-
scrolledOnce.current = true;
|
|
698
|
-
el.scrollLeft = el.scrollWidth;
|
|
699
|
-
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
700
|
-
el.scrollLeft = el.scrollWidth;
|
|
701
|
-
}
|
|
702
|
-
updateEdges(el);
|
|
703
|
-
});
|
|
704
|
-
const tipOf = (req) => {
|
|
705
|
-
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 });
|
|
706
|
-
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) }) : "");
|
|
707
|
-
};
|
|
708
|
-
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
709
|
-
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
710
|
-
return h(
|
|
711
|
-
"div",
|
|
712
|
-
{ className: "lc-chartrow" },
|
|
713
|
-
h(
|
|
714
|
-
"div",
|
|
715
|
-
{ className: "lc-axis" },
|
|
716
|
-
h("span", { className: "lc-axis-top" }, fmt3(maxTotal)),
|
|
717
|
-
h("span", { className: "lc-axis-mid" }, fmt3(Math.round(maxTotal / 2))),
|
|
718
|
-
h("span", { className: "lc-axis-bot" }, "0")
|
|
719
|
-
),
|
|
720
|
-
h(
|
|
721
|
-
"div",
|
|
722
|
-
{
|
|
723
|
-
// Turn-aware dim scope: while a turn is focused (bar or strip hover),
|
|
724
|
-
// bars and strip blocks OUTSIDE the active turn fade to 35%.
|
|
725
|
-
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
726
|
-
ref: scrollRef,
|
|
727
|
-
onScroll: (e) => {
|
|
728
|
-
updateEdges(e.currentTarget);
|
|
729
|
-
}
|
|
730
|
-
},
|
|
731
|
-
// Edge fades: visible whenever more history sits beyond the viewport,
|
|
732
|
-
// so the horizontal scroll affordance is obvious.
|
|
733
|
-
edges.left ? h("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
734
|
-
// Hovering a bar previews it in the detail below; leaving the plot
|
|
735
|
-
// clears the preview (a pinned selection, if any, takes over again).
|
|
736
|
-
h(
|
|
737
|
-
"div",
|
|
738
|
-
{
|
|
739
|
-
className: "lc-chart",
|
|
740
|
-
onMouseLeave: () => {
|
|
741
|
-
props.onHover(null);
|
|
742
|
-
}
|
|
743
|
-
},
|
|
744
|
-
h("div", { className: "lc-grid lc-grid-top" }),
|
|
745
|
-
h("div", { className: "lc-grid lc-grid-mid" }),
|
|
746
|
-
requests.map((req, i) => {
|
|
747
|
-
const marker = markers[i];
|
|
748
|
-
const markerAt = marker !== void 0 ? eventAt(marker) : null;
|
|
749
|
-
const selected = props.selectedSeq === req.seq;
|
|
750
|
-
const hovered = props.hoveredSeq === req.seq;
|
|
751
|
-
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
752
|
-
return h(
|
|
753
|
-
"div",
|
|
754
|
-
{
|
|
755
|
-
key: req.seq,
|
|
756
|
-
// Uniform column width in BOTH granularities: turn aggregates
|
|
757
|
-
// keep the same fixed width as step bars.
|
|
758
|
-
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
759
|
-
style: { width: BAR_W + "px" },
|
|
760
|
-
onClick: () => {
|
|
761
|
-
props.onSelect(selected ? null : req.seq);
|
|
762
|
-
},
|
|
763
|
-
onMouseEnter: () => {
|
|
764
|
-
props.onHover(req.seq);
|
|
765
|
-
}
|
|
766
|
-
},
|
|
767
|
-
// The ✂ tooltip names the event AND where it happened: the
|
|
768
|
-
// gap between the request before and the request after.
|
|
769
|
-
marker !== void 0 ? h("span", {
|
|
770
|
-
className: "lc-bar-marker",
|
|
771
|
-
title: "\u2702 " + (markerAt !== null ? markerAt + " \u2014 " : "") + eventLabel(marker)
|
|
772
|
-
}, "\u2702") : null,
|
|
773
|
-
h(
|
|
774
|
-
"div",
|
|
775
|
-
{ className: "lc-bar-stack" },
|
|
776
|
-
CATS.map((c) => {
|
|
777
|
-
const v = (req[c.key] || 0) * anchorOf(req);
|
|
778
|
-
if (!v) return null;
|
|
779
|
-
return h("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
780
|
-
})
|
|
781
|
-
)
|
|
782
|
-
);
|
|
783
|
-
})
|
|
784
|
-
),
|
|
785
|
-
// Instant hover tooltip, glued to its bar's column (it lives in the
|
|
786
|
-
// scrolling content, so it follows the bar while the chart scrolls).
|
|
787
|
-
hoveredReq !== null ? h("div", {
|
|
788
|
-
className: "lc-chart-tip",
|
|
789
|
-
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
790
|
-
}, tipOf(hoveredReq)) : null,
|
|
791
|
-
// Turn strip: one COLOR BLOCK per turn, spanning exactly its bars'
|
|
792
|
-
// columns, so the partition reads at a glance and lines up with the
|
|
793
|
-
// steps above. Hovering a block highlights that turn's bars in the
|
|
794
|
-
// chart (and hovering a bar highlights its block — the active turn
|
|
795
|
-
// is shared hover-only state).
|
|
796
|
-
h(
|
|
797
|
-
"div",
|
|
798
|
-
{ className: "lc-turns", onMouseLeave: () => {
|
|
799
|
-
props.onHoverTurn(null);
|
|
800
|
-
} },
|
|
801
|
-
groups.map((grp, gi) => {
|
|
802
|
-
const on = props.activeTurn === grp.turn;
|
|
803
|
-
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
804
|
-
return h("span", {
|
|
805
|
-
key: "turn-" + gi,
|
|
806
|
-
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
807
|
-
style: {
|
|
808
|
-
width: blockW + "px",
|
|
809
|
-
background: TURN_COLORS[gi % TURN_COLORS.length]
|
|
810
|
-
},
|
|
811
|
-
title: "T" + grp.turn,
|
|
812
|
-
onMouseEnter: () => {
|
|
813
|
-
props.onHoverTurn(grp.turn);
|
|
814
|
-
}
|
|
815
|
-
}, "T" + grp.turn);
|
|
816
|
-
})
|
|
817
|
-
)
|
|
818
|
-
),
|
|
819
|
-
edges.right ? h("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null
|
|
820
|
-
);
|
|
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))));
|
|
821
896
|
};
|
|
822
897
|
}
|
|
823
898
|
|
|
824
|
-
// src/client/components/contextView.
|
|
899
|
+
// src/client/components/contextView.tsx
|
|
825
900
|
var viewScroll = /* @__PURE__ */ new Map();
|
|
826
901
|
function makeContextView(ctx, kit) {
|
|
827
902
|
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
|
|
@@ -834,19 +909,13 @@ function makeContextView(ctx, kit) {
|
|
|
834
909
|
const StatsBoard = makeStatsBoard(kit);
|
|
835
910
|
return function ContextView(props) {
|
|
836
911
|
const sessionId = props.sessionId;
|
|
837
|
-
const
|
|
838
|
-
const [data, setData] = React.useState(initial);
|
|
839
|
-
const [error, setError] = React.useState(null);
|
|
912
|
+
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
840
913
|
const [selectedSeq, setSelectedSeq] = React.useState(null);
|
|
841
914
|
const [hoveredSeq, setHoveredSeq] = React.useState(null);
|
|
842
915
|
const [hoverTurn, setHoverTurn] = React.useState(null);
|
|
843
916
|
const [tick, setTick] = React.useState(0);
|
|
844
917
|
const [granularity, setGranularity] = React.useState("step");
|
|
845
918
|
const [hoverCat, setHoverCat] = React.useState(null);
|
|
846
|
-
const dataRef = React.useRef(initial);
|
|
847
|
-
React.useEffect(() => {
|
|
848
|
-
dataRef.current = data;
|
|
849
|
-
}, [data]);
|
|
850
919
|
const rootRef = React.useRef(null);
|
|
851
920
|
const scrollerRef = React.useRef(null);
|
|
852
921
|
const restoredRef = React.useRef(null);
|
|
@@ -867,53 +936,15 @@ function makeContextView(ctx, kit) {
|
|
|
867
936
|
viewScroll.set(sessionId, scroller.scrollTop);
|
|
868
937
|
};
|
|
869
938
|
}, [sessionId]);
|
|
870
|
-
React.useEffect(() => {
|
|
871
|
-
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
872
|
-
let alive = true;
|
|
873
|
-
const load = () => {
|
|
874
|
-
ctx.connection.rpc.call("/dsh-context", "snapshot", { sessionId }).then((res) => {
|
|
875
|
-
if (!alive) return;
|
|
876
|
-
if (res && res.ok) {
|
|
877
|
-
const snap = res.value;
|
|
878
|
-
cachePut(sessionId, snap);
|
|
879
|
-
setData(snap);
|
|
880
|
-
setError(null);
|
|
881
|
-
} else if (dataRef.current === null) {
|
|
882
|
-
setError(res && res.error ? String(res.error.message || res.error.code) : "failed");
|
|
883
|
-
}
|
|
884
|
-
}, (err) => {
|
|
885
|
-
if (alive && dataRef.current === null) {
|
|
886
|
-
setError(String(err instanceof Error ? err.message : err));
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
};
|
|
890
|
-
load();
|
|
891
|
-
const timerId = setInterval(() => {
|
|
892
|
-
if (document.visibilityState !== "hidden") load();
|
|
893
|
-
}, 2e3);
|
|
894
|
-
const onVisible = () => {
|
|
895
|
-
if (document.visibilityState === "visible") load();
|
|
896
|
-
};
|
|
897
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
898
|
-
return () => {
|
|
899
|
-
alive = false;
|
|
900
|
-
clearInterval(timerId);
|
|
901
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
902
|
-
};
|
|
903
|
-
}, [sessionId]);
|
|
904
939
|
React.useEffect(() => {
|
|
905
940
|
const localeSvc = ctx.get("locale");
|
|
906
941
|
if (!localeSvc) return void 0;
|
|
907
942
|
return localeSvc.subscribe(() => setTick((x) => x + 1));
|
|
908
943
|
}, []);
|
|
909
944
|
void tick;
|
|
910
|
-
if (error) {
|
|
911
|
-
return h("div", { className: "lc-root", ref: rootRef }, h("div", { className: "lc-empty" }, t("error") + error));
|
|
912
|
-
}
|
|
913
945
|
if (!data) {
|
|
914
|
-
return
|
|
946
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")));
|
|
915
947
|
}
|
|
916
|
-
const current = data.current;
|
|
917
948
|
const requests = data.requests || [];
|
|
918
949
|
const events = data.events || [];
|
|
919
950
|
const nodes = data.nodes || [];
|
|
@@ -938,125 +969,42 @@ function makeContextView(ctx, kit) {
|
|
|
938
969
|
break;
|
|
939
970
|
}
|
|
940
971
|
}
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
h(Legend, { parts, hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
978
|
-
data.toolList && data.toolList.length > 0 ? h(
|
|
979
|
-
"div",
|
|
980
|
-
{ className: "lc-tools" },
|
|
981
|
-
t("tools.top"),
|
|
982
|
-
data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
|
|
983
|
-
return h("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
|
|
984
|
-
}),
|
|
985
|
-
data.toolList.length > 5 ? h("span", { className: "lc-card-sub" }, " " + tr("tools.more", { n: data.toolList.length })) : null
|
|
986
|
-
) : null
|
|
987
|
-
),
|
|
988
|
-
// ---- trend ----
|
|
989
|
-
h(
|
|
990
|
-
"div",
|
|
991
|
-
{ className: "lc-card" },
|
|
992
|
-
h(
|
|
993
|
-
"div",
|
|
994
|
-
{ className: "lc-card-title" },
|
|
995
|
-
t("trend.title"),
|
|
996
|
-
h("span", { className: "lc-card-sub" }, t("trend.hint")),
|
|
997
|
-
h(
|
|
998
|
-
"div",
|
|
999
|
-
{ className: "lc-gran" },
|
|
1000
|
-
h("button", {
|
|
1001
|
-
className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
|
|
1002
|
-
onClick: () => {
|
|
1003
|
-
setGranularity("step");
|
|
1004
|
-
}
|
|
1005
|
-
}, t("gran.step")),
|
|
1006
|
-
h("button", {
|
|
1007
|
-
className: "lc-gran-btn" + (granularity === "turn" ? " lc-gran-on" : ""),
|
|
1008
|
-
onClick: () => {
|
|
1009
|
-
setGranularity("turn");
|
|
1010
|
-
}
|
|
1011
|
-
}, t("gran.turn"))
|
|
1012
|
-
)
|
|
1013
|
-
),
|
|
1014
|
-
displayRequests.length === 0 ? h("div", { className: "lc-empty" }, t("trend.empty")) : h(
|
|
1015
|
-
"div",
|
|
1016
|
-
null,
|
|
1017
|
-
h(TrendChart, {
|
|
1018
|
-
// Remount per session: switching sessions re-anchors the chart
|
|
1019
|
-
// at the newest bars instead of inheriting stale scroll state.
|
|
1020
|
-
key: sessionId,
|
|
1021
|
-
// The host caps the log at 160 requests; render them ALL so
|
|
1022
|
-
// earlier turns/steps stay reachable via horizontal scroll.
|
|
1023
|
-
requests: displayRequests,
|
|
1024
|
-
markers,
|
|
1025
|
-
selectedSeq: pinnedReq ? pinnedReq.seq : null,
|
|
1026
|
-
hoveredSeq,
|
|
1027
|
-
activeTurn,
|
|
1028
|
-
granularity,
|
|
1029
|
-
onSelect: setSelectedSeq,
|
|
1030
|
-
onHover: setHoveredSeq,
|
|
1031
|
-
onHoverTurn: setHoverTurn
|
|
1032
|
-
}),
|
|
1033
|
-
h(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 })
|
|
1034
|
-
)
|
|
1035
|
-
),
|
|
1036
|
-
// ---- events + messages ----
|
|
1037
|
-
h(
|
|
1038
|
-
"div",
|
|
1039
|
-
{ className: "lc-cols" },
|
|
1040
|
-
h(
|
|
1041
|
-
"div",
|
|
1042
|
-
{ className: "lc-card lc-col" },
|
|
1043
|
-
h("div", { className: "lc-card-title" }, t("events.title")),
|
|
1044
|
-
h(EventList, { events })
|
|
1045
|
-
),
|
|
1046
|
-
h(
|
|
1047
|
-
"div",
|
|
1048
|
-
{ className: "lc-card lc-col" },
|
|
1049
|
-
h(
|
|
1050
|
-
"div",
|
|
1051
|
-
{ className: "lc-card-title" },
|
|
1052
|
-
t("nodes.title"),
|
|
1053
|
-
h("span", { className: "lc-card-sub" }, t("nodes.hint"))
|
|
1054
|
-
),
|
|
1055
|
-
h(NodeList, { nodes, dropped: data.droppedNodes || 0 })
|
|
1056
|
-
)
|
|
1057
|
-
),
|
|
1058
|
-
h("div", { className: "lc-foot" }, t("footer"))
|
|
1059
|
-
);
|
|
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) => {
|
|
974
|
+
return /* @__PURE__ */ React.createElement("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
|
|
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(
|
|
976
|
+
"button",
|
|
977
|
+
{
|
|
978
|
+
className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
|
|
979
|
+
onClick: () => {
|
|
980
|
+
setGranularity("step");
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
t("gran.step")
|
|
984
|
+
), /* @__PURE__ */ React.createElement(
|
|
985
|
+
"button",
|
|
986
|
+
{
|
|
987
|
+
className: "lc-gran-btn" + (granularity === "turn" ? " lc-gran-on" : ""),
|
|
988
|
+
onClick: () => {
|
|
989
|
+
setGranularity("turn");
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
t("gran.turn")
|
|
993
|
+
))), displayRequests.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
|
|
994
|
+
TrendChart,
|
|
995
|
+
{
|
|
996
|
+
key: sessionId,
|
|
997
|
+
requests: displayRequests,
|
|
998
|
+
markers,
|
|
999
|
+
selectedSeq: pinnedReq ? pinnedReq.seq : null,
|
|
1000
|
+
hoveredSeq,
|
|
1001
|
+
activeTurn,
|
|
1002
|
+
granularity,
|
|
1003
|
+
onSelect: setSelectedSeq,
|
|
1004
|
+
onHover: setHoveredSeq,
|
|
1005
|
+
onHoverTurn: setHoverTurn
|
|
1006
|
+
}
|
|
1007
|
+
), /* @__PURE__ */ React.createElement(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("events.title")), /* @__PURE__ */ React.createElement(EventList, { events })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("nodes.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, { nodes, dropped: data.droppedNodes || 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
|
|
1060
1008
|
};
|
|
1061
1009
|
}
|
|
1062
1010
|
|
|
@@ -1075,11 +1023,12 @@ function makeViewKit(t) {
|
|
|
1075
1023
|
}
|
|
1076
1024
|
|
|
1077
1025
|
// src/client/index.ts
|
|
1026
|
+
var NS = "dsh-context";
|
|
1078
1027
|
function apply(ctx) {
|
|
1079
1028
|
ctx.effect(() => {
|
|
1080
|
-
return ctx.locale.register(
|
|
1029
|
+
return ctx.locale.register(NS, { zh: DICT_ZH, en: DICT_EN });
|
|
1081
1030
|
}, "dsh-context: dictionaries");
|
|
1082
|
-
const t = ctx.locale.bind(
|
|
1031
|
+
const t = ctx.locale.bind(NS);
|
|
1083
1032
|
ctx.effect(() => {
|
|
1084
1033
|
const tag = document.createElement("style");
|
|
1085
1034
|
tag.setAttribute("data-plugin", "dsh-context");
|
|
@@ -1089,18 +1038,34 @@ function apply(ctx) {
|
|
|
1089
1038
|
if (tag.parentNode !== null) tag.parentNode.removeChild(tag);
|
|
1090
1039
|
};
|
|
1091
1040
|
}, "dsh-context: styles");
|
|
1092
|
-
const
|
|
1041
|
+
const kit = makeViewKit(t);
|
|
1042
|
+
const ContextView = makeContextView(ctx, kit);
|
|
1093
1043
|
ctx.slots.inject("conversation.view", () => {
|
|
1094
1044
|
return ctx.slots.register(
|
|
1095
|
-
// order 20 renders right of Chat (0) and Trajectory (10)
|
|
1096
|
-
|
|
1045
|
+
// order 20 renders right of Chat (0) and Trajectory (10); the locale
|
|
1046
|
+
// namespace put the framework `t` seat on the component's props too.
|
|
1047
|
+
{ name: "conversation.view", id: "context", order: 20, locale: NS, label: () => t("tab") },
|
|
1097
1048
|
(props) => h(ContextView, props)
|
|
1098
1049
|
);
|
|
1099
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
|
+
});
|
|
1100
1065
|
}
|
|
1101
1066
|
module.exports = {
|
|
1102
1067
|
name: "dsh-context",
|
|
1103
|
-
inject: ["
|
|
1068
|
+
inject: ["slots", "locale"],
|
|
1104
1069
|
apply
|
|
1105
1070
|
};
|
|
1106
1071
|
|