dsh-recent-usage-metrics 0.3.6 → 0.4.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/CHANGELOG.md +20 -0
- package/README.en.md +7 -0
- package/README.md +5 -0
- package/client/client.js +222 -82
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 0.4.0
|
|
2
|
+
|
|
3
|
+
把「模型切换」与「请求配置变更」分开——它们此前被当成同一件事,而后者远比前者常见。
|
|
4
|
+
|
|
5
|
+
- 判定改为:窗口内有 `request/header` **且本步模型与上一步不同**才算切换模型;上一步
|
|
6
|
+
模型未知时以 header 为准。实测一个真实会话:27 个 header 里只有 7 次是真正的模型切换,
|
|
7
|
+
其余 20 次是工具集变化(启用或禁用插件)。旧规则把这 20 次也标成切换模型,是错的。
|
|
8
|
+
- 新增「配置变更」信号:有 header 但模型未变。它的可见后果就是缓存变冷,所以显示在黄色
|
|
9
|
+
那一行里。
|
|
10
|
+
- 序号列现在会在高亮行上给出说明:切换模型时写「该行切换模型至:X」;缓存过低时写
|
|
11
|
+
「该行请求配置变更(工具集 66→65)」加「缓存命中 0.4%,低于 90%」。普通行的序号没有提示。
|
|
12
|
+
- 两个工具数只有都已知时才写成区间,否则只说配置发生变化,不编造数字。
|
|
13
|
+
|
|
14
|
+
## 0.3.7
|
|
15
|
+
|
|
16
|
+
- `#` 表头的提示拆成三行:最近记录、三个状态图标、两种行颜色。原来挤成一句,
|
|
17
|
+
三件事连在一起读起来费劲。
|
|
18
|
+
- 实时行不再显示总用时的工具提示。还在跑的请求尚未结算工具时间,提示只能给出
|
|
19
|
+
「无工具调用」或一个不完整的数,对一个仍在进行的请求都是错的;行结算后再出现。
|
|
20
|
+
|
|
1
21
|
## 0.3.6
|
|
2
22
|
|
|
3
23
|
- `#` 表头的提示加了一行颜色说明:黄色表示缓存命中过低,蓝色表示这一步切换了模型。
|
package/README.en.md
CHANGED
|
@@ -165,6 +165,13 @@ Every drawer column and every header carries a hint:
|
|
|
165
165
|
When both hold, the text takes the warn colour and the bar keeps the business colour. A
|
|
166
166
|
compaction row never takes the cache highlight.
|
|
167
167
|
|
|
168
|
+
**A model switch and a configuration change are not the same thing.** A `request/header` only says
|
|
169
|
+
the routed configuration changed — enabling or disabling a plugin rewrites the tool list and writes
|
|
170
|
+
a header without touching the model. On a real session, only 7 of 27 headers were genuine model
|
|
171
|
+
switches. Blue therefore appears only when the model itself differs; a configuration change without
|
|
172
|
+
a model change shows up as a cold cache, coloured yellow, with the tool-list movement named in the
|
|
173
|
+
row-number hint.
|
|
174
|
+
|
|
168
175
|
**Where the model name comes from:** a `request/header` inside the step's window means a switch
|
|
169
176
|
and supplies the model; otherwise the previous step's model carries over, and a landed reply
|
|
170
177
|
overrides both via `assistant/message.source`. The live row follows the same rule, so the model
|
package/README.md
CHANGED
|
@@ -148,6 +148,11 @@ DSH 的极客级请求指标。它在 **设置 → 通用 → 性能与用量**
|
|
|
148
148
|
|
|
149
149
|
两者同时成立时文字取警告色、色条保留业务蓝。压缩行不参与缓存高亮。
|
|
150
150
|
|
|
151
|
+
**「切换模型」与「配置变更」不是一回事。** 一个请求会写出 `request/header`,只说明路由配置
|
|
152
|
+
变了——启用或禁用插件会改变工具集,同样会写 header,但模型没换。实测一个真实会话:27 个
|
|
153
|
+
header 里只有 7 次是真正的模型切换。所以蓝色只在模型确实不同时出现;模型没变但配置变了,
|
|
154
|
+
它的后果就是缓存变冷,显示为黄色,并在序号提示里写明工具集的变化。
|
|
155
|
+
|
|
151
156
|
**模型名的来源:** 本步窗口内的 `request/header` 判为切换,否则沿用上一步;回复落地后
|
|
152
157
|
由 `assistant/message.source` 覆盖。实时行同样适用,所以生成期间就能看到模型名。
|
|
153
158
|
|
package/client/client.js
CHANGED
|
@@ -114,9 +114,19 @@ window.__ModuleLoader__.load({
|
|
|
114
114
|
hoursAgo: '{count} 小时前',
|
|
115
115
|
// Column-header hints. `{bolt}` / `{tools}` / `{compact}` are replaced by the glyphs
|
|
116
116
|
// themselves, which is why these are rendered by the tooltip component and not by title.
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
/*
|
|
118
|
+
* The index hint is three lines: what the column holds, what the glyphs mean, then what
|
|
119
|
+
* the row colours mean. Splitting them keeps each line short enough to read at a glance
|
|
120
|
+
* instead of being one long sentence the eye has to parse.
|
|
121
|
+
*/
|
|
122
|
+
hintIndexRows: '行号:1–10 为已完成的最近记录',
|
|
123
|
+
hintIndexGlyphs: '{bolt} 为模型输出中,{tools} 为工具调用中,{compact} 为压缩上下文中',
|
|
119
124
|
hintColour: '整行黄色表示缓存命中过低,蓝色表示这一步切换了模型',
|
|
125
|
+
/* The row-number hint: only a highlighted row carries one, and it says why in numbers. */
|
|
126
|
+
rowSwitched: '该行切换模型至:{model}',
|
|
127
|
+
rowLowCache: '缓存命中 {rate},低于 {threshold}%',
|
|
128
|
+
rowConfigChanged: '该行请求配置变更(工具集 {from}→{to})',
|
|
129
|
+
rowConfigChangedUnknown: '该行请求配置变更,缓存前缀失效',
|
|
120
130
|
hintTime: '请求发出的时刻(本地时间)',
|
|
121
131
|
hintTtft: '从发出到收到第一个 token 的耗时',
|
|
122
132
|
hintSpeed: '模型生成速度 = 输出 token ÷ 解码时长',
|
|
@@ -178,9 +188,13 @@ window.__ModuleLoader__.load({
|
|
|
178
188
|
secondsAgo: '{count}s ago',
|
|
179
189
|
minutesAgo: '{count}m ago',
|
|
180
190
|
hoursAgo: '{count}h ago',
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
hintIndexRows: 'Row number: 1–10 are the most recent finished records',
|
|
192
|
+
hintIndexGlyphs: '{bolt} is a model reply, {tools} is a tool call, {compact} is a context compaction',
|
|
183
193
|
hintColour: 'A yellow row means a low cache hit; a blue row means this step switched models',
|
|
194
|
+
rowSwitched: 'This row switched to {model}',
|
|
195
|
+
rowLowCache: 'Cache hit {rate}, below {threshold}%',
|
|
196
|
+
rowConfigChanged: 'Request configuration changed on this row (tools {from}→{to})',
|
|
197
|
+
rowConfigChangedUnknown: 'Request configuration changed on this row; the cached prefix was lost',
|
|
184
198
|
hintTime: 'When the request was issued (local time)',
|
|
185
199
|
hintTtft: 'Time from issuing the request to receiving its first token',
|
|
186
200
|
hintSpeed: 'Generation speed = output tokens ÷ decode time',
|
|
@@ -1301,27 +1315,36 @@ window.__ModuleLoader__.load({
|
|
|
1301
1315
|
}
|
|
1302
1316
|
|
|
1303
1317
|
/**
|
|
1304
|
-
* Which model a step used,
|
|
1318
|
+
* Which model a step used, whether that request changed models, and whether it changed the
|
|
1319
|
+
* routed configuration at all.
|
|
1305
1320
|
*
|
|
1306
|
-
* A `request/header` is written
|
|
1307
|
-
*
|
|
1321
|
+
* A `request/header` is written when the routed configuration changes, and that includes far
|
|
1322
|
+
* more than the model: enabling or disabling a plugin changes the tool list, which rewrites
|
|
1323
|
+
* the request prefix and invalidates the provider's cache. Measured on a real session, 27
|
|
1324
|
+
* headers covered only 7 genuine model switches, so "a header exists" is NOT "the model
|
|
1325
|
+
* changed" — treating the two as one flagged every plugin toggle as a model switch.
|
|
1308
1326
|
*
|
|
1309
|
-
*
|
|
1310
|
-
* changed the routed configuration, and the header names the model it switched to;
|
|
1311
|
-
* - absent → the step reused whatever was already routed, so it inherits the previous
|
|
1312
|
-
* step's model.
|
|
1327
|
+
* The three signals are therefore separated:
|
|
1313
1328
|
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1329
|
+
* - `switched` — a header landed in the step's window AND the model differs from the one
|
|
1330
|
+
* before it. When the previous model is unknown the header is trusted, because there is
|
|
1331
|
+
* nothing to contradict it.
|
|
1332
|
+
* - `configChanged` — a header landed in the window but the model did NOT change, so the
|
|
1333
|
+
* change was elsewhere in the configuration (the tool list). Its visible consequence is a
|
|
1334
|
+
* cold cache, which is why it is reported beside the low-cache highlight.
|
|
1335
|
+
* - `toolCount` / `previousToolCount` — the tool-list sizes the two headers carried, so the
|
|
1336
|
+
* hint can say what actually moved rather than gesturing at "the configuration".
|
|
1320
1337
|
*/
|
|
1321
1338
|
function modelPlanFromStats(statsSnapshot, known) {
|
|
1322
1339
|
const plan = new Map()
|
|
1323
1340
|
const turns = statsSnapshot?.turns
|
|
1324
1341
|
if (!Array.isArray(turns)) return plan
|
|
1342
|
+
/*
|
|
1343
|
+
* The plan walks the session's steps in time order across turns, not turn by turn, because
|
|
1344
|
+
* "the previous model" has to compare against the step before it even when that step lives
|
|
1345
|
+
* in an earlier turn — a turn's first step is exactly where a switch is most likely.
|
|
1346
|
+
*/
|
|
1347
|
+
const sequence = []
|
|
1325
1348
|
for (const turn of turns) {
|
|
1326
1349
|
if (!Array.isArray(turn.steps)) continue
|
|
1327
1350
|
const headers = []
|
|
@@ -1331,62 +1354,98 @@ window.__ModuleLoader__.load({
|
|
|
1331
1354
|
const at = numberOrNull(event.time)
|
|
1332
1355
|
if (at === null) continue
|
|
1333
1356
|
if (typeof event.model !== 'string' && typeof event.provider !== 'string') continue
|
|
1334
|
-
headers.push({
|
|
1357
|
+
headers.push({
|
|
1358
|
+
time: at,
|
|
1359
|
+
provider: event.provider ?? null,
|
|
1360
|
+
model: event.model ?? null,
|
|
1361
|
+
tools: numberOrNull(event.toolCount),
|
|
1362
|
+
})
|
|
1335
1363
|
}
|
|
1336
1364
|
headers.sort((left, right) => left.time - right.time)
|
|
1337
1365
|
}
|
|
1338
|
-
const
|
|
1366
|
+
const steps = []
|
|
1339
1367
|
for (const step of turn.steps) {
|
|
1340
1368
|
const at = numberOrNull(step.startTime)
|
|
1341
1369
|
if (at === null || typeof step.step !== 'number') continue
|
|
1342
|
-
|
|
1370
|
+
steps.push({ at, step: step.step })
|
|
1371
|
+
}
|
|
1372
|
+
steps.sort((left, right) => left.at - right.at)
|
|
1373
|
+
for (const step of steps) sequence.push({ turn: turn.turn, step: step.step, at: step.at, headers })
|
|
1374
|
+
}
|
|
1375
|
+
sequence.sort((left, right) => left.at - right.at)
|
|
1376
|
+
|
|
1377
|
+
/** The model of the step before this one, or null when it is not known. */
|
|
1378
|
+
let previousModel = null
|
|
1379
|
+
let previousTools = null
|
|
1380
|
+
let inForce = null
|
|
1381
|
+
|
|
1382
|
+
for (let index = 0; index < sequence.length; index += 1) {
|
|
1383
|
+
const entry = sequence[index]
|
|
1384
|
+
const key = `${entry.turn}:${entry.step}`
|
|
1385
|
+
const nextAt = index + 1 < sequence.length ? sequence[index + 1].at : Number.POSITIVE_INFINITY
|
|
1386
|
+
let inside = null
|
|
1387
|
+
for (const header of entry.headers) {
|
|
1388
|
+
if (header.time < entry.at) continue
|
|
1389
|
+
if (header.time >= nextAt) break
|
|
1390
|
+
inside = header
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const settled = known === undefined ? undefined : known.get(key)
|
|
1394
|
+
const settledModel =
|
|
1395
|
+
settled !== undefined && settled.model !== null
|
|
1396
|
+
? { provider: settled.provider, model: settled.model }
|
|
1397
|
+
: null
|
|
1398
|
+
|
|
1399
|
+
if (inside !== null) {
|
|
1400
|
+
const headerModel = { provider: inside.provider, model: inside.model }
|
|
1401
|
+
const changed =
|
|
1402
|
+
previousModel === null ||
|
|
1403
|
+
headerModel.model === null ||
|
|
1404
|
+
previousModel.model !== headerModel.model ||
|
|
1405
|
+
previousModel.provider !== headerModel.provider
|
|
1406
|
+
plan.set(key, {
|
|
1407
|
+
provider: inside.provider,
|
|
1408
|
+
model: inside.model,
|
|
1409
|
+
/*
|
|
1410
|
+
* A header alone proves only that the routed configuration changed. Saying "the model
|
|
1411
|
+
* switched" additionally requires that the model itself differs — and when the
|
|
1412
|
+
* previous model is unknown, the header is the only evidence, so it is trusted.
|
|
1413
|
+
*/
|
|
1414
|
+
switched: changed,
|
|
1415
|
+
configChanged: !changed,
|
|
1416
|
+
toolCount: inside.tools,
|
|
1417
|
+
previousToolCount: previousTools,
|
|
1418
|
+
source: 'header',
|
|
1419
|
+
})
|
|
1420
|
+
inForce = headerModel
|
|
1421
|
+
previousModel = headerModel
|
|
1422
|
+
previousTools = inside.tools
|
|
1423
|
+
continue
|
|
1343
1424
|
}
|
|
1344
|
-
if (starts.length === 0) continue
|
|
1345
|
-
starts.sort((left, right) => left.at - right.at)
|
|
1346
1425
|
|
|
1347
1426
|
/*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
* A step whose header names a switch becomes the new carrier, so the step AFTER it
|
|
1351
|
-
* inherits the switched-to model — not the one before the switch, which `known` alone
|
|
1352
|
-
* would give.
|
|
1427
|
+
* No header: the step reused what was already routed. A finished reply outranks the
|
|
1428
|
+
* carried model, because `assistant/message.source` is the authority for a settled step.
|
|
1353
1429
|
*/
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
// The header was written for THIS request, so the model changed here.
|
|
1368
|
-
inForce = { provider: inside.provider, model: inside.model }
|
|
1369
|
-
plan.set(key, { provider: inside.provider, model: inside.model, switched: true, source: 'header' })
|
|
1370
|
-
continue
|
|
1371
|
-
}
|
|
1372
|
-
/*
|
|
1373
|
-
* No header: inherit. A finished reply outranks the carried model, because
|
|
1374
|
-
* `assistant/message.source` is the authority for a step that has settled.
|
|
1375
|
-
*/
|
|
1376
|
-
const settled = known === undefined ? undefined : known.get(key)
|
|
1377
|
-
if (settled !== undefined && settled.model !== null) {
|
|
1378
|
-
inForce = { provider: settled.provider, model: settled.model }
|
|
1379
|
-
plan.set(key, { provider: settled.provider, model: settled.model, switched: false, source: 'reply' })
|
|
1380
|
-
continue
|
|
1381
|
-
}
|
|
1382
|
-
if (inForce !== null) {
|
|
1383
|
-
plan.set(key, { provider: inForce.provider, model: inForce.model, switched: false, source: 'inherit' })
|
|
1384
|
-
}
|
|
1430
|
+
const resolved = settledModel ?? inForce
|
|
1431
|
+
if (resolved !== null) {
|
|
1432
|
+
plan.set(key, {
|
|
1433
|
+
provider: resolved.provider,
|
|
1434
|
+
model: resolved.model,
|
|
1435
|
+
switched: false,
|
|
1436
|
+
configChanged: false,
|
|
1437
|
+
toolCount: null,
|
|
1438
|
+
previousToolCount: null,
|
|
1439
|
+
source: settledModel !== null ? 'reply' : 'inherit',
|
|
1440
|
+
})
|
|
1441
|
+
inForce = resolved
|
|
1442
|
+
previousModel = resolved
|
|
1385
1443
|
}
|
|
1386
1444
|
}
|
|
1387
1445
|
return plan
|
|
1388
1446
|
}
|
|
1389
1447
|
|
|
1448
|
+
|
|
1390
1449
|
/**
|
|
1391
1450
|
* Whether any step started after this compaction finished.
|
|
1392
1451
|
*
|
|
@@ -1459,6 +1518,14 @@ window.__ModuleLoader__.load({
|
|
|
1459
1518
|
row.model = owner.model
|
|
1460
1519
|
row.provider = owner.provider
|
|
1461
1520
|
row.switched = owner.switched === true
|
|
1521
|
+
/*
|
|
1522
|
+
* A configuration change that was not a model switch still rewrote the request prefix,
|
|
1523
|
+
* so it is worth naming when the cache came back cold: that is the difference between
|
|
1524
|
+
* "the account changed" and "the tool list moved".
|
|
1525
|
+
*/
|
|
1526
|
+
row.configChanged = owner.configChanged === true
|
|
1527
|
+
row.toolCount = owner.toolCount ?? null
|
|
1528
|
+
row.previousToolCount = owner.previousToolCount ?? null
|
|
1462
1529
|
}
|
|
1463
1530
|
rows.push(row)
|
|
1464
1531
|
}
|
|
@@ -1879,6 +1946,47 @@ window.__ModuleLoader__.load({
|
|
|
1879
1946
|
)
|
|
1880
1947
|
}
|
|
1881
1948
|
|
|
1949
|
+
/**
|
|
1950
|
+
* The row-number hint, or an empty list when the row is not highlighted.
|
|
1951
|
+
*
|
|
1952
|
+
* A highlighted row is worth explaining, and the number column is the one place that has room
|
|
1953
|
+
* without competing with a figure. Two independent reasons can apply, so the result is a list
|
|
1954
|
+
* of lines rather than a sentence:
|
|
1955
|
+
*
|
|
1956
|
+
* - the model switched, named outright;
|
|
1957
|
+
* - the cache came back cold, with the rate and the threshold it fell below.
|
|
1958
|
+
*
|
|
1959
|
+
* A cold row that ALSO changed its routed configuration names the tool-list movement, because
|
|
1960
|
+
* that is the usual cause of a lost prefix and the difference between "the account rotated"
|
|
1961
|
+
* and "a plugin was enabled". When the two tool counts are not both known, the line says only
|
|
1962
|
+
* that the configuration changed rather than inventing numbers.
|
|
1963
|
+
*/
|
|
1964
|
+
function rowHintLines(row, t) {
|
|
1965
|
+
const lines = []
|
|
1966
|
+
if (row.compaction === true) return lines
|
|
1967
|
+
if (row.switched === true) {
|
|
1968
|
+
lines.push(t('rowSwitched', { model: modelHintText(row, t) }))
|
|
1969
|
+
}
|
|
1970
|
+
if (isLowCacheHit(row)) {
|
|
1971
|
+
if (row.configChanged === true) {
|
|
1972
|
+
const from = numberOrNull(row.previousToolCount)
|
|
1973
|
+
const to = numberOrNull(row.toolCount)
|
|
1974
|
+
lines.push(
|
|
1975
|
+
from !== null && to !== null
|
|
1976
|
+
? t('rowConfigChanged', { from, to })
|
|
1977
|
+
: t('rowConfigChangedUnknown'),
|
|
1978
|
+
)
|
|
1979
|
+
}
|
|
1980
|
+
lines.push(
|
|
1981
|
+
t('rowLowCache', {
|
|
1982
|
+
rate: `${(row.cacheHit * 100).toFixed(1)}%`,
|
|
1983
|
+
threshold: Math.round(LOW_CACHE_HIT * 100),
|
|
1984
|
+
}),
|
|
1985
|
+
)
|
|
1986
|
+
}
|
|
1987
|
+
return lines
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1882
1990
|
/**
|
|
1883
1991
|
* The speed column's hint: which model served this row.
|
|
1884
1992
|
*
|
|
@@ -1961,29 +2069,33 @@ window.__ModuleLoader__.load({
|
|
|
1961
2069
|
function indexHint(t) {
|
|
1962
2070
|
const glyph = (Icon) => h(Icon, { className: 'dsh-rum-tip-icon' })
|
|
1963
2071
|
/*
|
|
1964
|
-
*
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
2072
|
+
* Turn one sentence into elements, replacing each glyph placeholder with the glyph itself.
|
|
2073
|
+
* A single pass over a token list is simpler to follow than nesting one split per
|
|
2074
|
+
* placeholder, and it keeps the placeholders in whatever order a translation uses.
|
|
1967
2075
|
*/
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
const token
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2076
|
+
const withGlyphs = (text) => {
|
|
2077
|
+
const tokens = text.split(/(\{bolt\}|\{tools\}|\{compact\})/)
|
|
2078
|
+
const nodes = []
|
|
2079
|
+
for (const token of tokens) {
|
|
2080
|
+
if (token === '') continue
|
|
2081
|
+
if (token === '{bolt}') nodes.push(glyph(BoltIcon))
|
|
2082
|
+
else if (token === '{tools}') nodes.push(glyph(ToolIcon))
|
|
2083
|
+
else if (token === '{compact}') nodes.push(glyph(CompactionIcon))
|
|
2084
|
+
else nodes.push(token)
|
|
2085
|
+
}
|
|
2086
|
+
return nodes
|
|
1977
2087
|
}
|
|
1978
2088
|
/*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
2089
|
+
* Three lines: what the column holds, what the glyphs mean, then what the row colours mean.
|
|
2090
|
+
* The colour line leads with two swatches instead of naming the hues, so a reader matches a
|
|
2091
|
+
* row against the legend by colour rather than by translating a word. The swatches take the
|
|
2092
|
+
* same tokens the rows take, so the two cannot drift apart.
|
|
1982
2093
|
*/
|
|
1983
2094
|
return h(
|
|
1984
2095
|
'span',
|
|
1985
2096
|
{ className: 'dsh-rum-tip-body' },
|
|
1986
|
-
h('span', { className: 'dsh-rum-tip-line' },
|
|
2097
|
+
h('span', { className: 'dsh-rum-tip-line' }, t('hintIndexRows')),
|
|
2098
|
+
h('span', { className: 'dsh-rum-tip-line' }, ...withGlyphs(t('hintIndexGlyphs'))),
|
|
1987
2099
|
h(
|
|
1988
2100
|
'span',
|
|
1989
2101
|
{ className: 'dsh-rum-tip-line' },
|
|
@@ -3912,15 +4024,17 @@ window.__ModuleLoader__.load({
|
|
|
3912
4024
|
: speedText(live.speed, live.estimated === true, t),
|
|
3913
4025
|
),
|
|
3914
4026
|
h('span', { className: 'dsh-rum-col', role: 'cell' }, tokensText(live.tokens, live.estimated === true, t)),
|
|
4027
|
+
/*
|
|
4028
|
+
* No hint on the live row's total.
|
|
4029
|
+
*
|
|
4030
|
+
* A running request has not reported its tool time yet, so the only
|
|
4031
|
+
* possible readings are "no tools" or a partial count — both would be
|
|
4032
|
+
* wrong about a request that is still going. The hint appears when the
|
|
4033
|
+
* row settles and the fold has the real figures.
|
|
4034
|
+
*/
|
|
3915
4035
|
h(
|
|
3916
4036
|
'span',
|
|
3917
|
-
{
|
|
3918
|
-
className: 'dsh-rum-col',
|
|
3919
|
-
role: 'cell',
|
|
3920
|
-
/* Same hint as the numbered rows, so the meaning never changes. */
|
|
3921
|
-
onMouseEnter: (event) => hint.show(toolHintText(live, t), event),
|
|
3922
|
-
onMouseLeave: hint.hide,
|
|
3923
|
-
},
|
|
4037
|
+
{ className: 'dsh-rum-col', role: 'cell' },
|
|
3924
4038
|
secondsText(
|
|
3925
4039
|
live.compaction !== null && live.compaction !== undefined
|
|
3926
4040
|
? compactionSpan(live.compaction, now)
|
|
@@ -3961,7 +4075,33 @@ window.__ModuleLoader__.load({
|
|
|
3961
4075
|
*/
|
|
3962
4076
|
h(
|
|
3963
4077
|
'span',
|
|
3964
|
-
{
|
|
4078
|
+
{
|
|
4079
|
+
className: 'dsh-rum-index',
|
|
4080
|
+
role: 'cell',
|
|
4081
|
+
/*
|
|
4082
|
+
* Only a highlighted row explains itself here; an ordinary row's number
|
|
4083
|
+
* needs no gloss. The handler is attached unconditionally but returns
|
|
4084
|
+
* nothing to show when the list is empty, so hovering a plain row stays
|
|
4085
|
+
* silent.
|
|
4086
|
+
*/
|
|
4087
|
+
onMouseEnter: (event) => {
|
|
4088
|
+
const lines = rowHintLines(entry.row, t)
|
|
4089
|
+
if (lines.length === 0) return
|
|
4090
|
+
hint.show(
|
|
4091
|
+
lines.length === 1
|
|
4092
|
+
? lines[0]
|
|
4093
|
+
: h(
|
|
4094
|
+
'span',
|
|
4095
|
+
{ className: 'dsh-rum-tip-body' },
|
|
4096
|
+
...lines.map((line, index) =>
|
|
4097
|
+
h('span', { className: 'dsh-rum-tip-line' }, line),
|
|
4098
|
+
),
|
|
4099
|
+
),
|
|
4100
|
+
event,
|
|
4101
|
+
)
|
|
4102
|
+
},
|
|
4103
|
+
onMouseLeave: hint.hide,
|
|
4104
|
+
},
|
|
3965
4105
|
entry.row.compaction === true
|
|
3966
4106
|
? h(CompactionIcon, {
|
|
3967
4107
|
className:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recent-usage-metrics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Geek level for the Performance & usage setting: a live reading of the newest request (first-token latency, speed, tokens, total, amortised) plus a drawer listing the last ten, all derived from the shipped Statistics view data",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|