dsh-context 0.16.0 → 0.17.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 +3 -1
- package/lib/client.js +893 -815
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -20,6 +20,7 @@ var DICT_ZH = {
|
|
|
20
20
|
"overview.free": "\u5269\u4F59\u7A97\u53E3",
|
|
21
21
|
"overview.used": "\u4E0A\u4E0B\u6587\u5DF2\u7528",
|
|
22
22
|
"overview.ofUsed": "\u5360\u5DF2\u7528\u4E0A\u4E0B\u6587",
|
|
23
|
+
"overview.compactReserve": "\u81EA\u52A8\u538B\u7F29\u9884\u7559\uFF1A\u5360\u7528\u8FBE {pct}% \u7A97\u53E3\u65F6\u89E6\u53D1\u538B\u7F29\uFF0C\u6B64\u533A\u57DF\u4E00\u822C\u4E0D\u5B9E\u9645\u5360\u7528",
|
|
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",
|
|
@@ -27,6 +28,7 @@ var DICT_ZH = {
|
|
|
27
28
|
"stats.injects": "\u6CE8\u5165",
|
|
28
29
|
"stats.compactions": "\u538B\u7F29",
|
|
29
30
|
"stats.prunes": "\u526A\u679D",
|
|
31
|
+
"stats.cacheHit": "\u7F13\u5B58\u547D\u4E2D",
|
|
30
32
|
"plugin.title": "\u63D2\u4EF6\u4FE1\u606F",
|
|
31
33
|
"plugin.hint": "The best DSH context plugin \u2B50",
|
|
32
34
|
"plugin.name": "\u63D2\u4EF6",
|
|
@@ -80,7 +82,7 @@ var DICT_ZH = {
|
|
|
80
82
|
"node.empty": "(\u7A7A\u56DE\u590D)",
|
|
81
83
|
"node.nonText": "(\u975E\u6587\u672C\u6D88\u606F)",
|
|
82
84
|
"node.snapshot": "\u5FEB\u7167: ",
|
|
83
|
-
"cmd.desc": "\u67E5\u770B\u4E0A\u4E0B\u6587\
|
|
85
|
+
"cmd.desc": "\u67E5\u770B\u5F53\u524D\u4E0A\u4E0B\u6587\u6784\u6210\uFF0C\u6D4F\u89C8\u5404\u6B65\u9AA4\u7EC4\u6210",
|
|
84
86
|
"cmd.close": "\u5173\u95ED",
|
|
85
87
|
"browser.title": "\u4E0A\u4E0B\u6587\u6D4F\u89C8\u5668",
|
|
86
88
|
"browser.live": "\u5F53\u524D\uFF08\u4E0B\u4E00\u6B21\u8BF7\u6C42\uFF09",
|
|
@@ -88,6 +90,7 @@ var DICT_ZH = {
|
|
|
88
90
|
"browser.items": "{n} \u9879",
|
|
89
91
|
"browser.missingLive": "\u2026 \u53E6\u6709 {n} \u6761\u66F4\u65E9\u7684\u6D88\u606F\u4E5F\u5728\u4E0A\u4E0B\u6587\u4E2D\uFF08\u8D85\u51FA\u5C55\u793A\u7A97\u53E3\uFF09",
|
|
90
92
|
"browser.approx": "\u8BE5\u6B65\u9AA4\u6D89\u53CA\u7684\u90E8\u5206\u5DF2\u79FB\u9664\u6D88\u606F\u8D85\u51FA\u4FDD\u7559\u8303\u56F4\uFF0C\u4EE5\u4E0B\u4E3A\u8FD1\u4F3C\u6784\u6210",
|
|
93
|
+
"browser.deltaHint": "\u5BF9\u6BD4\u4E0A\u8F6E\u672B\u6B65\u7684\u53D8\u52A8",
|
|
91
94
|
"browser.noHeader": "\u6B64\u6570\u636E\u6765\u81EA\u65E7\u7248\u63D2\u4EF6\uFF1A\u4EC5\u63D0\u4F9B token \u4F30\u7B97\uFF0C\u65E0\u5B9E\u9645\u5185\u5BB9",
|
|
92
95
|
"browser.noEpoch": "\u8BE5\u6B65\u9AA4\u7684\u5934\u90E8\u5185\u5BB9\uFF08\u7CFB\u7EDF\u63D0\u793A\u8BCD / \u5DE5\u5177\u5B9A\u4E49\uFF09\u4E0D\u5728\u4FDD\u7559\u8303\u56F4\u5185",
|
|
93
96
|
"browser.noContent": "\u5B8C\u6574\u5185\u5BB9\u4E0D\u5728\u5F53\u524D\u52A0\u8F7D\u7684\u6D88\u606F\u7A97\u53E3\u5185\uFF08\u5728\u804A\u5929\u9875\u52A0\u8F7D\u66F4\u65E9\u5386\u53F2\u540E\u53EF\u67E5\u770B\uFF09",
|
|
@@ -115,6 +118,7 @@ var DICT_EN = {
|
|
|
115
118
|
"overview.free": "Free Window",
|
|
116
119
|
"overview.used": "of context used",
|
|
117
120
|
"overview.ofUsed": "of used context",
|
|
121
|
+
"overview.compactReserve": "Auto-compaction reserve: auto-compaction triggers at {pct}% of the window \u2014 this area is normally kept as headroom, not actually used",
|
|
118
122
|
"stats.title": "Context Stats",
|
|
119
123
|
"stats.hint": "Over the retained history window (same as the History chart)",
|
|
120
124
|
"stats.turns": "Turns",
|
|
@@ -122,6 +126,7 @@ var DICT_EN = {
|
|
|
122
126
|
"stats.injects": "Injections",
|
|
123
127
|
"stats.compactions": "Compactions",
|
|
124
128
|
"stats.prunes": "Prunes",
|
|
129
|
+
"stats.cacheHit": "Cache Hits",
|
|
125
130
|
"plugin.title": "Plugin Info",
|
|
126
131
|
"plugin.hint": "The best DSH context plugin \u2B50",
|
|
127
132
|
"plugin.name": "Plugin",
|
|
@@ -175,7 +180,7 @@ var DICT_EN = {
|
|
|
175
180
|
"node.empty": "(empty reply)",
|
|
176
181
|
"node.nonText": "(non-text message)",
|
|
177
182
|
"node.snapshot": "Snapshot: ",
|
|
178
|
-
"cmd.desc": "View context makeup
|
|
183
|
+
"cmd.desc": "View current context makeup, browse per-step composition",
|
|
179
184
|
"cmd.close": "Close",
|
|
180
185
|
"browser.title": "Context Browser",
|
|
181
186
|
"browser.live": "Live (Next Request)",
|
|
@@ -183,6 +188,7 @@ var DICT_EN = {
|
|
|
183
188
|
"browser.items": "{n} Items",
|
|
184
189
|
"browser.missingLive": "\u2026 {n} earlier messages are also part of the context (outside the served window)",
|
|
185
190
|
"browser.approx": "Some removed messages of this step exceed retention \u2014 the makeup below is approximate",
|
|
191
|
+
"browser.deltaHint": "vs previous turn",
|
|
186
192
|
"browser.noHeader": "Served by an older plugin build: token estimates only, no content",
|
|
187
193
|
"browser.noEpoch": "The header content (System Prompt / Tool Schemas) of this step is outside retention",
|
|
188
194
|
"browser.noContent": "Full content is outside the loaded message window (load older history in Chat to view)",
|
|
@@ -310,751 +316,112 @@ function asRecord(value) {
|
|
|
310
316
|
}
|
|
311
317
|
var timelineOf = (value) => asRecord(value);
|
|
312
318
|
var contextPressureOf = (value) => asRecord(value);
|
|
319
|
+
var tokenUsageOf = (value) => asRecord(value);
|
|
313
320
|
function headersOf(value) {
|
|
314
321
|
const headers = asRecord(value);
|
|
315
322
|
return headers !== null && Array.isArray(headers.headers) ? headers : null;
|
|
316
323
|
}
|
|
317
324
|
|
|
325
|
+
// src/client/assemble.ts
|
|
326
|
+
function headerAt(headers, seq) {
|
|
327
|
+
if (headers === null || headers.headers.length === 0) return null;
|
|
328
|
+
if (seq === null) return headers.headers[headers.headers.length - 1];
|
|
329
|
+
for (let i = headers.headers.length - 1; i >= 0; i--) {
|
|
330
|
+
if (headers.headers[i].seq < seq) return headers.headers[i];
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
function assemble(data, headers, seq) {
|
|
335
|
+
const live = seq === null;
|
|
336
|
+
let nodes;
|
|
337
|
+
if (live) {
|
|
338
|
+
nodes = data.nodes.slice();
|
|
339
|
+
} else {
|
|
340
|
+
const picked = [];
|
|
341
|
+
for (const n of data.nodes) {
|
|
342
|
+
if (n.seq < seq) picked.push(n);
|
|
343
|
+
}
|
|
344
|
+
for (const n of data.archive || []) {
|
|
345
|
+
if (n.seq < seq && n.gone !== void 0 && n.gone > seq) picked.push(n);
|
|
346
|
+
}
|
|
347
|
+
nodes = picked;
|
|
348
|
+
}
|
|
349
|
+
nodes.sort((a, b) => a.seq - b.seq);
|
|
350
|
+
let missingLive = 0;
|
|
351
|
+
if (data.droppedNodes > 0) {
|
|
352
|
+
if (live || data.surfaceFloor !== void 0 && seq > data.surfaceFloor) {
|
|
353
|
+
missingLive = data.droppedNodes;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const approximate = !live && data.archiveFloor !== void 0 && seq < data.archiveFloor;
|
|
357
|
+
return { live, header: headerAt(headers, seq), nodes, missingLive, approximate };
|
|
358
|
+
}
|
|
359
|
+
|
|
318
360
|
// src/client/react.ts
|
|
319
361
|
var React = require("react");
|
|
320
362
|
var h = React.createElement;
|
|
321
363
|
|
|
322
|
-
// src/client/components/
|
|
323
|
-
function
|
|
324
|
-
const { t
|
|
325
|
-
return function
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
329
|
-
const head = isTurn ? t("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
330
|
-
const marker = props.marker ?? null;
|
|
331
|
-
const markerAt = marker !== null ? eventAt(marker) : null;
|
|
332
|
-
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, t("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, t("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) => {
|
|
333
|
-
const v = req[c.key] || 0;
|
|
334
|
-
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) + "%" : ""));
|
|
335
|
-
})));
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// src/client/components/stackedBar.tsx
|
|
340
|
-
function makeStackedBar(kit) {
|
|
341
|
-
const { t, fmt: fmt3, catLabel } = kit;
|
|
342
|
-
return function StackedBar(props) {
|
|
343
|
-
let total = 0;
|
|
344
|
-
for (const p of props.parts) total += p.value;
|
|
345
|
-
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
346
|
-
const free = props.max !== void 0 && props.max > total ? props.max - total : 0;
|
|
347
|
-
const usedPct = scale > 0 ? total / scale * 100 : 0;
|
|
348
|
-
const hovering = props.hoverKey !== null && props.hoverKey !== void 0;
|
|
349
|
-
const showBox = free > 0 && hovering;
|
|
350
|
-
let tip = null;
|
|
351
|
-
if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
352
|
-
if (props.hoverKey === "free" && free > 0) {
|
|
353
|
-
const pct = scale > 0 ? free / scale * 100 : 0;
|
|
354
|
-
tip = {
|
|
355
|
-
text: t("overview.free") + " " + fmt3(free) + " (" + Math.round(pct) + "%)",
|
|
356
|
-
leftPct: Math.max(12, Math.min(total / scale * 100 + pct / 2, 88))
|
|
357
|
-
};
|
|
358
|
-
} else {
|
|
359
|
-
let acc = 0;
|
|
360
|
-
for (const p of props.parts) {
|
|
361
|
-
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
362
|
-
if (p.key === props.hoverKey && p.value > 0) {
|
|
363
|
-
tip = {
|
|
364
|
-
// "(pct%)" is a share of the OCCUPIED total — the dashed box
|
|
365
|
-
// that appears on hover frames exactly this reference region.
|
|
366
|
-
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + t("overview.ofUsed"),
|
|
367
|
-
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
368
|
-
};
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
acc += pct;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
364
|
+
// src/client/components/nodes.tsx
|
|
365
|
+
function makeNodeText(kit) {
|
|
366
|
+
const { t } = kit;
|
|
367
|
+
return function nodeText(n) {
|
|
368
|
+
if (n.cat === "tool") {
|
|
369
|
+
return t("node.toolResult") + (n.tool ? " \u2190 " + n.tool : "") + (n.err ? " \u26A0" : "");
|
|
374
370
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
total > 0 ? props.parts.map((p) => {
|
|
385
|
-
if (!p.value) return null;
|
|
386
|
-
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
387
|
-
return /* @__PURE__ */ React.createElement(
|
|
388
|
-
"div",
|
|
389
|
-
{
|
|
390
|
-
key: p.key,
|
|
391
|
-
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
392
|
-
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
393
|
-
onMouseEnter: () => {
|
|
394
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
);
|
|
398
|
-
}) : null,
|
|
399
|
-
free > 0 ? /* @__PURE__ */ React.createElement(
|
|
400
|
-
"div",
|
|
401
|
-
{
|
|
402
|
-
key: "free",
|
|
403
|
-
className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
|
|
404
|
-
style: { width: free / scale * 100 + "%" },
|
|
405
|
-
onMouseEnter: () => {
|
|
406
|
-
if (props.onHoverKey !== void 0) props.onHoverKey("free");
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
) : null,
|
|
410
|
-
/* @__PURE__ */ React.createElement(
|
|
411
|
-
"div",
|
|
412
|
-
{
|
|
413
|
-
className: "lc-occupied-box" + (showBox ? " lc-occupied-box-on" : ""),
|
|
414
|
-
style: { width: usedPct + "%" }
|
|
415
|
-
}
|
|
416
|
-
)
|
|
417
|
-
), props.tip !== false ? /* @__PURE__ */ React.createElement(
|
|
418
|
-
"div",
|
|
419
|
-
{
|
|
420
|
-
className: "lc-bar-tip" + (tip ? " lc-bar-tip-on" : ""),
|
|
421
|
-
style: { left: tip ? tip.leftPct + "%" : "50%" }
|
|
422
|
-
},
|
|
423
|
-
tip ? tip.text : ""
|
|
424
|
-
) : null);
|
|
371
|
+
if (n.skill) return "Skill: " + n.skill;
|
|
372
|
+
if (n.calls) return t("node.calls") + n.calls.join(", ");
|
|
373
|
+
if (n.text) return n.form === "snapshot" ? t("node.snapshot") + n.text : n.text;
|
|
374
|
+
if (n.cat === "assistant") return t("node.empty");
|
|
375
|
+
if (n.cat === "inject") return t("form." + (n.form || "context"));
|
|
376
|
+
return t("node.nonText");
|
|
425
377
|
};
|
|
426
378
|
}
|
|
427
|
-
function
|
|
428
|
-
const { t, fmt: fmt3,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
onMouseEnter: () => {
|
|
441
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
442
|
-
},
|
|
443
|
-
onMouseLeave: () => {
|
|
444
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
/* @__PURE__ */ React.createElement("i", { style: { background: p.color } }),
|
|
448
|
-
catLabel(p.key) + " \u2248" + fmt3(p.value),
|
|
449
|
-
total > 0 ? /* @__PURE__ */ React.createElement("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
450
|
-
);
|
|
379
|
+
function makeNodeList(kit) {
|
|
380
|
+
const { t, fmt: fmt3, fmtTime: fmtTime2 } = kit;
|
|
381
|
+
const nodeText = makeNodeText(kit);
|
|
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" }, t("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)));
|
|
451
392
|
}));
|
|
452
393
|
};
|
|
453
394
|
}
|
|
454
395
|
|
|
455
|
-
// src/client/components/
|
|
456
|
-
function
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
} else {
|
|
465
|
-
runSteps = 1;
|
|
466
|
-
out.push({ ...req, stepCount: 1 });
|
|
467
|
-
}
|
|
396
|
+
// src/client/components/browser.tsx
|
|
397
|
+
function unionTypesOf(p) {
|
|
398
|
+
const branches = [];
|
|
399
|
+
if (Array.isArray(p.anyOf)) branches.push(...p.anyOf);
|
|
400
|
+
if (Array.isArray(p.oneOf)) branches.push(...p.oneOf);
|
|
401
|
+
if (branches.length === 0) return null;
|
|
402
|
+
const parts = [];
|
|
403
|
+
for (const b of branches) {
|
|
404
|
+
if (b !== null && typeof b === "object") parts.push(typeOf(b));
|
|
468
405
|
}
|
|
469
|
-
return
|
|
406
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
470
407
|
}
|
|
471
|
-
function
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
408
|
+
function typeOf(p) {
|
|
409
|
+
const u = unionTypesOf(p);
|
|
410
|
+
if (u !== null) return u;
|
|
411
|
+
const t = p.type;
|
|
412
|
+
if (t === "array") {
|
|
413
|
+
const items = p.items;
|
|
414
|
+
if (items !== null && typeof items === "object") {
|
|
415
|
+
const inner = typeOf(items);
|
|
416
|
+
return "array<" + inner + ">";
|
|
480
417
|
}
|
|
418
|
+
return "array";
|
|
481
419
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const BAR_W = 14;
|
|
488
|
-
const BAR_GAP = 2;
|
|
489
|
-
const TURN_FILLS = ["rgba(128,128,128,0.12)", "rgba(128,128,128,0.26)"];
|
|
490
|
-
return function TrendChart(props) {
|
|
491
|
-
const requests = props.requests;
|
|
492
|
-
const markers = props.markers;
|
|
493
|
-
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
494
|
-
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
495
|
-
let maxTotal = 1;
|
|
496
|
-
for (const req of requests) {
|
|
497
|
-
const bt = barTotalOf(req);
|
|
498
|
-
if (bt > maxTotal) maxTotal = bt;
|
|
499
|
-
}
|
|
500
|
-
const groups = [];
|
|
501
|
-
for (const req of requests) {
|
|
502
|
-
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
503
|
-
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
504
|
-
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
505
|
-
groups.push(grp);
|
|
506
|
-
}
|
|
507
|
-
grp.count++;
|
|
508
|
-
grp.span += req.stepCount ?? 1;
|
|
509
|
-
}
|
|
510
|
-
const scrollRef = React.useRef(null);
|
|
511
|
-
const scrolledOnce = React.useRef(false);
|
|
512
|
-
const lastGranRef = React.useRef(props.granularity);
|
|
513
|
-
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
514
|
-
const edgesRef = React.useRef(edges);
|
|
515
|
-
const updateEdges = (el) => {
|
|
516
|
-
const left = el.scrollLeft > 4;
|
|
517
|
-
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
518
|
-
const prev = edgesRef.current;
|
|
519
|
-
if (prev.left === left && prev.right === right) return;
|
|
520
|
-
edgesRef.current = { left, right };
|
|
521
|
-
setEdges({ left, right });
|
|
522
|
-
};
|
|
523
|
-
React.useLayoutEffect(() => {
|
|
524
|
-
const el = scrollRef.current;
|
|
525
|
-
if (el === null) return;
|
|
526
|
-
if (props.granularity !== lastGranRef.current) {
|
|
527
|
-
lastGranRef.current = props.granularity;
|
|
528
|
-
scrolledOnce.current = false;
|
|
529
|
-
}
|
|
530
|
-
if (!scrolledOnce.current) {
|
|
531
|
-
scrolledOnce.current = true;
|
|
532
|
-
el.scrollLeft = el.scrollWidth;
|
|
533
|
-
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
534
|
-
el.scrollLeft = el.scrollWidth;
|
|
535
|
-
}
|
|
536
|
-
updateEdges(el);
|
|
537
|
-
});
|
|
538
|
-
const tipOf = (req) => {
|
|
539
|
-
const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : t("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
540
|
-
return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + t("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + t("tip.actual", { n: fmt3(req.prompt) }) : "");
|
|
541
|
-
};
|
|
542
|
-
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
543
|
-
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
544
|
-
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(
|
|
545
|
-
"div",
|
|
546
|
-
{
|
|
547
|
-
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
548
|
-
ref: scrollRef,
|
|
549
|
-
onScroll: (e) => {
|
|
550
|
-
updateEdges(e.currentTarget);
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
|
-
edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
554
|
-
/* @__PURE__ */ React.createElement(
|
|
555
|
-
"div",
|
|
556
|
-
{
|
|
557
|
-
className: "lc-chart",
|
|
558
|
-
onMouseLeave: () => {
|
|
559
|
-
props.onHover(null);
|
|
560
|
-
}
|
|
561
|
-
},
|
|
562
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-top" }),
|
|
563
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-mid" }),
|
|
564
|
-
requests.map((req, i) => {
|
|
565
|
-
const marker = markers[i];
|
|
566
|
-
const markerAt = marker !== void 0 ? eventAt(marker) : null;
|
|
567
|
-
const selected = props.selectedSeq === req.seq;
|
|
568
|
-
const hovered = props.hoveredSeq === req.seq;
|
|
569
|
-
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
570
|
-
return /* @__PURE__ */ React.createElement(
|
|
571
|
-
"div",
|
|
572
|
-
{
|
|
573
|
-
key: req.seq,
|
|
574
|
-
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
575
|
-
style: { width: BAR_W + "px" },
|
|
576
|
-
onClick: () => {
|
|
577
|
-
props.onSelect(selected ? null : req.seq);
|
|
578
|
-
},
|
|
579
|
-
onMouseEnter: () => {
|
|
580
|
-
props.onHover(req.seq);
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
marker !== void 0 ? /* @__PURE__ */ React.createElement(
|
|
584
|
-
"span",
|
|
585
|
-
{
|
|
586
|
-
className: "lc-bar-marker",
|
|
587
|
-
title: "\u2702 " + (markerAt !== null ? markerAt + " \u2014 " : "") + eventLabel(marker)
|
|
588
|
-
},
|
|
589
|
-
"\u2702"
|
|
590
|
-
) : null,
|
|
591
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-bar-stack" }, CATS.map((c) => {
|
|
592
|
-
const v = (req[c.key] || 0) * anchorOf(req);
|
|
593
|
-
if (!v) return null;
|
|
594
|
-
return /* @__PURE__ */ React.createElement("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
595
|
-
}))
|
|
596
|
-
);
|
|
597
|
-
})
|
|
598
|
-
),
|
|
599
|
-
hoveredReq !== null ? /* @__PURE__ */ React.createElement(
|
|
600
|
-
"div",
|
|
601
|
-
{
|
|
602
|
-
className: "lc-chart-tip",
|
|
603
|
-
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
604
|
-
},
|
|
605
|
-
tipOf(hoveredReq)
|
|
606
|
-
) : null,
|
|
607
|
-
/* @__PURE__ */ React.createElement("div", { className: "lc-turns", onMouseLeave: () => {
|
|
608
|
-
props.onHoverTurn(null);
|
|
609
|
-
} }, groups.map((grp, gi) => {
|
|
610
|
-
const on = props.activeTurn === grp.turn;
|
|
611
|
-
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
612
|
-
return /* @__PURE__ */ React.createElement(
|
|
613
|
-
"span",
|
|
614
|
-
{
|
|
615
|
-
key: "turn-" + gi,
|
|
616
|
-
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
617
|
-
style: {
|
|
618
|
-
width: blockW + "px",
|
|
619
|
-
background: TURN_FILLS[gi % TURN_FILLS.length]
|
|
620
|
-
},
|
|
621
|
-
title: "T" + grp.turn,
|
|
622
|
-
onMouseEnter: () => {
|
|
623
|
-
props.onHoverTurn(grp.turn);
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
"T" + grp.turn
|
|
627
|
-
);
|
|
628
|
-
}))
|
|
629
|
-
), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// src/client/components/contextModal.tsx
|
|
634
|
-
var TREND_TURNS = 10;
|
|
635
|
-
function makeContextModal(ctx, kit) {
|
|
636
|
-
const { t, fmt: fmt3 } = kit;
|
|
637
|
-
const sessions = ctx.get("sessions");
|
|
638
|
-
const StackedBar = makeStackedBar(kit);
|
|
639
|
-
const Legend = makeLegend(kit);
|
|
640
|
-
const TrendChart = makeTrendChart(kit);
|
|
641
|
-
const RequestDetail = makeRequestDetail(kit, StackedBar);
|
|
642
|
-
return function ContextModal(props) {
|
|
643
|
-
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
644
|
-
const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
|
|
645
|
-
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
646
|
-
const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
|
|
647
|
-
const [selectedSeq, setSelectedSeq] = React.useState(null);
|
|
648
|
-
const [hoveredSeq, setHoveredSeq] = React.useState(null);
|
|
649
|
-
const [hoverCat, setHoverCat] = React.useState(null);
|
|
650
|
-
const close = React.useCallback(() => {
|
|
651
|
-
if (sessionId === "") return;
|
|
652
|
-
modalStoreOf(sessionId).set(false);
|
|
653
|
-
const guard = takePendingConsume(sessionId);
|
|
654
|
-
if (guard === void 0 || sessions === void 0) return;
|
|
655
|
-
const scope = sessions.scope(sessionId);
|
|
656
|
-
if (scope !== void 0) scope.bail(scope, "slash/input-consume-token", { guard });
|
|
657
|
-
}, [sessionId]);
|
|
658
|
-
React.useEffect(() => {
|
|
659
|
-
if (!open) return void 0;
|
|
660
|
-
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
661
|
-
const onKey = (ev) => {
|
|
662
|
-
if (ev.key !== "Escape") return;
|
|
663
|
-
ev.preventDefault();
|
|
664
|
-
ev.stopPropagation();
|
|
665
|
-
close();
|
|
666
|
-
};
|
|
667
|
-
window.addEventListener("keydown", onKey, true);
|
|
668
|
-
return () => {
|
|
669
|
-
window.removeEventListener("keydown", onKey, true);
|
|
670
|
-
if (previous !== null && document.contains(previous)) previous.focus();
|
|
671
|
-
};
|
|
672
|
-
}, [open, close]);
|
|
673
|
-
if (!open) return null;
|
|
674
|
-
const requests = data !== null ? data.requests || [] : [];
|
|
675
|
-
const events = data !== null ? data.events || [] : [];
|
|
676
|
-
const turns = aggregateByTurn(requests).slice(-TREND_TURNS);
|
|
677
|
-
const markers = attachMarkers(turns, events);
|
|
678
|
-
let pinnedReq = null;
|
|
679
|
-
for (const req of turns) if (req.seq === selectedSeq) pinnedReq = req;
|
|
680
|
-
let activeReq = null;
|
|
681
|
-
if (hoveredSeq !== null) {
|
|
682
|
-
for (const req of turns) if (req.seq === hoveredSeq) activeReq = req;
|
|
683
|
-
}
|
|
684
|
-
if (activeReq === null) activeReq = pinnedReq;
|
|
685
|
-
if (activeReq === null && turns.length > 0) activeReq = turns[turns.length - 1];
|
|
686
|
-
const markerOf = (req) => {
|
|
687
|
-
const i = turns.indexOf(req);
|
|
688
|
-
return i >= 0 ? markers[i] : void 0;
|
|
689
|
-
};
|
|
690
|
-
const head = data !== null ? headlineOf(data, pressure) : null;
|
|
691
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-modal-backdrop", onClick: close }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-card", onClick: (ev) => {
|
|
692
|
-
ev.stopPropagation();
|
|
693
|
-
} }, /* @__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) + " tokens" : " " + t("overview.estimate")), head.pct !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-overview-pct" }, /* @__PURE__ */ React.createElement("b", null, head.pct + "%"), t("overview.used")) : 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" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("trend.title"))), turns.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
|
|
694
|
-
TrendChart,
|
|
695
|
-
{
|
|
696
|
-
requests: turns,
|
|
697
|
-
markers,
|
|
698
|
-
selectedSeq: pinnedReq !== null ? pinnedReq.seq : null,
|
|
699
|
-
hoveredSeq,
|
|
700
|
-
activeTurn: null,
|
|
701
|
-
granularity: "turn",
|
|
702
|
-
onSelect: setSelectedSeq,
|
|
703
|
-
onHover: setHoveredSeq,
|
|
704
|
-
onHoverTurn: () => {
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
), /* @__PURE__ */ React.createElement(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 })))));
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// src/client/styles.ts
|
|
712
|
-
var STYLES = [
|
|
713
|
-
".lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }",
|
|
714
|
-
".lc-card { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }",
|
|
715
|
-
".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
|
|
716
|
-
".lc-card-title-text { flex: none; white-space: nowrap; }",
|
|
717
|
-
".lc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; }",
|
|
718
|
-
".lc-stat { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
|
|
719
|
-
".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
720
|
-
".lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }",
|
|
721
|
-
".lc-stat-sub { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
722
|
-
".lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
723
|
-
".lc-gran, .lc-kinds { margin-left: auto; display: flex; gap: 2px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 1px; }",
|
|
724
|
-
".lc-gran-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; }",
|
|
725
|
-
".lc-gran-btn:hover { color: var(--dsw-alias-label-primary); }",
|
|
726
|
-
".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }",
|
|
727
|
-
".lc-overview-num { display: flex; align-items: baseline; gap: 6px; margin-bottom: 8px; }",
|
|
728
|
-
".lc-overview-num > b { font-size: 20px; }",
|
|
729
|
-
".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
|
|
730
|
-
// The used-percentage figure is the emphasis of the line: pushed to the
|
|
731
|
-
// right edge, large and primary-colored, with a small "used" caption.
|
|
732
|
-
".lc-overview-pct { margin-left: auto; font-size: 11px; }",
|
|
733
|
-
".lc-overview-pct b { font-size: 20px; color: var(--dsw-alias-label-primary); margin-right: 4px; }",
|
|
734
|
-
".lc-stacked-wrap { position: relative; width: 100%; }",
|
|
735
|
-
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); position: relative; }",
|
|
736
|
-
// Hover reference frame around the OCCUPIED region of the composition bar:
|
|
737
|
-
// dashes from the left edge to the used/window boundary, so the legend's
|
|
738
|
-
// "share of used" percentages visibly map to the boxed part (the free track
|
|
739
|
-
// sits outside it). pointer-events: none keeps hover on the segments/free.
|
|
740
|
-
// Deliberately high-contrast (label-primary) and thick so the frame reads at
|
|
741
|
-
// a glance, and the other parts dim underneath it (`.lc-stacked-dim`).
|
|
742
|
-
".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: 0; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-2); transition: opacity 120ms ease; }",
|
|
743
|
-
".lc-occupied-box-on { opacity: 1; }",
|
|
744
|
-
".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; opacity: 0; transition: opacity 120ms ease; }",
|
|
745
|
-
".lc-bar-tip-on { opacity: 1; }",
|
|
746
|
-
".lc-stacked > div { height: 100%; }",
|
|
747
|
-
// Hover eases: the shared composition hover (dim + segment brighten, on the
|
|
748
|
-
// overview or the mirrored browser bar) glides in and out — same 120ms
|
|
749
|
-
// rhythm as the rest of the view.
|
|
750
|
-
".lc-stacked-seg { transition: filter 120ms ease, opacity 120ms ease; }",
|
|
751
|
-
".lc-stacked-free { transition: box-shadow 120ms ease, opacity 120ms ease; }",
|
|
752
|
-
".lc-stacked-seg-on { filter: brightness(1.18); }",
|
|
753
|
-
".lc-stacked-free-on { box-shadow: inset 0 0 0 1px var(--dsw-alias-label-secondary); border-radius: 3px; }",
|
|
754
|
-
// Hover focus: everything except the hovered part (segment, legend chip, or
|
|
755
|
-
// free track) recedes, so the composition highlight and the occupied-region
|
|
756
|
-
// frame read clearly. The selected segment/free keeps full opacity.
|
|
757
|
-
".lc-stacked-dim .lc-stacked-seg { opacity: 0.35; }",
|
|
758
|
-
".lc-stacked-dim .lc-stacked-seg-on { opacity: 1; }",
|
|
759
|
-
".lc-stacked-dim .lc-stacked-free { opacity: 0.35; }",
|
|
760
|
-
".lc-stacked-dim .lc-stacked-free-on { opacity: 1; }",
|
|
761
|
-
".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
|
|
762
|
-
".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); padding: 1px 6px; border-radius: 6px; transition: background-color 120ms ease; }",
|
|
763
|
-
".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
|
|
764
|
-
".lc-chip i { transition: box-shadow 120ms ease; }",
|
|
765
|
-
".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
|
|
766
|
-
// The selected chip glows with the shared hover tint (smoothly, like the
|
|
767
|
-
// browser rows) instead of snapping its weight; the dot ring on top marks
|
|
768
|
-
// the exact segment.
|
|
769
|
-
".lc-chip-on { font-weight: 600; background: var(--dsw-alias-interactive-bg-hover); }",
|
|
770
|
-
".lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
771
|
-
".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
|
|
772
|
-
// The overview tool list links into the Context browser: the "工具定义 Top"
|
|
773
|
-
// label, its "等 N 个" overflow link, and every tool chip are buttons, and
|
|
774
|
-
// hover underlines them (the chip already reads as a tag, so the underline
|
|
775
|
-
// is the only affordance).
|
|
776
|
-
".lc-tools-label, .lc-tools-more { border: 0; background: transparent; padding: 0; color: var(--dsw-alias-label-secondary); font-family: inherit; font-size: 12px; cursor: pointer; }",
|
|
777
|
-
".lc-tools-label:hover, .lc-tools-more:hover { text-decoration: underline; }",
|
|
778
|
-
".lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border: 0; border-radius: 4px; padding: 1px 7px; font: inherit; font-size: 12px; color: var(--dsw-alias-label-primary); cursor: pointer; }",
|
|
779
|
-
".lc-tool-chip:hover { text-decoration: underline; }",
|
|
780
|
-
".lc-chartrow { display: flex; gap: 6px; align-items: stretch; }",
|
|
781
|
-
".lc-axis { position: relative; width: 40px; height: 150px; padding-top: 18px; box-sizing: border-box; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
782
|
-
".lc-axis span { position: absolute; right: 0; line-height: 1; }",
|
|
783
|
-
".lc-axis-top { top: 13px; }",
|
|
784
|
-
".lc-axis-mid { top: 69px; }",
|
|
785
|
-
".lc-axis-bot { top: 125px; }",
|
|
786
|
-
".lc-chart-scroll { position: relative; flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
|
|
787
|
-
".lc-chart-fade { position: absolute; top: 0; bottom: 0; width: 26px; pointer-events: none; z-index: 2; }",
|
|
788
|
-
".lc-chart-fade-l { left: 0; background: linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
789
|
-
".lc-chart-fade-r { right: 0; background: linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
790
|
-
".lc-chart { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 130px; padding-top: 18px; box-sizing: border-box; width: max-content; min-width: 100%; }",
|
|
791
|
-
".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
|
|
792
|
-
".lc-grid-top { top: 18px; }",
|
|
793
|
-
".lc-grid-mid { top: 74px; }",
|
|
794
|
-
".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; transition: opacity 120ms ease, background-color 120ms ease; }",
|
|
795
|
-
// Turn-aware dimming: while a turn is focused, bars OUTSIDE the active
|
|
796
|
-
// turn fade to 35% and the whole current turn stays fully opaque.
|
|
797
|
-
".lc-chart-dim .lc-bar { opacity: 0.35; }",
|
|
798
|
-
".lc-chart-dim .lc-bar-in-turn { opacity: 1; }",
|
|
799
|
-
".lc-chart-dim .lc-turn { opacity: 0.35; }",
|
|
800
|
-
".lc-chart-dim .lc-turn-on { opacity: 1; }",
|
|
801
|
-
".lc-chart-tip { position: absolute; top: 0; 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; }",
|
|
802
|
-
".lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }",
|
|
803
|
-
".lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
804
|
-
".lc-bar-hovered { outline: 1px dashed var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
805
|
-
".lc-bar-in-turn { background: rgba(128,128,128,0.14); }",
|
|
806
|
-
".lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }",
|
|
807
|
-
".lc-bar-stack > div { width: 100%; }",
|
|
808
|
-
".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
|
|
809
|
-
// Turn strip: NEUTRAL zebra fills (inline, see TURN_FILLS) with secondary
|
|
810
|
-
// text — never the category palette, or the strip reads as a detached
|
|
811
|
-
// bottom segment of the bars above it.
|
|
812
|
-
".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }",
|
|
813
|
-
".lc-turn { flex: none; box-sizing: border-box; text-align: center; font-size: 10px; line-height: 14px; font-weight: 600; color: var(--dsw-alias-label-secondary); border-radius: 3px; height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; transition: filter 120ms, opacity 120ms; }",
|
|
814
|
-
".lc-turn-on { filter: brightness(1.35); color: var(--dsw-alias-label-primary); }",
|
|
815
|
-
".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
|
|
816
|
-
".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
817
|
-
".lc-detail-head b { color: var(--dsw-alias-label-primary); }",
|
|
818
|
-
".lc-detail-marker { color: var(--dsw-alias-state-warn-primary); font-size: 11px; background: var(--dsw-alias-bg-layer-2); border-radius: 6px; padding: 1px 7px; }",
|
|
819
|
-
".lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
820
|
-
".lc-detail-tag { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); }",
|
|
821
|
-
".lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }",
|
|
822
|
-
".lc-detail-row { display: flex; align-items: center; gap: 8px; }",
|
|
823
|
-
".lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }",
|
|
824
|
-
".lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: rgba(128,128,128,0.18); overflow: hidden; display: block; }",
|
|
825
|
-
".lc-bar-fill { display: block; height: 100%; border-radius: 3px; }",
|
|
826
|
-
".lc-detail-num { width: 44px; text-align: right; }",
|
|
827
|
-
".lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }",
|
|
828
|
-
// Section rows share one 14px vertical rhythm, owned by the ROW (flex gap
|
|
829
|
-
// covers the wrapped-row axis too), never by the cards inside: a card that
|
|
830
|
-
// IS a column (head, events/messages) drops its own bottom margin, and a
|
|
831
|
-
// card stacked inside a wrapper column (overview + trend) keeps its margin
|
|
832
|
-
// as the inner gap but the last one drops it — so every section boundary
|
|
833
|
-
// measures exactly one 14px, whichever way the flex wraps.
|
|
834
|
-
".lc-cols { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }",
|
|
835
|
-
".lc-col { flex: 1; min-width: 280px; }",
|
|
836
|
-
".lc-cols > .lc-card { margin-bottom: 0; }",
|
|
837
|
-
".lc-col > .lc-card:last-child { margin-bottom: 0; }",
|
|
838
|
-
// Head row: stats board + plugin info sit side by side under the shared
|
|
839
|
-
// `lc-cols` flex — stats takes ~7/10 of the row, plugin info ~3/10, both
|
|
840
|
-
// wrap onto their own line when the available width falls below each card's
|
|
841
|
-
// min-width (so a narrow viewport keeps both readable). Both children are
|
|
842
|
-
// `.lc-card` themselves (rendered by StatsBoard / PluginInfo); the flex
|
|
843
|
-
// sizing lives on the head row's direct children so the existing card
|
|
844
|
-
// chrome doesn't change.
|
|
845
|
-
".lc-head > .lc-card:first-child { flex: 7 1 0; min-width: 360px; }",
|
|
846
|
-
".lc-head > .lc-card:last-child { flex: 3 1 0; min-width: 220px; }",
|
|
847
|
-
// Plugin info: two full-width rows (Plugin / GitHub), each one horizontal
|
|
848
|
-
// line with the label on the left and the value on the right — a compact
|
|
849
|
-
// definition list rather than a multi-cell grid.
|
|
850
|
-
".lc-pi-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }",
|
|
851
|
-
// The row IS the link: an `<a>` with the label + value on one baseline.
|
|
852
|
-
// Hovering a row underlines its value — the only hover affordance.
|
|
853
|
-
".lc-pi-row { display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; gap: 12px; min-width: 0; text-decoration: none; color: inherit; }",
|
|
854
|
-
".lc-pi-row:hover .lc-pi-value { text-decoration: underline; }",
|
|
855
|
-
// Upgrade chip appended to the Plugin value when the npm registry has a
|
|
856
|
-
// newer version than the baked-in one.
|
|
857
|
-
".lc-pi-update { color: var(--dsw-alias-state-warn-primary); font-size: 11px; margin-left: 6px; white-space: nowrap; }",
|
|
858
|
-
".lc-pi-label { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
859
|
-
// min-width: 0 lets the value shrink inside the flex row and truncate with
|
|
860
|
-
// an ellipsis; without it a narrow card pushes the value over the label.
|
|
861
|
-
".lc-pi-value { min-width: 0; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }",
|
|
862
|
-
".lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }",
|
|
863
|
-
".lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
864
|
-
".lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }",
|
|
865
|
-
".lc-event-icon.lc-event-inject { color: #a855f7; }",
|
|
866
|
-
".lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }",
|
|
867
|
-
// Kind chip: the event classification at a glance; the tint matches the
|
|
868
|
-
// impact direction (inject = adds context, compaction/prune = frees it,
|
|
869
|
-
// model switch = neutral), mirroring the token sign colors below.
|
|
870
|
-
".lc-kind { flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }",
|
|
871
|
-
".lc-kind-inject { background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); color: var(--dsw-alias-state-success-primary); }",
|
|
872
|
-
".lc-kind-compaction { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); color: var(--dsw-alias-state-error-primary); }",
|
|
873
|
-
".lc-kind-prune { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #8b5cf6; }",
|
|
874
|
-
".lc-kind-model { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent); color: var(--dsw-alias-brand-primary); }",
|
|
875
|
-
".lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
876
|
-
".lc-event-at { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; }",
|
|
877
|
-
".lc-event-tokens { color: var(--dsw-alias-state-success-primary); font-weight: 600; white-space: nowrap; }",
|
|
878
|
-
".lc-event-tokens.lc-up { color: var(--dsw-alias-state-warn-primary); }",
|
|
879
|
-
".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
880
|
-
".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
881
|
-
".lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }",
|
|
882
|
-
".lc-node-time { color: var(--dsw-alias-label-secondary); font-size: 12px; min-width: 54px; text-align: right; }",
|
|
883
|
-
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
884
|
-
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
885
|
-
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
|
886
|
-
".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }",
|
|
887
|
-
// ---- /context modal (centered dialog; escapes the composer anchor via fixed positioning) ----
|
|
888
|
-
".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; }",
|
|
889
|
-
".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; }",
|
|
890
|
-
".lc-modal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }",
|
|
891
|
-
".lc-modal-title { font-weight: 600; font-size: 14px; }",
|
|
892
|
-
".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; }",
|
|
893
|
-
".lc-modal-close:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); }",
|
|
894
|
-
".lc-modal-trend { margin-top: 14px; }",
|
|
895
|
-
// ---- Context browser card (progressive disclosure: category accordion ->
|
|
896
|
-
// element rows -> per-kind content) ----
|
|
897
|
-
".lc-br-pick { margin-left: auto; font: inherit; font-size: 12px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 6px; max-width: 240px; }",
|
|
898
|
-
".lc-br-meta { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
899
|
-
".lc-br-meta b { color: var(--dsw-alias-label-primary); }",
|
|
900
|
-
".lc-br-meta .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
901
|
-
".lc-br-note { margin-top: 8px; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
902
|
-
".lc-br-cats { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }",
|
|
903
|
-
".lc-br-cat { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }",
|
|
904
|
-
".lc-br-cat-empty { opacity: 0.55; }",
|
|
905
|
-
".lc-br-cat-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 7px 10px; cursor: pointer; text-align: left; transition: background-color 120ms ease; }",
|
|
906
|
-
".lc-br-cat-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
907
|
-
// Current-composition hover echo: the category row lit by the shared hover
|
|
908
|
-
// link (hovering the Current Composition card's bar/legend) looks hovered
|
|
909
|
-
// too — same tint as the physical hover, plus a ring on its color dot.
|
|
910
|
-
".lc-br-cat-on { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
911
|
-
".lc-br-cat-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
912
|
-
".lc-br-cat-row i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex: none; transition: box-shadow 120ms ease; }",
|
|
913
|
-
".lc-br-cat-label { font-weight: 600; }",
|
|
914
|
-
".lc-br-cat-count { color: var(--dsw-alias-label-secondary); font-size: 12px; flex: 1; }",
|
|
915
|
-
".lc-br-chev { flex: none; width: 12px; color: var(--dsw-alias-label-secondary); transition: transform 120ms ease; }",
|
|
916
|
-
".lc-br-chev-on { transform: rotate(90deg); }",
|
|
917
|
-
".lc-br-tokens { flex: none; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
918
|
-
".lc-br-pct { flex: none; width: 36px; text-align: right; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
919
|
-
".lc-br-body { border-top: 1px solid var(--dsw-alias-border-l1); padding: 4px 6px; display: flex; flex-direction: column; gap: 2px; }",
|
|
920
|
-
".lc-br-elem { border-radius: 6px; }",
|
|
921
|
-
".lc-br-elem-on { background: var(--dsw-alias-interactive-bg-active); }",
|
|
922
|
-
".lc-br-elem-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 5px 6px; cursor: pointer; text-align: left; border-radius: 6px; transition: background-color 120ms ease; }",
|
|
923
|
-
".lc-br-elem-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
924
|
-
".lc-br-tag { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
925
|
-
".lc-br-preview { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
926
|
-
".lc-br-time { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
927
|
-
".lc-br-content { padding: 2px 6px 8px 26px; display: flex; flex-direction: column; gap: 6px; }",
|
|
928
|
-
".lc-br-pre { margin: 0; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 320px; overflow-y: auto; scrollbar-width: thin; }",
|
|
929
|
-
".lc-br-dim { color: var(--dsw-alias-label-secondary); }",
|
|
930
|
-
".lc-br-call { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }",
|
|
931
|
-
// ---- Tool schema body (browser card, tools category). The description
|
|
932
|
-
// and parameter table share one "card with a small title" chrome — a
|
|
933
|
-
// labeled bordered block so a reader can scan three stacked sections
|
|
934
|
-
// without losing track of which is which. The raw JSON sits below as a
|
|
935
|
-
// collapsed toggle (no card chrome — it's a debug view, not a primary
|
|
936
|
-
// section). ----
|
|
937
|
-
".lc-ts-card { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; overflow: hidden; }",
|
|
938
|
-
".lc-ts-card-head { display: flex; align-items: center; gap: 8px; padding: 6px 10px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); border-bottom: 1px solid var(--dsw-alias-border-l1); }",
|
|
939
|
-
".lc-ts-card-head b { color: var(--dsw-alias-label-primary); }",
|
|
940
|
-
".lc-ts-card-count { margin-left: auto; }",
|
|
941
|
-
".lc-ts-desc-body { margin: 0; padding: 8px 10px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto; scrollbar-width: thin; }",
|
|
942
|
-
// Grid: name + type + required chip + description. Columns wrap on narrow
|
|
943
|
-
// rows; the description cell spans the full second row so a long blurb
|
|
944
|
-
// never breaks the one-line rhythm of name/type/required.
|
|
945
|
-
".lc-ts-param-row { display: grid; grid-template-columns: 140px 90px 56px 1fr; column-gap: 10px; row-gap: 2px; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1); font-size: 12px; align-items: baseline; }",
|
|
946
|
-
".lc-ts-param-row:first-of-type { border-top: 0; }",
|
|
947
|
-
".lc-ts-param-name { font-weight: 600; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
948
|
-
".lc-ts-param-type { color: var(--dsw-alias-label-secondary); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
949
|
-
".lc-ts-param-req { color: var(--dsw-alias-state-warn-primary); font-size: 11px; font-weight: 600; }",
|
|
950
|
-
".lc-ts-param-req-off { color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
951
|
-
".lc-ts-param-desc { grid-column: 1 / -1; color: var(--dsw-alias-label-secondary); line-height: 1.5; }",
|
|
952
|
-
".lc-ts-params-empty { padding: 8px 10px; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
953
|
-
// Raw JSON toggle: a slim button that owns the open/closed state, sitting
|
|
954
|
-
// just under the parameter table. The <pre> itself only mounts when open
|
|
955
|
-
// (avoids stringifying the schema on every render and keeps the default
|
|
956
|
-
// body compact).
|
|
957
|
-
".lc-ts-json { display: flex; flex-direction: column; gap: 4px; }",
|
|
958
|
-
".lc-ts-json-toggle { align-self: flex-start; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 11px; padding: 0; cursor: pointer; }",
|
|
959
|
-
".lc-ts-json-toggle:hover { color: var(--dsw-alias-label-primary); text-decoration: underline; }"
|
|
960
|
-
].join("\n");
|
|
961
|
-
|
|
962
|
-
// src/client/assemble.ts
|
|
963
|
-
function headerAt(headers, seq) {
|
|
964
|
-
if (headers === null || headers.headers.length === 0) return null;
|
|
965
|
-
if (seq === null) return headers.headers[headers.headers.length - 1];
|
|
966
|
-
for (let i = headers.headers.length - 1; i >= 0; i--) {
|
|
967
|
-
if (headers.headers[i].seq < seq) return headers.headers[i];
|
|
968
|
-
}
|
|
969
|
-
return null;
|
|
970
|
-
}
|
|
971
|
-
function assemble(data, headers, seq) {
|
|
972
|
-
const live = seq === null;
|
|
973
|
-
let nodes;
|
|
974
|
-
if (live) {
|
|
975
|
-
nodes = data.nodes.slice();
|
|
976
|
-
} else {
|
|
977
|
-
const picked = [];
|
|
978
|
-
for (const n of data.nodes) {
|
|
979
|
-
if (n.seq < seq) picked.push(n);
|
|
980
|
-
}
|
|
981
|
-
for (const n of data.archive || []) {
|
|
982
|
-
if (n.seq < seq && n.gone !== void 0 && n.gone > seq) picked.push(n);
|
|
983
|
-
}
|
|
984
|
-
nodes = picked;
|
|
985
|
-
}
|
|
986
|
-
nodes.sort((a, b) => a.seq - b.seq);
|
|
987
|
-
let missingLive = 0;
|
|
988
|
-
if (data.droppedNodes > 0) {
|
|
989
|
-
if (live || data.surfaceFloor !== void 0 && seq > data.surfaceFloor) {
|
|
990
|
-
missingLive = data.droppedNodes;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const approximate = !live && data.archiveFloor !== void 0 && seq < data.archiveFloor;
|
|
994
|
-
return { live, header: headerAt(headers, seq), nodes, missingLive, approximate };
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// src/client/components/nodes.tsx
|
|
998
|
-
function makeNodeText(kit) {
|
|
999
|
-
const { t } = kit;
|
|
1000
|
-
return function nodeText(n) {
|
|
1001
|
-
if (n.cat === "tool") {
|
|
1002
|
-
return t("node.toolResult") + (n.tool ? " \u2190 " + n.tool : "") + (n.err ? " \u26A0" : "");
|
|
1003
|
-
}
|
|
1004
|
-
if (n.skill) return "Skill: " + n.skill;
|
|
1005
|
-
if (n.calls) return t("node.calls") + n.calls.join(", ");
|
|
1006
|
-
if (n.text) return n.form === "snapshot" ? t("node.snapshot") + n.text : n.text;
|
|
1007
|
-
if (n.cat === "assistant") return t("node.empty");
|
|
1008
|
-
if (n.cat === "inject") return t("form." + (n.form || "context"));
|
|
1009
|
-
return t("node.nonText");
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function makeNodeList(kit) {
|
|
1013
|
-
const { t, fmt: fmt3, fmtTime: fmtTime2 } = kit;
|
|
1014
|
-
const nodeText = makeNodeText(kit);
|
|
1015
|
-
return function NodeList(props) {
|
|
1016
|
-
if (props.nodes.length === 0) {
|
|
1017
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("nodes.empty"));
|
|
1018
|
-
}
|
|
1019
|
-
const catColor = {};
|
|
1020
|
-
for (const c of CATS) catColor[c.key] = c.color;
|
|
1021
|
-
const rows = props.nodes.slice().reverse();
|
|
1022
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, t("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
|
|
1023
|
-
const text = nodeText(n);
|
|
1024
|
-
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)));
|
|
1025
|
-
}));
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// src/client/components/browser.tsx
|
|
1030
|
-
function unionTypesOf(p) {
|
|
1031
|
-
const branches = [];
|
|
1032
|
-
if (Array.isArray(p.anyOf)) branches.push(...p.anyOf);
|
|
1033
|
-
if (Array.isArray(p.oneOf)) branches.push(...p.oneOf);
|
|
1034
|
-
if (branches.length === 0) return null;
|
|
1035
|
-
const parts = [];
|
|
1036
|
-
for (const b of branches) {
|
|
1037
|
-
if (b !== null && typeof b === "object") parts.push(typeOf(b));
|
|
1038
|
-
}
|
|
1039
|
-
return parts.length > 0 ? parts.join(" | ") : null;
|
|
1040
|
-
}
|
|
1041
|
-
function typeOf(p) {
|
|
1042
|
-
const u = unionTypesOf(p);
|
|
1043
|
-
if (u !== null) return u;
|
|
1044
|
-
const t = p.type;
|
|
1045
|
-
if (t === "array") {
|
|
1046
|
-
const items = p.items;
|
|
1047
|
-
if (items !== null && typeof items === "object") {
|
|
1048
|
-
const inner = typeOf(items);
|
|
1049
|
-
return "array<" + inner + ">";
|
|
1050
|
-
}
|
|
1051
|
-
return "array";
|
|
1052
|
-
}
|
|
1053
|
-
if (typeof t === "string") {
|
|
1054
|
-
if (t === "object") {
|
|
1055
|
-
const props = p.properties;
|
|
1056
|
-
if (props !== null && typeof props === "object" && Object.keys(props).length > 0) {
|
|
1057
|
-
return "object{" + Object.keys(props).length + "}";
|
|
420
|
+
if (typeof t === "string") {
|
|
421
|
+
if (t === "object") {
|
|
422
|
+
const props = p.properties;
|
|
423
|
+
if (props !== null && typeof props === "object" && Object.keys(props).length > 0) {
|
|
424
|
+
return "object{" + Object.keys(props).length + "}";
|
|
1058
425
|
}
|
|
1059
426
|
}
|
|
1060
427
|
if (Array.isArray(p.enum) && p.enum.length > 0) {
|
|
@@ -1156,6 +523,21 @@ function NodeContent(props) {
|
|
|
1156
523
|
}
|
|
1157
524
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint));
|
|
1158
525
|
}
|
|
526
|
+
function byCatOf(asm) {
|
|
527
|
+
var _a;
|
|
528
|
+
const m = {};
|
|
529
|
+
for (const n of asm.nodes) (m[_a = n.cat] ?? (m[_a] = [])).push(n);
|
|
530
|
+
return m;
|
|
531
|
+
}
|
|
532
|
+
function countOf(asm, c) {
|
|
533
|
+
if (c === "system") return asm.header !== null && asm.header.system !== void 0 ? 1 : 0;
|
|
534
|
+
if (c === "tools") return asm.header !== null ? asm.header.tools.length : 0;
|
|
535
|
+
return byCatOf(asm)[c]?.length ?? 0;
|
|
536
|
+
}
|
|
537
|
+
function lastOfTurn(requests, turn) {
|
|
538
|
+
for (let i = requests.length - 1; i >= 0; i--) if ((requests[i].turn ?? 0) === turn) return requests[i];
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
1159
541
|
function makeContextBrowser(kit, StackedBar) {
|
|
1160
542
|
const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
|
|
1161
543
|
const nodeText = makeNodeText(kit);
|
|
@@ -1163,7 +545,6 @@ function makeContextBrowser(kit, StackedBar) {
|
|
|
1163
545
|
for (const c of CATS) catColor[c.key] = c.color;
|
|
1164
546
|
const MAX_AUTO_PAGES = 20;
|
|
1165
547
|
return function ContextBrowser(props) {
|
|
1166
|
-
var _a;
|
|
1167
548
|
const { data, headers } = props;
|
|
1168
549
|
const [sel, setSel] = React.useState("live");
|
|
1169
550
|
const [openCat, setOpenCat] = React.useState(null);
|
|
@@ -1228,13 +609,10 @@ function makeContextBrowser(kit, StackedBar) {
|
|
|
1228
609
|
setOpenCat(null);
|
|
1229
610
|
setOpenElem(null);
|
|
1230
611
|
};
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
if (c === "tools") return view.header !== null ? view.header.tools.length : 0;
|
|
1236
|
-
return byCat[c]?.length ?? 0;
|
|
1237
|
-
};
|
|
612
|
+
const refReq = req === null ? requests.length > 0 ? requests[requests.length - 1] : null : lastOfTurn(requests, (req.turn ?? 0) - 1);
|
|
613
|
+
const prevView = refReq !== null ? assemble(data, headers, refReq.seq) : null;
|
|
614
|
+
const byCat = byCatOf(view);
|
|
615
|
+
const toolCount = (c) => countOf(view, c);
|
|
1238
616
|
const toggleCat = (c) => {
|
|
1239
617
|
const openable = toolCount(c) > 0 || (c === "system" || c === "tools") && view.header === null;
|
|
1240
618
|
if (!openable) return;
|
|
@@ -1312,57 +690,576 @@ function makeContextBrowser(kit, StackedBar) {
|
|
|
1312
690
|
);
|
|
1313
691
|
});
|
|
1314
692
|
};
|
|
1315
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("browser.title")), /* @__PURE__ */ React.createElement(
|
|
693
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("browser.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-br-hint" }, t("browser.deltaHint")), /* @__PURE__ */ React.createElement(
|
|
1316
694
|
"select",
|
|
1317
695
|
{
|
|
1318
|
-
className: "lc-br-pick",
|
|
1319
|
-
value: seq === null ? "live" : String(seq),
|
|
1320
|
-
onChange: (e) => {
|
|
1321
|
-
pick(e.target.value);
|
|
1322
|
-
}
|
|
1323
|
-
},
|
|
1324
|
-
/* @__PURE__ */ React.createElement("option", { value: "live" }, t("browser.live")),
|
|
1325
|
-
requests.slice().reverse().map((r) => /* @__PURE__ */ React.createElement("option", { key: r.seq, value: String(r.seq) }, t("detail.step", { t: r.turn ?? 0, s: r.step ?? 0 }) + " \xB7 " + fmtTime2(r.time)))
|
|
1326
|
-
)), /* @__PURE__ */ React.createElement("div", { className: "lc-br-meta" }, /* @__PURE__ */ React.createElement("b", null, req !== null ? t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 }) : t("browser.liveNow")), req !== null ? /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)) : null, hoverReq !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("browser.preview")) : null, /* @__PURE__ */ React.createElement("span", null, t("detail.estTotal", { n: fmt3(total) })), req !== null && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-br-bar" }, /* @__PURE__ */ React.createElement(
|
|
1327
|
-
StackedBar,
|
|
696
|
+
className: "lc-br-pick",
|
|
697
|
+
value: seq === null ? "live" : String(seq),
|
|
698
|
+
onChange: (e) => {
|
|
699
|
+
pick(e.target.value);
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
/* @__PURE__ */ React.createElement("option", { value: "live" }, t("browser.live")),
|
|
703
|
+
requests.slice().reverse().map((r) => /* @__PURE__ */ React.createElement("option", { key: r.seq, value: String(r.seq) }, t("detail.step", { t: r.turn ?? 0, s: r.step ?? 0 }) + " \xB7 " + fmtTime2(r.time)))
|
|
704
|
+
)), /* @__PURE__ */ React.createElement("div", { className: "lc-br-meta" }, /* @__PURE__ */ React.createElement("b", null, req !== null ? t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 }) : t("browser.liveNow")), req !== null ? /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)) : null, hoverReq !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("browser.preview")) : null, /* @__PURE__ */ React.createElement("span", null, t("detail.estTotal", { n: fmt3(total) })), req !== null && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-br-bar" }, /* @__PURE__ */ React.createElement(
|
|
705
|
+
StackedBar,
|
|
706
|
+
{
|
|
707
|
+
parts,
|
|
708
|
+
height: 10,
|
|
709
|
+
hoverKey: linked ? linkKey : void 0,
|
|
710
|
+
onHoverKey: linked ? props.onHoverKey : void 0,
|
|
711
|
+
tip: false
|
|
712
|
+
}
|
|
713
|
+
)), view.missingLive > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.missingLive", { n: view.missingLive })) : null, view.approximate ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.approx")) : null, /* @__PURE__ */ React.createElement("div", { className: "lc-br-cats" }, CATS.map((c) => {
|
|
714
|
+
const count = toolCount(c.key);
|
|
715
|
+
const v = breakdown[c.key] || 0;
|
|
716
|
+
const prevCount = prevView !== null ? countOf(prevView, c.key) : null;
|
|
717
|
+
const countDelta = prevCount !== null ? count - prevCount : null;
|
|
718
|
+
const prevTokens = refReq !== null ? refReq[c.key] || 0 : null;
|
|
719
|
+
const tokenDelta = prevTokens !== null ? v - prevTokens : null;
|
|
720
|
+
const openable = count > 0 || (c.key === "system" || c.key === "tools") && view.header === null;
|
|
721
|
+
const open = openCat === c.key && openable;
|
|
722
|
+
return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-br-cat" + (openable ? "" : " lc-br-cat-empty") }, /* @__PURE__ */ React.createElement(
|
|
723
|
+
"button",
|
|
724
|
+
{
|
|
725
|
+
type: "button",
|
|
726
|
+
className: "lc-br-cat-row" + (linked && props.hoverKey === c.key ? " lc-br-cat-on" : ""),
|
|
727
|
+
onMouseEnter: linked ? () => {
|
|
728
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(c.key);
|
|
729
|
+
} : void 0,
|
|
730
|
+
onMouseLeave: linked ? () => {
|
|
731
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
732
|
+
} : void 0,
|
|
733
|
+
onClick: () => {
|
|
734
|
+
toggleCat(c.key);
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "\u25B8"),
|
|
738
|
+
/* @__PURE__ */ React.createElement("i", { style: { background: c.color } }),
|
|
739
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-label" }, catLabel(c.key)),
|
|
740
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-br-count-grp" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-count" }, t("browser.items", { n: count })), countDelta !== null && countDelta !== 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-delta lc-br-delta-" + (countDelta > 0 ? "up" : "down") }, (countDelta > 0 ? "+" : "") + countDelta) : null),
|
|
741
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens-grp" }, tokenDelta !== null && tokenDelta !== 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-tdelta lc-br-tdelta-" + (tokenDelta > 0 ? "up" : "down") }, (tokenDelta > 0 ? "+" : "") + fmt3(tokenDelta)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "\u2248" + fmt3(v))),
|
|
742
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-br-pct" }, total > 0 ? Math.round(v / total * 100) + "%" : "")
|
|
743
|
+
), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-body" }, catBody(c.key)) : null);
|
|
744
|
+
})));
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/client/components/stackedBar.tsx
|
|
749
|
+
var AUTO_COMPACT_RATIO = 0.8;
|
|
750
|
+
function makeStackedBar(kit) {
|
|
751
|
+
const { t, fmt: fmt3, catLabel } = kit;
|
|
752
|
+
return function StackedBar(props) {
|
|
753
|
+
const [reserveOn, setReserveOn] = React.useState(false);
|
|
754
|
+
let total = 0;
|
|
755
|
+
for (const p of props.parts) total += p.value;
|
|
756
|
+
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
757
|
+
const free = props.max !== void 0 && props.max > total ? props.max - total : 0;
|
|
758
|
+
const usedPct = scale > 0 ? total / scale * 100 : 0;
|
|
759
|
+
const hovering = props.hoverKey !== null && props.hoverKey !== void 0;
|
|
760
|
+
const showBox = free > 0 && hovering;
|
|
761
|
+
const reserve = props.reserve !== void 0 && props.max !== void 0 && props.max > 0 ? props.reserve : null;
|
|
762
|
+
const reserveLeft = reserve !== null ? Math.round(props.max * reserve.ratio / scale * 1e3) / 10 : 0;
|
|
763
|
+
const reserveWidth = reserve !== null ? Math.round((1 - reserve.ratio) * props.max / scale * 1e3) / 10 : 0;
|
|
764
|
+
let tip = null;
|
|
765
|
+
if (reserveOn && reserve !== null) {
|
|
766
|
+
tip = {
|
|
767
|
+
text: reserve.label,
|
|
768
|
+
leftPct: Math.max(12, Math.min(reserveLeft + reserveWidth / 2, 88))
|
|
769
|
+
};
|
|
770
|
+
} else if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
771
|
+
if (props.hoverKey === "free" && free > 0) {
|
|
772
|
+
const pct = scale > 0 ? free / scale * 100 : 0;
|
|
773
|
+
tip = {
|
|
774
|
+
text: t("overview.free") + " " + fmt3(free) + " (" + Math.round(pct) + "%)",
|
|
775
|
+
leftPct: Math.max(12, Math.min(total / scale * 100 + pct / 2, 88))
|
|
776
|
+
};
|
|
777
|
+
} else {
|
|
778
|
+
let acc = 0;
|
|
779
|
+
for (const p of props.parts) {
|
|
780
|
+
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
781
|
+
if (p.key === props.hoverKey && p.value > 0) {
|
|
782
|
+
tip = {
|
|
783
|
+
// "(pct%)" is a share of the OCCUPIED total — the solid box
|
|
784
|
+
// that appears on hover frames exactly this reference region.
|
|
785
|
+
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + t("overview.ofUsed"),
|
|
786
|
+
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
787
|
+
};
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
acc += pct;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-stacked-wrap" }, /* @__PURE__ */ React.createElement(
|
|
795
|
+
"div",
|
|
796
|
+
{
|
|
797
|
+
className: "lc-stacked" + (hovering ? " lc-stacked-dim" : ""),
|
|
798
|
+
style: { height: (props.height || 14) + "px" },
|
|
799
|
+
onMouseLeave: () => {
|
|
800
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
801
|
+
setReserveOn(false);
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
total > 0 ? props.parts.map((p) => {
|
|
805
|
+
if (!p.value) return null;
|
|
806
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
807
|
+
return /* @__PURE__ */ React.createElement(
|
|
808
|
+
"div",
|
|
809
|
+
{
|
|
810
|
+
key: p.key,
|
|
811
|
+
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
812
|
+
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
813
|
+
onMouseEnter: () => {
|
|
814
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
}) : null,
|
|
819
|
+
free > 0 ? /* @__PURE__ */ React.createElement(
|
|
820
|
+
"div",
|
|
821
|
+
{
|
|
822
|
+
key: "free",
|
|
823
|
+
className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
|
|
824
|
+
style: { width: free / scale * 100 + "%" },
|
|
825
|
+
onMouseEnter: () => {
|
|
826
|
+
if (props.onHoverKey !== void 0) props.onHoverKey("free");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
) : null,
|
|
830
|
+
reserve !== null ? /* @__PURE__ */ React.createElement(
|
|
831
|
+
"div",
|
|
832
|
+
{
|
|
833
|
+
className: "lc-reserve",
|
|
834
|
+
style: { left: reserveLeft + "%", width: reserveWidth + "%" },
|
|
835
|
+
onMouseEnter: () => {
|
|
836
|
+
setReserveOn(true);
|
|
837
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
838
|
+
},
|
|
839
|
+
onMouseLeave: () => setReserveOn(false)
|
|
840
|
+
}
|
|
841
|
+
) : null,
|
|
842
|
+
/* @__PURE__ */ React.createElement(
|
|
843
|
+
"div",
|
|
844
|
+
{
|
|
845
|
+
className: "lc-occupied-box" + (showBox ? " lc-occupied-box-on" : ""),
|
|
846
|
+
style: { width: usedPct + "%" }
|
|
847
|
+
}
|
|
848
|
+
)
|
|
849
|
+
), props.tip !== false ? /* @__PURE__ */ React.createElement(
|
|
850
|
+
"div",
|
|
851
|
+
{
|
|
852
|
+
className: "lc-bar-tip" + (tip ? " lc-bar-tip-on" : ""),
|
|
853
|
+
style: { left: tip ? tip.leftPct + "%" : "50%" }
|
|
854
|
+
},
|
|
855
|
+
tip ? tip.text : ""
|
|
856
|
+
) : null);
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function makeLegend(kit) {
|
|
860
|
+
const { t, fmt: fmt3, catLabel } = kit;
|
|
861
|
+
return function Legend(props) {
|
|
862
|
+
let total = 0;
|
|
863
|
+
for (const p of props.parts) total += p.value;
|
|
864
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-legend" }, props.parts.map((p) => {
|
|
865
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
866
|
+
return /* @__PURE__ */ React.createElement(
|
|
867
|
+
"span",
|
|
868
|
+
{
|
|
869
|
+
key: p.key,
|
|
870
|
+
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
871
|
+
title: t("overview.ofUsed"),
|
|
872
|
+
onMouseEnter: () => {
|
|
873
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
874
|
+
},
|
|
875
|
+
onMouseLeave: () => {
|
|
876
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
/* @__PURE__ */ React.createElement("i", { style: { background: p.color } }),
|
|
880
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-chip-label" }, catLabel(p.key)),
|
|
881
|
+
/* @__PURE__ */ React.createElement("span", { className: "lc-chip-nums" }, "\u2248" + fmt3(p.value), total > 0 ? /* @__PURE__ */ React.createElement("em", null, Math.round(p.value / total * 100) + "%") : null)
|
|
882
|
+
);
|
|
883
|
+
}));
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/client/components/currentComposition.tsx
|
|
888
|
+
function makeCurrentComposition(kit, StackedBar, Legend) {
|
|
889
|
+
const { t, fmt: fmt3 } = kit;
|
|
890
|
+
return function CurrentComposition(props) {
|
|
891
|
+
const head = props.head;
|
|
892
|
+
const reserve = head.window != null && head.window > 0 ? { ratio: AUTO_COMPACT_RATIO, label: t("overview.compactReserve", { pct: Math.round(AUTO_COMPACT_RATIO * 100) }) } : void 0;
|
|
893
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("overview.title")), props.subtitle !== void 0 && props.subtitle !== "" ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, props.subtitle) : 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) + " tokens" : " " + t("overview.estimate")), head.pct !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-overview-pct" }, /* @__PURE__ */ React.createElement("b", null, head.pct + "%"), t("overview.used")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: props.hoverKey, onHoverKey: props.onHoverKey, reserve }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: props.hoverKey, onHoverKey: props.onHoverKey }), props.tools !== void 0 && props.tools.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "lc-tools-label", onClick: () => {
|
|
894
|
+
if (props.onToolFocus !== void 0) props.onToolFocus({});
|
|
895
|
+
} }, t("tools.top")), props.tools.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => /* @__PURE__ */ React.createElement("button", { key: tool.name, type: "button", className: "lc-tool-chip", onClick: () => {
|
|
896
|
+
if (props.onToolFocus !== void 0) props.onToolFocus({ tool: tool.name });
|
|
897
|
+
} }, tool.name + " " + fmt3(tool.tokens))), props.tools.length > 5 ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "lc-tools-more", onClick: () => {
|
|
898
|
+
if (props.onToolFocus !== void 0) props.onToolFocus({});
|
|
899
|
+
} }, t("tools.more", { n: props.tools.length })) : null) : null);
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/client/components/contextModal.tsx
|
|
904
|
+
function makeContextModal(ctx, kit) {
|
|
905
|
+
const { t } = kit;
|
|
906
|
+
const sessions = ctx.get("sessions");
|
|
907
|
+
const StackedBar = makeStackedBar(kit);
|
|
908
|
+
const Legend = makeLegend(kit);
|
|
909
|
+
const CurrentComposition = makeCurrentComposition(kit, StackedBar, Legend);
|
|
910
|
+
const ContextBrowser = makeContextBrowser(kit, StackedBar);
|
|
911
|
+
return function ContextModal(props) {
|
|
912
|
+
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
913
|
+
const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
|
|
914
|
+
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
915
|
+
const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
|
|
916
|
+
const headers = typeof props.useProjection === "function" ? headersOf(props.useProjection("contextHeaders")) : null;
|
|
917
|
+
const [hoverCat, setHoverCat] = React.useState(null);
|
|
918
|
+
const [toolFocus, setToolFocus] = React.useState(null);
|
|
919
|
+
const close = React.useCallback(() => {
|
|
920
|
+
if (sessionId === "") return;
|
|
921
|
+
modalStoreOf(sessionId).set(false);
|
|
922
|
+
const guard = takePendingConsume(sessionId);
|
|
923
|
+
if (guard === void 0 || sessions === void 0) return;
|
|
924
|
+
const scope = sessions.scope(sessionId);
|
|
925
|
+
if (scope !== void 0) scope.bail(scope, "slash/input-consume-token", { guard });
|
|
926
|
+
}, [sessionId]);
|
|
927
|
+
React.useEffect(() => {
|
|
928
|
+
if (!open) return void 0;
|
|
929
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
930
|
+
const onKey = (ev) => {
|
|
931
|
+
if (ev.key !== "Escape") return;
|
|
932
|
+
ev.preventDefault();
|
|
933
|
+
ev.stopPropagation();
|
|
934
|
+
close();
|
|
935
|
+
};
|
|
936
|
+
window.addEventListener("keydown", onKey, true);
|
|
937
|
+
return () => {
|
|
938
|
+
window.removeEventListener("keydown", onKey, true);
|
|
939
|
+
if (previous !== null && document.contains(previous)) previous.focus();
|
|
940
|
+
};
|
|
941
|
+
}, [open, close]);
|
|
942
|
+
if (!open) return null;
|
|
943
|
+
const head = data !== null ? headlineOf(data, pressure) : null;
|
|
944
|
+
const subtitle = data !== null ? (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "") : "";
|
|
945
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-modal-backdrop", onClick: close }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-card", onClick: (ev) => {
|
|
946
|
+
ev.stopPropagation();
|
|
947
|
+
} }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("tab")), /* @__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(
|
|
948
|
+
CurrentComposition,
|
|
949
|
+
{
|
|
950
|
+
head,
|
|
951
|
+
subtitle,
|
|
952
|
+
hoverKey: hoverCat,
|
|
953
|
+
onHoverKey: setHoverCat,
|
|
954
|
+
tools: data.toolList,
|
|
955
|
+
onToolFocus: setToolFocus
|
|
956
|
+
}
|
|
957
|
+
), /* @__PURE__ */ React.createElement(
|
|
958
|
+
ContextBrowser,
|
|
1328
959
|
{
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
960
|
+
data,
|
|
961
|
+
headers,
|
|
962
|
+
useSession: props.useSession,
|
|
963
|
+
loadOlderHistory: props.loadOlderHistory,
|
|
964
|
+
hoverKey: hoverCat,
|
|
965
|
+
onHoverKey: setHoverCat,
|
|
966
|
+
toolFocus,
|
|
967
|
+
onToolFocusHandled: () => {
|
|
968
|
+
setToolFocus(null);
|
|
969
|
+
}
|
|
1334
970
|
}
|
|
1335
|
-
))
|
|
1336
|
-
const count = toolCount(c.key);
|
|
1337
|
-
const v = breakdown[c.key] || 0;
|
|
1338
|
-
const openable = count > 0 || (c.key === "system" || c.key === "tools") && view.header === null;
|
|
1339
|
-
const open = openCat === c.key && openable;
|
|
1340
|
-
return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-br-cat" + (openable ? "" : " lc-br-cat-empty") }, /* @__PURE__ */ React.createElement(
|
|
1341
|
-
"button",
|
|
1342
|
-
{
|
|
1343
|
-
type: "button",
|
|
1344
|
-
className: "lc-br-cat-row" + (linked && props.hoverKey === c.key ? " lc-br-cat-on" : ""),
|
|
1345
|
-
onMouseEnter: linked ? () => {
|
|
1346
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(c.key);
|
|
1347
|
-
} : void 0,
|
|
1348
|
-
onMouseLeave: linked ? () => {
|
|
1349
|
-
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
1350
|
-
} : void 0,
|
|
1351
|
-
onClick: () => {
|
|
1352
|
-
toggleCat(c.key);
|
|
1353
|
-
}
|
|
1354
|
-
},
|
|
1355
|
-
/* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "\u25B8"),
|
|
1356
|
-
/* @__PURE__ */ React.createElement("i", { style: { background: c.color } }),
|
|
1357
|
-
/* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-label" }, catLabel(c.key)),
|
|
1358
|
-
/* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-count" }, t("browser.items", { n: count })),
|
|
1359
|
-
/* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "\u2248" + fmt3(v)),
|
|
1360
|
-
/* @__PURE__ */ React.createElement("span", { className: "lc-br-pct" }, total > 0 ? Math.round(v / total * 100) + "%" : "")
|
|
1361
|
-
), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-body" }, catBody(c.key)) : null);
|
|
1362
|
-
})));
|
|
971
|
+
))));
|
|
1363
972
|
};
|
|
1364
973
|
}
|
|
1365
974
|
|
|
975
|
+
// src/client/styles.ts
|
|
976
|
+
var STYLES = [
|
|
977
|
+
".lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }",
|
|
978
|
+
".lc-card { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }",
|
|
979
|
+
".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
|
|
980
|
+
".lc-card-title-text { flex: none; white-space: nowrap; }",
|
|
981
|
+
".lc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; }",
|
|
982
|
+
".lc-stat { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
|
|
983
|
+
".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
984
|
+
".lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }",
|
|
985
|
+
".lc-stat-sub { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
986
|
+
".lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
987
|
+
".lc-gran, .lc-kinds { margin-left: auto; display: flex; gap: 2px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 1px; }",
|
|
988
|
+
".lc-gran-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; }",
|
|
989
|
+
".lc-gran-btn:hover { color: var(--dsw-alias-label-primary); }",
|
|
990
|
+
".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }",
|
|
991
|
+
".lc-overview-num { display: flex; align-items: baseline; gap: 6px; margin-bottom: 8px; }",
|
|
992
|
+
".lc-overview-num > b { font-size: 20px; }",
|
|
993
|
+
".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
|
|
994
|
+
// The used-percentage figure is the emphasis of the line: pushed to the
|
|
995
|
+
// right edge, large and primary-colored, with a small "used" caption.
|
|
996
|
+
".lc-overview-pct { margin-left: auto; font-size: 11px; }",
|
|
997
|
+
".lc-overview-pct b { font-size: 20px; color: var(--dsw-alias-label-primary); margin-right: 4px; }",
|
|
998
|
+
".lc-stacked-wrap { position: relative; width: 100%; }",
|
|
999
|
+
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); position: relative; }",
|
|
1000
|
+
// Hover reference frame around the OCCUPIED region of the composition bar:
|
|
1001
|
+
// a SOLID box from the left edge to the used/window boundary, so the
|
|
1002
|
+
// legend's "share of used" percentages visibly map to the boxed part (the
|
|
1003
|
+
// free track sits outside it). pointer-events: none keeps hover on the
|
|
1004
|
+
// segments/free. A soft LIGHT-GRAY stroke — a gentle guide rather than a
|
|
1005
|
+
// hard black box — that still reads through its 2px thickness against the
|
|
1006
|
+
// dimmed parts underneath it (`.lc-stacked-dim`). The free track echoes
|
|
1007
|
+
// the same weight with a dashed box, so the two read as one system.
|
|
1008
|
+
".lc-occupied-box { position: absolute; top: 0; bottom: 0; left: 0; border: 2px solid var(--dsw-alias-label-tertiary); border-radius: 5px; box-sizing: border-box; pointer-events: none; opacity: 0; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-2); transition: opacity 120ms ease; }",
|
|
1009
|
+
".lc-occupied-box-on { opacity: 1; }",
|
|
1010
|
+
".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; opacity: 0; transition: opacity 120ms ease; }",
|
|
1011
|
+
".lc-bar-tip-on { opacity: 1; }",
|
|
1012
|
+
".lc-stacked > div { height: 100%; }",
|
|
1013
|
+
// Hover eases: the shared composition hover (dim + segment brighten, on the
|
|
1014
|
+
// overview or the mirrored browser bar) glides in and out — same 120ms
|
|
1015
|
+
// rhythm as the rest of the view.
|
|
1016
|
+
".lc-stacked-seg { transition: filter 120ms ease, opacity 120ms ease; }",
|
|
1017
|
+
".lc-stacked-free { box-sizing: border-box; transition: box-shadow 120ms ease, opacity 120ms ease; }",
|
|
1018
|
+
".lc-stacked-seg-on { filter: brightness(1.18); }",
|
|
1019
|
+
// The free track mirrors the occupied frame: a DASHED 2px box (the occupied
|
|
1020
|
+
// frame is solid) at the same weight — border, not the old inset shadow, so
|
|
1021
|
+
// the dash pattern renders; the base box-sizing keeps the width from shifting.
|
|
1022
|
+
// Auto-compaction reserve band: the rightmost (1−ratio) of the window,
|
|
1023
|
+
// striped diagonally like a warning plate so it reads as "headroom, not
|
|
1024
|
+
// real usage" rather than plain free space. Sits above the track/segments
|
|
1025
|
+
// (overlay visible even past the threshold) with its own pointer so the
|
|
1026
|
+
// hover explains it (help cursor signals "more info").
|
|
1027
|
+
".lc-reserve { position: absolute; top: 0; bottom: 0; z-index: 1; pointer-events: auto; cursor: help; background: repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-state-warn-primary) 24%, transparent) 0 5px, transparent 5px 10px); }",
|
|
1028
|
+
".lc-stacked-free-on { border: 2px dashed var(--dsw-alias-label-secondary); border-radius: 3px; }",
|
|
1029
|
+
// Hover focus: everything except the hovered part (segment, legend chip, or
|
|
1030
|
+
// free track) recedes, so the composition highlight and the occupied-region
|
|
1031
|
+
// frame read clearly. The selected segment/free keeps full opacity.
|
|
1032
|
+
".lc-stacked-dim .lc-stacked-seg { opacity: 0.35; }",
|
|
1033
|
+
".lc-stacked-dim .lc-stacked-seg-on { opacity: 1; }",
|
|
1034
|
+
".lc-stacked-dim .lc-stacked-free { opacity: 0.35; }",
|
|
1035
|
+
".lc-stacked-dim .lc-stacked-free-on { opacity: 1; }",
|
|
1036
|
+
// Current-composition legend: an auto-flowing grid (as many columns as the
|
|
1037
|
+
// card fits, narrowing flows to fewer) whose cells are label-left /
|
|
1038
|
+
// numbers-right rows — each category a tidy "● 系统提示词 ≈2.0k 2%".
|
|
1039
|
+
".lc-legend { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 6px 14px; margin-top: 10px; }",
|
|
1040
|
+
".lc-chip { display: flex; align-items: center; gap: 5px; min-width: 0; color: var(--dsw-alias-label-primary); padding: 1px 6px; border-radius: 6px; cursor: pointer; transition: background-color 120ms ease; }",
|
|
1041
|
+
// The category name is the row's emphasis: bold at rest (the chip is a
|
|
1042
|
+
// legend label, not inline prose), ellipsized if a narrow cell squeezes it
|
|
1043
|
+
// while the right-aligned figures stay intact.
|
|
1044
|
+
".lc-chip-label { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }",
|
|
1045
|
+
// The numeric group (≈tokens + percent) pins to the cell's right edge while
|
|
1046
|
+
// the category label stays left; a baseline acts as the shared vertical
|
|
1047
|
+
// rhythm for the two figures.
|
|
1048
|
+
".lc-chip-nums { margin-left: auto; flex: none; display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }",
|
|
1049
|
+
".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
|
|
1050
|
+
".lc-chip i { transition: box-shadow 120ms ease; }",
|
|
1051
|
+
".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
|
|
1052
|
+
// The selected chip glows with the shared hover tint (smoothly, like the
|
|
1053
|
+
// browser rows) instead of snapping its weight; the dot ring on top marks
|
|
1054
|
+
// the exact segment.
|
|
1055
|
+
".lc-chip-on { font-weight: 600; background: var(--dsw-alias-interactive-bg-hover); }",
|
|
1056
|
+
".lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
1057
|
+
".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
|
|
1058
|
+
// The overview tool list links into the Context browser: the "工具定义 Top"
|
|
1059
|
+
// label, its "等 N 个" overflow link, and every tool chip are buttons, and
|
|
1060
|
+
// hover underlines them (the chip already reads as a tag, so the underline
|
|
1061
|
+
// is the only affordance).
|
|
1062
|
+
".lc-tools-label, .lc-tools-more { border: 0; background: transparent; padding: 0; color: var(--dsw-alias-label-secondary); font-family: inherit; font-size: 12px; cursor: pointer; }",
|
|
1063
|
+
".lc-tools-label:hover, .lc-tools-more:hover { text-decoration: underline; }",
|
|
1064
|
+
".lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border: 0; border-radius: 4px; padding: 1px 7px; font: inherit; font-size: 12px; color: var(--dsw-alias-label-primary); cursor: pointer; }",
|
|
1065
|
+
".lc-tool-chip:hover { text-decoration: underline; }",
|
|
1066
|
+
".lc-chartrow { display: flex; gap: 6px; align-items: stretch; }",
|
|
1067
|
+
".lc-axis { position: relative; width: 40px; height: 150px; padding-top: 18px; box-sizing: border-box; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
1068
|
+
".lc-axis span { position: absolute; right: 0; line-height: 1; }",
|
|
1069
|
+
".lc-axis-top { top: 13px; }",
|
|
1070
|
+
".lc-axis-mid { top: 69px; }",
|
|
1071
|
+
".lc-axis-bot { top: 125px; }",
|
|
1072
|
+
".lc-chart-scroll { position: relative; flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
|
|
1073
|
+
".lc-chart-fade { position: absolute; top: 0; bottom: 0; width: 26px; pointer-events: none; z-index: 2; }",
|
|
1074
|
+
".lc-chart-fade-l { left: 0; background: linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
1075
|
+
".lc-chart-fade-r { right: 0; background: linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
1076
|
+
".lc-chart { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 130px; padding-top: 18px; box-sizing: border-box; width: max-content; min-width: 100%; }",
|
|
1077
|
+
".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
|
|
1078
|
+
".lc-grid-top { top: 18px; }",
|
|
1079
|
+
".lc-grid-mid { top: 74px; }",
|
|
1080
|
+
".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; transition: opacity 120ms ease, background-color 120ms ease; }",
|
|
1081
|
+
// Turn-aware dimming: while a turn is focused, bars OUTSIDE the active
|
|
1082
|
+
// turn fade to 35% and the whole current turn stays fully opaque.
|
|
1083
|
+
".lc-chart-dim .lc-bar { opacity: 0.35; }",
|
|
1084
|
+
".lc-chart-dim .lc-bar-in-turn { opacity: 1; }",
|
|
1085
|
+
".lc-chart-dim .lc-turn { opacity: 0.35; }",
|
|
1086
|
+
".lc-chart-dim .lc-turn-on { opacity: 1; }",
|
|
1087
|
+
".lc-chart-tip { position: absolute; top: 0; 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; }",
|
|
1088
|
+
".lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }",
|
|
1089
|
+
".lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
1090
|
+
".lc-bar-hovered { outline: 1px dashed var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
1091
|
+
".lc-bar-in-turn { background: rgba(128,128,128,0.14); }",
|
|
1092
|
+
".lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }",
|
|
1093
|
+
".lc-bar-stack > div { width: 100%; }",
|
|
1094
|
+
".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
|
|
1095
|
+
// Turn strip: NEUTRAL zebra fills (inline, see TURN_FILLS) with secondary
|
|
1096
|
+
// text — never the category palette, or the strip reads as a detached
|
|
1097
|
+
// bottom segment of the bars above it.
|
|
1098
|
+
".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }",
|
|
1099
|
+
".lc-turn { flex: none; box-sizing: border-box; text-align: center; font-size: 10px; line-height: 14px; font-weight: 600; color: var(--dsw-alias-label-secondary); border-radius: 3px; height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; transition: filter 120ms, opacity 120ms; }",
|
|
1100
|
+
".lc-turn-on { filter: brightness(1.35); color: var(--dsw-alias-label-primary); }",
|
|
1101
|
+
".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
|
|
1102
|
+
".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
1103
|
+
".lc-detail-head b { color: var(--dsw-alias-label-primary); }",
|
|
1104
|
+
".lc-detail-marker { color: var(--dsw-alias-state-warn-primary); font-size: 11px; background: var(--dsw-alias-bg-layer-2); border-radius: 6px; padding: 1px 7px; }",
|
|
1105
|
+
".lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
1106
|
+
".lc-detail-tag { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); }",
|
|
1107
|
+
".lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }",
|
|
1108
|
+
".lc-detail-row { display: flex; align-items: center; gap: 8px; }",
|
|
1109
|
+
".lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }",
|
|
1110
|
+
".lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: rgba(128,128,128,0.18); overflow: hidden; display: block; }",
|
|
1111
|
+
".lc-bar-fill { display: block; height: 100%; border-radius: 3px; }",
|
|
1112
|
+
".lc-detail-num { width: 44px; text-align: right; }",
|
|
1113
|
+
".lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }",
|
|
1114
|
+
// Section rows share one 14px vertical rhythm, owned by the ROW (flex gap
|
|
1115
|
+
// covers the wrapped-row axis too), never by the cards inside: a card that
|
|
1116
|
+
// IS a column (head, events/messages) drops its own bottom margin, and a
|
|
1117
|
+
// card stacked inside a wrapper column (overview + trend) keeps its margin
|
|
1118
|
+
// as the inner gap but the last one drops it — so every section boundary
|
|
1119
|
+
// measures exactly one 14px, whichever way the flex wraps.
|
|
1120
|
+
".lc-cols { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }",
|
|
1121
|
+
".lc-col { flex: 1; min-width: 280px; }",
|
|
1122
|
+
".lc-cols > .lc-card { margin-bottom: 0; }",
|
|
1123
|
+
".lc-col > .lc-card:last-child { margin-bottom: 0; }",
|
|
1124
|
+
// Head row: stats board + plugin info sit side by side under the shared
|
|
1125
|
+
// `lc-cols` flex — stats takes ~7/10 of the row, plugin info ~3/10, both
|
|
1126
|
+
// wrap onto their own line when the available width falls below each card's
|
|
1127
|
+
// min-width (so a narrow viewport keeps both readable). Both children are
|
|
1128
|
+
// `.lc-card` themselves (rendered by StatsBoard / PluginInfo); the flex
|
|
1129
|
+
// sizing lives on the head row's direct children so the existing card
|
|
1130
|
+
// chrome doesn't change.
|
|
1131
|
+
".lc-head > .lc-card:first-child { flex: 7 1 0; min-width: 360px; }",
|
|
1132
|
+
".lc-head > .lc-card:last-child { flex: 3 1 0; min-width: 220px; }",
|
|
1133
|
+
// Plugin info: two full-width rows (Plugin / GitHub), each one horizontal
|
|
1134
|
+
// line with the label on the left and the value on the right — a compact
|
|
1135
|
+
// definition list rather than a multi-cell grid.
|
|
1136
|
+
".lc-pi-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }",
|
|
1137
|
+
// The row IS the link: an `<a>` with the label + value on one baseline.
|
|
1138
|
+
// Hovering a row underlines its value — the only hover affordance.
|
|
1139
|
+
".lc-pi-row { display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; gap: 12px; min-width: 0; text-decoration: none; color: inherit; }",
|
|
1140
|
+
".lc-pi-row:hover .lc-pi-value { text-decoration: underline; }",
|
|
1141
|
+
// Upgrade chip appended to the Plugin value when the npm registry has a
|
|
1142
|
+
// newer version than the baked-in one.
|
|
1143
|
+
".lc-pi-update { color: var(--dsw-alias-state-warn-primary); font-size: 11px; margin-left: 6px; white-space: nowrap; }",
|
|
1144
|
+
".lc-pi-label { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
|
|
1145
|
+
// min-width: 0 lets the value shrink inside the flex row and truncate with
|
|
1146
|
+
// an ellipsis; without it a narrow card pushes the value over the label.
|
|
1147
|
+
".lc-pi-value { min-width: 0; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }",
|
|
1148
|
+
".lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }",
|
|
1149
|
+
".lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
1150
|
+
".lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }",
|
|
1151
|
+
".lc-event-icon.lc-event-inject { color: #a855f7; }",
|
|
1152
|
+
".lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }",
|
|
1153
|
+
// Kind chip: the event classification at a glance; the tint matches the
|
|
1154
|
+
// impact direction (inject = adds context, compaction/prune = frees it,
|
|
1155
|
+
// model switch = neutral), mirroring the token sign colors below.
|
|
1156
|
+
".lc-kind { flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }",
|
|
1157
|
+
".lc-kind-inject { background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); color: var(--dsw-alias-state-success-primary); }",
|
|
1158
|
+
".lc-kind-compaction { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); color: var(--dsw-alias-state-error-primary); }",
|
|
1159
|
+
".lc-kind-prune { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #8b5cf6; }",
|
|
1160
|
+
".lc-kind-model { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent); color: var(--dsw-alias-brand-primary); }",
|
|
1161
|
+
".lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1162
|
+
".lc-event-at { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; }",
|
|
1163
|
+
".lc-event-tokens { color: var(--dsw-alias-state-success-primary); font-weight: 600; white-space: nowrap; }",
|
|
1164
|
+
".lc-event-tokens.lc-up { color: var(--dsw-alias-state-warn-primary); }",
|
|
1165
|
+
".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
1166
|
+
".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
1167
|
+
".lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }",
|
|
1168
|
+
".lc-node-time { color: var(--dsw-alias-label-secondary); font-size: 12px; min-width: 54px; text-align: right; }",
|
|
1169
|
+
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
1170
|
+
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
1171
|
+
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
|
1172
|
+
".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }",
|
|
1173
|
+
// ---- /context modal (centered dialog; escapes the composer anchor via fixed positioning) ----
|
|
1174
|
+
".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; }",
|
|
1175
|
+
".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; }",
|
|
1176
|
+
".lc-modal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }",
|
|
1177
|
+
".lc-modal-title { font-weight: 600; font-size: 14px; }",
|
|
1178
|
+
".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; }",
|
|
1179
|
+
".lc-modal-close:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); }",
|
|
1180
|
+
// ---- Context browser card (progressive disclosure: category accordion ->
|
|
1181
|
+
// element rows -> per-kind content) ----
|
|
1182
|
+
// The δ-baseline caption sits at the card title's right edge, just left of
|
|
1183
|
+
// the step picker (it takes the row's auto margin the picker used to own).
|
|
1184
|
+
".lc-br-hint { margin-left: auto; color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 400; white-space: nowrap; }",
|
|
1185
|
+
".lc-br-pick { font: inherit; font-size: 12px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 6px; max-width: 240px; }",
|
|
1186
|
+
".lc-br-meta { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
1187
|
+
".lc-br-meta b { color: var(--dsw-alias-label-primary); }",
|
|
1188
|
+
".lc-br-meta .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
1189
|
+
".lc-br-note { margin-top: 8px; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
1190
|
+
".lc-br-cats { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }",
|
|
1191
|
+
".lc-br-cat { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }",
|
|
1192
|
+
".lc-br-cat-empty { opacity: 0.55; }",
|
|
1193
|
+
".lc-br-cat-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 7px 10px; cursor: pointer; text-align: left; transition: background-color 120ms ease; }",
|
|
1194
|
+
".lc-br-cat-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
1195
|
+
// Current-composition hover echo: the category row lit by the shared hover
|
|
1196
|
+
// link (hovering the Current Composition card's bar/legend) looks hovered
|
|
1197
|
+
// too — same tint as the physical hover, plus a ring on its color dot.
|
|
1198
|
+
".lc-br-cat-on { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
1199
|
+
".lc-br-cat-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
1200
|
+
".lc-br-cat-row i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex: none; transition: box-shadow 120ms ease; }",
|
|
1201
|
+
".lc-br-cat-label { font-weight: 600; }",
|
|
1202
|
+
".lc-br-cat-count { color: var(--dsw-alias-label-secondary); font-size: 12px; white-space: nowrap; }",
|
|
1203
|
+
// Count + Δ pill form one attached group: the group absorbs the row's free
|
|
1204
|
+
// space (tokens/percent stay right-aligned) with a tight 4px inner gap so
|
|
1205
|
+
// the pill hugs the item count instead of standing at the row's 8px rhythm.
|
|
1206
|
+
".lc-br-count-grp { flex: 1; min-width: 0; display: inline-flex; align-items: center; gap: 4px; }",
|
|
1207
|
+
".lc-br-chev { flex: none; width: 12px; color: var(--dsw-alias-label-secondary); transition: transform 120ms ease; }",
|
|
1208
|
+
".lc-br-chev-on { transform: rotate(90deg); }",
|
|
1209
|
+
".lc-br-tokens { flex: none; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
1210
|
+
// Δ pills on a category row: count and token swings vs the previous turn's
|
|
1211
|
+
// last step, tinted by direction (added = success, freed = error — the same
|
|
1212
|
+
// language as the events card's kind chips).
|
|
1213
|
+
".lc-br-delta, .lc-br-tdelta { flex: none; font-size: 11px; font-weight: 600; padding: 0 5px; border-radius: 4px; white-space: nowrap; }",
|
|
1214
|
+
".lc-br-delta-up, .lc-br-tdelta-up { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); }",
|
|
1215
|
+
".lc-br-delta-down, .lc-br-tdelta-down { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); }",
|
|
1216
|
+
// The token Δ pill hugs the LEFT of the token figure (tight 4px gap, like
|
|
1217
|
+
// the count group) while the figure itself keeps its right-aligned slot.
|
|
1218
|
+
".lc-br-tokens-grp { flex: none; min-width: 0; display: inline-flex; align-items: center; gap: 4px; }",
|
|
1219
|
+
".lc-br-pct { flex: none; width: 36px; text-align: right; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
1220
|
+
".lc-br-body { border-top: 1px solid var(--dsw-alias-border-l1); padding: 4px 6px; display: flex; flex-direction: column; gap: 2px; }",
|
|
1221
|
+
".lc-br-elem { border-radius: 6px; }",
|
|
1222
|
+
".lc-br-elem-on { background: var(--dsw-alias-interactive-bg-active); }",
|
|
1223
|
+
".lc-br-elem-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 5px 6px; cursor: pointer; text-align: left; border-radius: 6px; transition: background-color 120ms ease; }",
|
|
1224
|
+
".lc-br-elem-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
|
|
1225
|
+
".lc-br-tag { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1226
|
+
".lc-br-preview { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1227
|
+
".lc-br-time { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
1228
|
+
".lc-br-content { padding: 2px 6px 8px 26px; display: flex; flex-direction: column; gap: 6px; }",
|
|
1229
|
+
".lc-br-pre { margin: 0; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 320px; overflow-y: auto; scrollbar-width: thin; }",
|
|
1230
|
+
".lc-br-dim { color: var(--dsw-alias-label-secondary); }",
|
|
1231
|
+
".lc-br-call { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }",
|
|
1232
|
+
// ---- Tool schema body (browser card, tools category). The description
|
|
1233
|
+
// and parameter table share one "card with a small title" chrome — a
|
|
1234
|
+
// labeled bordered block so a reader can scan three stacked sections
|
|
1235
|
+
// without losing track of which is which. The raw JSON sits below as a
|
|
1236
|
+
// collapsed toggle (no card chrome — it's a debug view, not a primary
|
|
1237
|
+
// section). ----
|
|
1238
|
+
".lc-ts-card { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; overflow: hidden; }",
|
|
1239
|
+
".lc-ts-card-head { display: flex; align-items: center; gap: 8px; padding: 6px 10px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); border-bottom: 1px solid var(--dsw-alias-border-l1); }",
|
|
1240
|
+
".lc-ts-card-head b { color: var(--dsw-alias-label-primary); }",
|
|
1241
|
+
".lc-ts-card-count { margin-left: auto; }",
|
|
1242
|
+
".lc-ts-desc-body { margin: 0; padding: 8px 10px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto; scrollbar-width: thin; }",
|
|
1243
|
+
// Grid: name + type + required chip + description. Columns wrap on narrow
|
|
1244
|
+
// rows; the description cell spans the full second row so a long blurb
|
|
1245
|
+
// never breaks the one-line rhythm of name/type/required.
|
|
1246
|
+
".lc-ts-param-row { display: grid; grid-template-columns: 140px 90px 56px 1fr; column-gap: 10px; row-gap: 2px; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1); font-size: 12px; align-items: baseline; }",
|
|
1247
|
+
".lc-ts-param-row:first-of-type { border-top: 0; }",
|
|
1248
|
+
".lc-ts-param-name { font-weight: 600; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1249
|
+
".lc-ts-param-type { color: var(--dsw-alias-label-secondary); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1250
|
+
".lc-ts-param-req { color: var(--dsw-alias-state-warn-primary); font-size: 11px; font-weight: 600; }",
|
|
1251
|
+
".lc-ts-param-req-off { color: var(--dsw-alias-label-secondary); font-size: 11px; }",
|
|
1252
|
+
".lc-ts-param-desc { grid-column: 1 / -1; color: var(--dsw-alias-label-secondary); line-height: 1.5; }",
|
|
1253
|
+
".lc-ts-params-empty { padding: 8px 10px; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
1254
|
+
// Raw JSON toggle: a slim button that owns the open/closed state, sitting
|
|
1255
|
+
// just under the parameter table. The <pre> itself only mounts when open
|
|
1256
|
+
// (avoids stringifying the schema on every render and keeps the default
|
|
1257
|
+
// body compact).
|
|
1258
|
+
".lc-ts-json { display: flex; flex-direction: column; gap: 4px; }",
|
|
1259
|
+
".lc-ts-json-toggle { align-self: flex-start; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 11px; padding: 0; cursor: pointer; }",
|
|
1260
|
+
".lc-ts-json-toggle:hover { color: var(--dsw-alias-label-primary); text-decoration: underline; }"
|
|
1261
|
+
].join("\n");
|
|
1262
|
+
|
|
1366
1263
|
// src/client/components/events.tsx
|
|
1367
1264
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1368
1265
|
|
|
@@ -1440,7 +1337,7 @@ function makeEventList(kit) {
|
|
|
1440
1337
|
|
|
1441
1338
|
// src/client/meta.ts
|
|
1442
1339
|
var PLUGIN_NAME = "dsh-context";
|
|
1443
|
-
var PLUGIN_VERSION = true ? "0.
|
|
1340
|
+
var PLUGIN_VERSION = true ? "0.17.0" : "0.0.0-dev";
|
|
1444
1341
|
var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
|
|
1445
1342
|
var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
|
|
1446
1343
|
|
|
@@ -1492,7 +1389,30 @@ function makePluginInfo(kit) {
|
|
|
1492
1389
|
};
|
|
1493
1390
|
}
|
|
1494
1391
|
|
|
1392
|
+
// src/client/components/requestDetail.tsx
|
|
1393
|
+
function makeRequestDetail(kit, StackedBar) {
|
|
1394
|
+
const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
1395
|
+
return function RequestDetail(props) {
|
|
1396
|
+
const req = props.request;
|
|
1397
|
+
if (!req) return null;
|
|
1398
|
+
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
1399
|
+
const head = isTurn ? t("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
1400
|
+
const marker = props.marker ?? null;
|
|
1401
|
+
const markerAt = marker !== null ? eventAt(marker) : null;
|
|
1402
|
+
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, t("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, t("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) => {
|
|
1403
|
+
const v = req[c.key] || 0;
|
|
1404
|
+
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) + "%" : ""));
|
|
1405
|
+
})));
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1495
1409
|
// src/client/components/statsBoard.tsx
|
|
1410
|
+
function cacheHitPercent(usage) {
|
|
1411
|
+
const billed = usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
1412
|
+
if (billed === 0) return null;
|
|
1413
|
+
const hundredths = Math.trunc(usage.cacheReadTokens / billed * 1e4 + 1e-9);
|
|
1414
|
+
return `${Math.floor(hundredths / 100)}.${String(hundredths % 100).padStart(2, "0")}`;
|
|
1415
|
+
}
|
|
1496
1416
|
function makeStatsBoard(kit) {
|
|
1497
1417
|
const { t, fmt: fmt3 } = kit;
|
|
1498
1418
|
return function StatsBoard(props) {
|
|
@@ -1507,8 +1427,187 @@ function makeStatsBoard(kit) {
|
|
|
1507
1427
|
else if (ev.kind === "prune") prunes++;
|
|
1508
1428
|
else if (ev.kind === "inject") injects++;
|
|
1509
1429
|
}
|
|
1430
|
+
const hit = props.usage !== null ? cacheHitPercent(props.usage) : null;
|
|
1510
1431
|
const cell = (label, value) => /* @__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));
|
|
1511
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, 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(t("stats.injects"), fmt3(injects)), cell(t("stats.compactions"), fmt3(compactions)), cell(t("stats.prunes"), fmt3(prunes))));
|
|
1432
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, 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(t("stats.injects"), fmt3(injects)), cell(t("stats.compactions"), fmt3(compactions)), cell(t("stats.prunes"), fmt3(prunes)), cell(t("stats.cacheHit"), hit === null ? "\u2014" : hit + "%")));
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/client/components/trendChart.tsx
|
|
1437
|
+
function aggregateByTurn(requests) {
|
|
1438
|
+
const out = [];
|
|
1439
|
+
let runSteps = 0;
|
|
1440
|
+
for (const req of requests) {
|
|
1441
|
+
const last = out.length > 0 ? out[out.length - 1] : null;
|
|
1442
|
+
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0)) {
|
|
1443
|
+
runSteps++;
|
|
1444
|
+
out[out.length - 1] = { ...req, stepCount: runSteps };
|
|
1445
|
+
} else {
|
|
1446
|
+
runSteps = 1;
|
|
1447
|
+
out.push({ ...req, stepCount: 1 });
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return out;
|
|
1451
|
+
}
|
|
1452
|
+
function attachMarkers(requests, events) {
|
|
1453
|
+
const markers = new Array(requests.length);
|
|
1454
|
+
for (const ev of events) {
|
|
1455
|
+
if (ev.kind !== "compaction" && ev.kind !== "prune") continue;
|
|
1456
|
+
for (let r = 0; r < requests.length; r++) {
|
|
1457
|
+
if (requests[r].seq >= ev.seq) {
|
|
1458
|
+
if (markers[r] === void 0) markers[r] = ev;
|
|
1459
|
+
break;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
return markers;
|
|
1464
|
+
}
|
|
1465
|
+
function makeTrendChart(kit) {
|
|
1466
|
+
const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
|
|
1467
|
+
const CHART_H = 112;
|
|
1468
|
+
const BAR_W = 14;
|
|
1469
|
+
const BAR_GAP = 2;
|
|
1470
|
+
const TURN_FILLS = ["rgba(128,128,128,0.12)", "rgba(128,128,128,0.26)"];
|
|
1471
|
+
return function TrendChart(props) {
|
|
1472
|
+
const requests = props.requests;
|
|
1473
|
+
const markers = props.markers;
|
|
1474
|
+
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
1475
|
+
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
1476
|
+
let maxTotal = 1;
|
|
1477
|
+
for (const req of requests) {
|
|
1478
|
+
const bt = barTotalOf(req);
|
|
1479
|
+
if (bt > maxTotal) maxTotal = bt;
|
|
1480
|
+
}
|
|
1481
|
+
const groups = [];
|
|
1482
|
+
for (const req of requests) {
|
|
1483
|
+
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
1484
|
+
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
1485
|
+
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
1486
|
+
groups.push(grp);
|
|
1487
|
+
}
|
|
1488
|
+
grp.count++;
|
|
1489
|
+
grp.span += req.stepCount ?? 1;
|
|
1490
|
+
}
|
|
1491
|
+
const scrollRef = React.useRef(null);
|
|
1492
|
+
const scrolledOnce = React.useRef(false);
|
|
1493
|
+
const lastGranRef = React.useRef(props.granularity);
|
|
1494
|
+
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
1495
|
+
const edgesRef = React.useRef(edges);
|
|
1496
|
+
const updateEdges = (el) => {
|
|
1497
|
+
const left = el.scrollLeft > 4;
|
|
1498
|
+
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
1499
|
+
const prev = edgesRef.current;
|
|
1500
|
+
if (prev.left === left && prev.right === right) return;
|
|
1501
|
+
edgesRef.current = { left, right };
|
|
1502
|
+
setEdges({ left, right });
|
|
1503
|
+
};
|
|
1504
|
+
React.useLayoutEffect(() => {
|
|
1505
|
+
const el = scrollRef.current;
|
|
1506
|
+
if (el === null) return;
|
|
1507
|
+
if (props.granularity !== lastGranRef.current) {
|
|
1508
|
+
lastGranRef.current = props.granularity;
|
|
1509
|
+
scrolledOnce.current = false;
|
|
1510
|
+
}
|
|
1511
|
+
if (!scrolledOnce.current) {
|
|
1512
|
+
scrolledOnce.current = true;
|
|
1513
|
+
el.scrollLeft = el.scrollWidth;
|
|
1514
|
+
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
1515
|
+
el.scrollLeft = el.scrollWidth;
|
|
1516
|
+
}
|
|
1517
|
+
updateEdges(el);
|
|
1518
|
+
});
|
|
1519
|
+
const tipOf = (req) => {
|
|
1520
|
+
const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : t("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
1521
|
+
return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + t("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + t("tip.actual", { n: fmt3(req.prompt) }) : "");
|
|
1522
|
+
};
|
|
1523
|
+
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
1524
|
+
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
1525
|
+
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(
|
|
1526
|
+
"div",
|
|
1527
|
+
{
|
|
1528
|
+
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
1529
|
+
ref: scrollRef,
|
|
1530
|
+
onScroll: (e) => {
|
|
1531
|
+
updateEdges(e.currentTarget);
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
1535
|
+
/* @__PURE__ */ React.createElement(
|
|
1536
|
+
"div",
|
|
1537
|
+
{
|
|
1538
|
+
className: "lc-chart",
|
|
1539
|
+
onMouseLeave: () => {
|
|
1540
|
+
props.onHover(null);
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-top" }),
|
|
1544
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-grid lc-grid-mid" }),
|
|
1545
|
+
requests.map((req, i) => {
|
|
1546
|
+
const marker = markers[i];
|
|
1547
|
+
const markerAt = marker !== void 0 ? eventAt(marker) : null;
|
|
1548
|
+
const selected = props.selectedSeq === req.seq;
|
|
1549
|
+
const hovered = props.hoveredSeq === req.seq;
|
|
1550
|
+
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
1551
|
+
return /* @__PURE__ */ React.createElement(
|
|
1552
|
+
"div",
|
|
1553
|
+
{
|
|
1554
|
+
key: req.seq,
|
|
1555
|
+
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
1556
|
+
style: { width: BAR_W + "px" },
|
|
1557
|
+
onClick: () => {
|
|
1558
|
+
props.onSelect(selected ? null : req.seq);
|
|
1559
|
+
},
|
|
1560
|
+
onMouseEnter: () => {
|
|
1561
|
+
props.onHover(req.seq);
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
marker !== void 0 ? /* @__PURE__ */ React.createElement(
|
|
1565
|
+
"span",
|
|
1566
|
+
{
|
|
1567
|
+
className: "lc-bar-marker",
|
|
1568
|
+
title: "\u2702 " + (markerAt !== null ? markerAt + " \u2014 " : "") + eventLabel(marker)
|
|
1569
|
+
},
|
|
1570
|
+
"\u2702"
|
|
1571
|
+
) : null,
|
|
1572
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-bar-stack" }, CATS.map((c) => {
|
|
1573
|
+
const v = (req[c.key] || 0) * anchorOf(req);
|
|
1574
|
+
if (!v) return null;
|
|
1575
|
+
return /* @__PURE__ */ React.createElement("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
1576
|
+
}))
|
|
1577
|
+
);
|
|
1578
|
+
})
|
|
1579
|
+
),
|
|
1580
|
+
hoveredReq !== null ? /* @__PURE__ */ React.createElement(
|
|
1581
|
+
"div",
|
|
1582
|
+
{
|
|
1583
|
+
className: "lc-chart-tip",
|
|
1584
|
+
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
1585
|
+
},
|
|
1586
|
+
tipOf(hoveredReq)
|
|
1587
|
+
) : null,
|
|
1588
|
+
/* @__PURE__ */ React.createElement("div", { className: "lc-turns", onMouseLeave: () => {
|
|
1589
|
+
props.onHoverTurn(null);
|
|
1590
|
+
} }, groups.map((grp, gi) => {
|
|
1591
|
+
const on = props.activeTurn === grp.turn;
|
|
1592
|
+
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
1593
|
+
return /* @__PURE__ */ React.createElement(
|
|
1594
|
+
"span",
|
|
1595
|
+
{
|
|
1596
|
+
key: "turn-" + gi,
|
|
1597
|
+
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
1598
|
+
style: {
|
|
1599
|
+
width: blockW + "px",
|
|
1600
|
+
background: TURN_FILLS[gi % TURN_FILLS.length]
|
|
1601
|
+
},
|
|
1602
|
+
title: "T" + grp.turn,
|
|
1603
|
+
onMouseEnter: () => {
|
|
1604
|
+
props.onHoverTurn(grp.turn);
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
"T" + grp.turn
|
|
1608
|
+
);
|
|
1609
|
+
}))
|
|
1610
|
+
), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
|
|
1512
1611
|
};
|
|
1513
1612
|
}
|
|
1514
1613
|
|
|
@@ -1516,9 +1615,10 @@ function makeStatsBoard(kit) {
|
|
|
1516
1615
|
var viewScroll = /* @__PURE__ */ new Map();
|
|
1517
1616
|
var EVENT_KINDS = ["inject", "compaction", "prune", "model"];
|
|
1518
1617
|
function makeContextView(ctx, kit) {
|
|
1519
|
-
const { t
|
|
1618
|
+
const { t } = kit;
|
|
1520
1619
|
const StackedBar = makeStackedBar(kit);
|
|
1521
1620
|
const Legend = makeLegend(kit);
|
|
1621
|
+
const CurrentComposition = makeCurrentComposition(kit, StackedBar, Legend);
|
|
1522
1622
|
const TrendChart = makeTrendChart(kit);
|
|
1523
1623
|
const RequestDetail = makeRequestDetail(kit, StackedBar);
|
|
1524
1624
|
const EventList = makeEventList(kit);
|
|
@@ -1530,6 +1630,7 @@ function makeContextView(ctx, kit) {
|
|
|
1530
1630
|
const sessionId = props.sessionId;
|
|
1531
1631
|
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
1532
1632
|
const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
|
|
1633
|
+
const usage = typeof props.useProjection === "function" ? tokenUsageOf(props.useProjection("tokenUsage")) : null;
|
|
1533
1634
|
const headers = typeof props.useProjection === "function" ? headersOf(props.useProjection("contextHeaders")) : null;
|
|
1534
1635
|
const [selectedSeq, setSelectedSeq] = React.useState(null);
|
|
1535
1636
|
const [hoveredSeq, setHoveredSeq] = React.useState(null);
|
|
@@ -1604,40 +1705,17 @@ function makeContextView(ctx, kit) {
|
|
|
1604
1705
|
}
|
|
1605
1706
|
}
|
|
1606
1707
|
const head = headlineOf(data, pressure);
|
|
1607
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-col" }, /* @__PURE__ */ React.createElement(
|
|
1608
|
-
|
|
1609
|
-
{
|
|
1610
|
-
type: "button",
|
|
1611
|
-
className: "lc-tools-label",
|
|
1612
|
-
onClick: () => {
|
|
1613
|
-
setToolFocus({});
|
|
1614
|
-
}
|
|
1615
|
-
},
|
|
1616
|
-
t("tools.top")
|
|
1617
|
-
), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
|
|
1618
|
-
return /* @__PURE__ */ React.createElement(
|
|
1619
|
-
"button",
|
|
1620
|
-
{
|
|
1621
|
-
key: tool.name,
|
|
1622
|
-
type: "button",
|
|
1623
|
-
className: "lc-tool-chip",
|
|
1624
|
-
onClick: () => {
|
|
1625
|
-
setToolFocus({ tool: tool.name });
|
|
1626
|
-
}
|
|
1627
|
-
},
|
|
1628
|
-
tool.name + " " + fmt3(tool.tokens)
|
|
1629
|
-
);
|
|
1630
|
-
}), data.toolList.length > 5 ? /* @__PURE__ */ React.createElement(
|
|
1631
|
-
"button",
|
|
1708
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events, usage }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-col" }, /* @__PURE__ */ React.createElement(
|
|
1709
|
+
CurrentComposition,
|
|
1632
1710
|
{
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
)
|
|
1711
|
+
head,
|
|
1712
|
+
subtitle: (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""),
|
|
1713
|
+
hoverKey: hoverCat,
|
|
1714
|
+
onHoverKey: setHoverCat,
|
|
1715
|
+
tools: data.toolList,
|
|
1716
|
+
onToolFocus: setToolFocus
|
|
1717
|
+
}
|
|
1718
|
+
), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("trend.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement(
|
|
1641
1719
|
"button",
|
|
1642
1720
|
{
|
|
1643
1721
|
className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
|