dsh-recent-usage-metrics 0.3.3 → 0.3.4
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 +13 -0
- package/README.en.md +15 -0
- package/README.md +12 -0
- package/client/client.js +222 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 0.3.4
|
|
2
|
+
|
|
3
|
+
- 实时行也能显示模型名了:形态与编号行相同——本步窗口内有 `request/header` 判为切换、
|
|
4
|
+
直接用它的模型;否则沿用上一步的模型。回复落地后由 `assistant/message.source` 覆盖。
|
|
5
|
+
实测 3202 步:22 次切换全部命中,沿用 99.84%,唯一缺口是回合第一步且无 header。
|
|
6
|
+
- 实时行补上速度列的悬浮提示。未知时显示破折号而不是「使用对话模型」——后者读起来像
|
|
7
|
+
一个事实,其实只是插件当时的假设。
|
|
8
|
+
- 新增两种行高亮:模型切换用业务蓝,缓存命中低于 90% 用警告黄。
|
|
9
|
+
阈值来自实测:2921 条回复的中位数是 99.9%,1% 分位是 92.4%,90% 只标记 0.9% 的行。
|
|
10
|
+
两者同时成立时文字取警告色、左侧色条保留业务蓝,两个信号都不丢。
|
|
11
|
+
压缩行不参与缓存高亮——它已在速度列自报家门,且它的缓存数字描述的是重放而非应答。
|
|
12
|
+
- 未上报缓存读取的 provider(如 trae 把缓存并入输入)不会被当作 0% 而误标。
|
|
13
|
+
|
|
1
14
|
## 0.3.3
|
|
2
15
|
|
|
3
16
|
- 输出 Token 列的悬浮提示里,输入 token 改为带千分位的完整数字(`370,683 tok`),
|
package/README.en.md
CHANGED
|
@@ -154,6 +154,21 @@ Every drawer column and every header carries a hint:
|
|
|
154
154
|
| Tokens column | input tokens (with thousands separators, e.g. `370,683 tok`) and cache hit rate |
|
|
155
155
|
| Headers | what the column measures (the `#` header draws the four state glyphs) |
|
|
156
156
|
|
|
157
|
+
**Row highlights:**
|
|
158
|
+
|
|
159
|
+
| Trigger | Colour | Frequency |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| The request switched models | business blue (text + left bar) | 0.7% |
|
|
162
|
+
| Cache hit below 90% | warn yellow (text + left bar) | 0.9% |
|
|
163
|
+
|
|
164
|
+
When both hold, the text takes the warn colour and the bar keeps the business colour. A
|
|
165
|
+
compaction row never takes the cache highlight.
|
|
166
|
+
|
|
167
|
+
**Where the model name comes from:** a `request/header` inside the step's window means a switch
|
|
168
|
+
and supplies the model; otherwise the previous step's model carries over, and a landed reply
|
|
169
|
+
overrides both via `assistant/message.source`. The live row follows the same rule, so the model
|
|
170
|
+
is visible while the reply is still streaming.
|
|
171
|
+
|
|
157
172
|
The hints are a real overlay rather than `title`: `title` can only show plain text, and the
|
|
158
173
|
`#` header has to draw glyphs.
|
|
159
174
|
|
package/README.md
CHANGED
|
@@ -138,6 +138,18 @@ DSH 的极客级请求指标。它在 **设置 → 通用 → 性能与用量**
|
|
|
138
138
|
| 输出 Token 列 | 输入 token(带千分位,如 `370,683 tok`)与缓存命中率 |
|
|
139
139
|
| 表头 | 该列的含义(`#` 一列用图标展示四种状态) |
|
|
140
140
|
|
|
141
|
+
**行高亮:**
|
|
142
|
+
|
|
143
|
+
| 触发 | 颜色 | 实测频率 |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| 该请求切换了模型 | 业务蓝(文字 + 左侧色条) | 0.7% |
|
|
146
|
+
| 缓存命中低于 90% | 警告黄(文字 + 左侧色条) | 0.9% |
|
|
147
|
+
|
|
148
|
+
两者同时成立时文字取警告色、色条保留业务蓝。压缩行不参与缓存高亮。
|
|
149
|
+
|
|
150
|
+
**模型名的来源:** 本步窗口内的 `request/header` 判为切换,否则沿用上一步;回复落地后
|
|
151
|
+
由 `assistant/message.source` 覆盖。实时行同样适用,所以生成期间就能看到模型名。
|
|
152
|
+
|
|
141
153
|
提示是自建的浮层而不是 `title`:`title` 只能显示纯文本,而 `#` 列的说明要画出图标。
|
|
142
154
|
|
|
143
155
|
## 数字是怎么算出来的
|
package/client/client.js
CHANGED
|
@@ -481,6 +481,30 @@ window.__ModuleLoader__.load({
|
|
|
481
481
|
.dsh-rum-live-row {
|
|
482
482
|
background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);
|
|
483
483
|
}
|
|
484
|
+
/*
|
|
485
|
+
* Two reasons a finished row is worth a second look.
|
|
486
|
+
*
|
|
487
|
+
* Both use the theme's own state tokens, never a literal colour, so they follow dark mode and
|
|
488
|
+
* any future palette change. The text colour carries the meaning; the left bar makes the row
|
|
489
|
+
* findable when the drawer is scanned rather than read.
|
|
490
|
+
*
|
|
491
|
+
* Cache-outranks-switch: a cold prompt is a performance fact the user can act on, while a model
|
|
492
|
+
* change is context. When both are true the warn colour wins and the bar keeps the business
|
|
493
|
+
* colour, so neither signal is lost.
|
|
494
|
+
*/
|
|
495
|
+
.dsh-rum-item[data-switched="true"] {
|
|
496
|
+
color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));
|
|
497
|
+
box-shadow: inset 2px 0 0 var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));
|
|
498
|
+
}
|
|
499
|
+
.dsh-rum-item[data-lowcache="true"] {
|
|
500
|
+
color: var(--dsw-alias-state-warn-primary, var(--dsw-alias-state-warn-label));
|
|
501
|
+
box-shadow: inset 2px 0 0 var(--dsw-alias-state-warn-primary, var(--dsw-alias-state-warn-label));
|
|
502
|
+
}
|
|
503
|
+
/* The bar keeps the switch signal when a cold prompt outranks it in the text. */
|
|
504
|
+
.dsh-rum-item[data-switched="true"][data-lowcache="true"] {
|
|
505
|
+
color: var(--dsw-alias-state-warn-primary, var(--dsw-alias-state-warn-label));
|
|
506
|
+
box-shadow: inset 2px 0 0 var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));
|
|
507
|
+
}
|
|
484
508
|
/*
|
|
485
509
|
* The streaming marker: the same bolt the status pill pulses, sized for the index column.
|
|
486
510
|
*
|
|
@@ -1196,11 +1220,98 @@ window.__ModuleLoader__.load({
|
|
|
1196
1220
|
const provider = typeof meta.provider === 'string' && meta.provider !== '' ? meta.provider : null
|
|
1197
1221
|
if (model === null && provider === null) continue
|
|
1198
1222
|
if (typeof node.turn !== 'number' || typeof node.step !== 'number') continue
|
|
1199
|
-
index.set(`${node.turn}:${node.step}`, { provider, model })
|
|
1223
|
+
index.set(`${node.turn}:${node.step}`, { provider, model, switched: false, source: 'trajectory' })
|
|
1200
1224
|
}
|
|
1201
1225
|
return index
|
|
1202
1226
|
}
|
|
1203
1227
|
|
|
1228
|
+
/**
|
|
1229
|
+
* Which model a step used, and whether that request changed models.
|
|
1230
|
+
*
|
|
1231
|
+
* A `request/header` is written only when the routed model or tool set CHANGES, so its
|
|
1232
|
+
* placement is itself the signal:
|
|
1233
|
+
*
|
|
1234
|
+
* - inside a step's own seq window (from its `step/start` to the next one) → this request
|
|
1235
|
+
* changed the routed configuration, and the header names the model it switched to;
|
|
1236
|
+
* - absent → the step reused whatever was already routed, so it inherits the previous
|
|
1237
|
+
* step's model.
|
|
1238
|
+
*
|
|
1239
|
+
* Measured on a real 3202-step session: 22 steps carried a header and all 22 were genuine
|
|
1240
|
+
* switches; 3180 inherited, of which every one with a known predecessor matched the model
|
|
1241
|
+
* the reply later reported. The only gaps are a turn's first step with no header, where
|
|
1242
|
+
* nothing earlier is known and the model stays null.
|
|
1243
|
+
*
|
|
1244
|
+
* A finished reply overrides both, because `assistant/message.source` is the authority.
|
|
1245
|
+
*/
|
|
1246
|
+
function modelPlanFromStats(statsSnapshot, known) {
|
|
1247
|
+
const plan = new Map()
|
|
1248
|
+
const turns = statsSnapshot?.turns
|
|
1249
|
+
if (!Array.isArray(turns)) return plan
|
|
1250
|
+
for (const turn of turns) {
|
|
1251
|
+
if (!Array.isArray(turn.steps)) continue
|
|
1252
|
+
const headers = []
|
|
1253
|
+
if (Array.isArray(turn.events)) {
|
|
1254
|
+
for (const event of turn.events) {
|
|
1255
|
+
if (event?.kind !== 'request-header') continue
|
|
1256
|
+
const at = numberOrNull(event.time)
|
|
1257
|
+
if (at === null) continue
|
|
1258
|
+
if (typeof event.model !== 'string' && typeof event.provider !== 'string') continue
|
|
1259
|
+
headers.push({ time: at, provider: event.provider ?? null, model: event.model ?? null })
|
|
1260
|
+
}
|
|
1261
|
+
headers.sort((left, right) => left.time - right.time)
|
|
1262
|
+
}
|
|
1263
|
+
const starts = []
|
|
1264
|
+
for (const step of turn.steps) {
|
|
1265
|
+
const at = numberOrNull(step.startTime)
|
|
1266
|
+
if (at === null || typeof step.step !== 'number') continue
|
|
1267
|
+
starts.push({ at, step: step.step })
|
|
1268
|
+
}
|
|
1269
|
+
if (starts.length === 0) continue
|
|
1270
|
+
starts.sort((left, right) => left.at - right.at)
|
|
1271
|
+
|
|
1272
|
+
/*
|
|
1273
|
+
* The model currently in force, carried forward while the plan is built.
|
|
1274
|
+
*
|
|
1275
|
+
* A step whose header names a switch becomes the new carrier, so the step AFTER it
|
|
1276
|
+
* inherits the switched-to model — not the one before the switch, which `known` alone
|
|
1277
|
+
* would give.
|
|
1278
|
+
*/
|
|
1279
|
+
let inForce = null
|
|
1280
|
+
|
|
1281
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
1282
|
+
const key = `${turn.turn}:${starts[i].step}`
|
|
1283
|
+
const from = starts[i].at
|
|
1284
|
+
const until = i + 1 < starts.length ? starts[i + 1].at : Number.POSITIVE_INFINITY
|
|
1285
|
+
let inside = null
|
|
1286
|
+
for (const header of headers) {
|
|
1287
|
+
if (header.time < from) continue
|
|
1288
|
+
if (header.time >= until) break
|
|
1289
|
+
inside = header
|
|
1290
|
+
}
|
|
1291
|
+
if (inside !== null) {
|
|
1292
|
+
// The header was written for THIS request, so the model changed here.
|
|
1293
|
+
inForce = { provider: inside.provider, model: inside.model }
|
|
1294
|
+
plan.set(key, { provider: inside.provider, model: inside.model, switched: true, source: 'header' })
|
|
1295
|
+
continue
|
|
1296
|
+
}
|
|
1297
|
+
/*
|
|
1298
|
+
* No header: inherit. A finished reply outranks the carried model, because
|
|
1299
|
+
* `assistant/message.source` is the authority for a step that has settled.
|
|
1300
|
+
*/
|
|
1301
|
+
const settled = known === undefined ? undefined : known.get(key)
|
|
1302
|
+
if (settled !== undefined && settled.model !== null) {
|
|
1303
|
+
inForce = { provider: settled.provider, model: settled.model }
|
|
1304
|
+
plan.set(key, { provider: settled.provider, model: settled.model, switched: false, source: 'reply' })
|
|
1305
|
+
continue
|
|
1306
|
+
}
|
|
1307
|
+
if (inForce !== null) {
|
|
1308
|
+
plan.set(key, { provider: inForce.provider, model: inForce.model, switched: false, source: 'inherit' })
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return plan
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1204
1315
|
/**
|
|
1205
1316
|
* Whether any step started after this compaction finished.
|
|
1206
1317
|
*
|
|
@@ -1256,17 +1367,23 @@ window.__ModuleLoader__.load({
|
|
|
1256
1367
|
*/
|
|
1257
1368
|
function historyFromStats(statsSnapshot, models) {
|
|
1258
1369
|
const steps = statsSteps(statsSnapshot)
|
|
1370
|
+
/*
|
|
1371
|
+
* `models` carries the per-step attribution worked out from the trajectory nodes and the
|
|
1372
|
+
* turn's request headers. It answers two questions at once: which model, and whether this
|
|
1373
|
+
* request changed it — the drawer colours a row for the second.
|
|
1374
|
+
*/
|
|
1375
|
+
const plan = modelPlanFromStats(statsSnapshot, models)
|
|
1259
1376
|
const rows = []
|
|
1260
1377
|
for (const entry of steps) {
|
|
1261
1378
|
// A step only has a row once it has started; `step/start` is what supplies this.
|
|
1262
1379
|
if (numberOrNull(entry.metrics.startTime) === null) continue
|
|
1263
1380
|
if (!statsStepHasFigures(entry.metrics)) continue
|
|
1264
1381
|
const row = readingOfStatsStep(entry)
|
|
1265
|
-
|
|
1266
|
-
const owner = models === undefined ? undefined : models.get(row.key)
|
|
1382
|
+
const owner = plan.get(row.key) ?? (models === undefined ? undefined : models.get(row.key))
|
|
1267
1383
|
if (owner !== undefined) {
|
|
1268
1384
|
row.model = owner.model
|
|
1269
1385
|
row.provider = owner.provider
|
|
1386
|
+
row.switched = owner.switched === true
|
|
1270
1387
|
}
|
|
1271
1388
|
rows.push(row)
|
|
1272
1389
|
}
|
|
@@ -1529,6 +1646,32 @@ window.__ModuleLoader__.load({
|
|
|
1529
1646
|
return approximate ? t('approxTokensUnit', { value: rounded }) : t('tokensUnit', { value: rounded })
|
|
1530
1647
|
}
|
|
1531
1648
|
|
|
1649
|
+
/**
|
|
1650
|
+
* Cache-hit rate below which a row is worth flagging.
|
|
1651
|
+
*
|
|
1652
|
+
* Measured over 2921 replies in a real session: the median is 99.9% and the 1st percentile
|
|
1653
|
+
* is 92.4%, because a warm conversation re-reads its prefix almost every time. 90% therefore
|
|
1654
|
+
* marks a genuinely cold request rather than ordinary variation, and flags only 0.9% of rows.
|
|
1655
|
+
* A threshold at 70% would flag 0.6% — nearly the same rows, with less headroom above the
|
|
1656
|
+
* noise.
|
|
1657
|
+
*/
|
|
1658
|
+
const LOW_CACHE_HIT = 0.9
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* Whether a row should be drawn as a low-cache-hit row.
|
|
1662
|
+
*
|
|
1663
|
+
* Only a REPORTED rate counts. A provider that folds cache reads into its input count (trae
|
|
1664
|
+
* does) reports none, and treating "not reported" as "0%" would flag every one of its rows.
|
|
1665
|
+
* A compaction is excluded: it already announces itself in the speed column, and its cache
|
|
1666
|
+
* figure describes replaying the conversation rather than serving a request.
|
|
1667
|
+
*/
|
|
1668
|
+
function isLowCacheHit(row) {
|
|
1669
|
+
if (row.compaction === true) return false
|
|
1670
|
+
const rate = numberOrNull(row.cacheHit)
|
|
1671
|
+
if (rate === null) return false
|
|
1672
|
+
return rate < LOW_CACHE_HIT
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1532
1675
|
/**
|
|
1533
1676
|
* A whole token count with thousands separators, for a hover hint.
|
|
1534
1677
|
*
|
|
@@ -1661,12 +1804,23 @@ window.__ModuleLoader__.load({
|
|
|
1661
1804
|
)
|
|
1662
1805
|
}
|
|
1663
1806
|
|
|
1664
|
-
/**
|
|
1807
|
+
/**
|
|
1808
|
+
* The speed column's hint: which model served this row.
|
|
1809
|
+
*
|
|
1810
|
+
* A request with no attribution shows the dash rather than a guess. "Chat model" would read
|
|
1811
|
+
* as a fact about the request when it is only what the plugin would have assumed, and a
|
|
1812
|
+
* wrong model name is worse than none — that is the whole reason this column stopped
|
|
1813
|
+
* borrowing `request/header` rows.
|
|
1814
|
+
*
|
|
1815
|
+
* A compaction is different: it does not "have no model", it has one that is only reported
|
|
1816
|
+
* once the summary lands, so while it runs the generic wording is honest.
|
|
1817
|
+
*/
|
|
1665
1818
|
function modelHintText(row, t) {
|
|
1666
1819
|
if (row.compaction === true) return compactionModelName(row, t)
|
|
1667
1820
|
const model = typeof row.model === 'string' && row.model !== '' ? row.model : null
|
|
1668
1821
|
const provider = typeof row.provider === 'string' && row.provider !== '' ? row.provider : null
|
|
1669
|
-
if (model === null) return t('
|
|
1822
|
+
if (model === null && provider === null) return t('none')
|
|
1823
|
+
if (model === null) return provider
|
|
1670
1824
|
return provider === null ? model : `${provider} / ${model}`
|
|
1671
1825
|
}
|
|
1672
1826
|
|
|
@@ -2158,6 +2312,7 @@ window.__ModuleLoader__.load({
|
|
|
2158
2312
|
sessionTurnOpen,
|
|
2159
2313
|
sessionRunning,
|
|
2160
2314
|
sessionStats,
|
|
2315
|
+
models,
|
|
2161
2316
|
) {
|
|
2162
2317
|
const partial = useChat((snapshot) => snapshot.legacy.partial)
|
|
2163
2318
|
const nodes = useChat((snapshot) => snapshot.legacy.nodes)
|
|
@@ -2341,6 +2496,20 @@ window.__ModuleLoader__.load({
|
|
|
2341
2496
|
* lag the fold.
|
|
2342
2497
|
* 3. Reconstruction from the previous step's last tool result, then the turn's start.
|
|
2343
2498
|
*/
|
|
2499
|
+
/*
|
|
2500
|
+
* The model for the running step, worked out the same way the drawer does it: a header
|
|
2501
|
+
* written inside this step names a switch, otherwise the previous step's model carries
|
|
2502
|
+
* over. A finished reply overrides both, which is why the plan prefers it.
|
|
2503
|
+
*
|
|
2504
|
+
* This is what lets the live row show a model while it is still streaming instead of
|
|
2505
|
+
* waiting for `assistant/message` to land.
|
|
2506
|
+
*/
|
|
2507
|
+
const livePlan =
|
|
2508
|
+
sessionStats === undefined ? undefined : modelPlanFromStats(sessionStats, models)
|
|
2509
|
+
const liveOwner =
|
|
2510
|
+
liveStep === null || livePlan === undefined
|
|
2511
|
+
? undefined
|
|
2512
|
+
: livePlan.get(`${liveStep.turn}:${liveStep.step}`)
|
|
2344
2513
|
/*
|
|
2345
2514
|
* A compaction has no step to measure, so the reading is built from the compaction alone:
|
|
2346
2515
|
* its own clock, its own span and its own model. Returning here keeps every step-shaped
|
|
@@ -2446,6 +2615,9 @@ window.__ModuleLoader__.load({
|
|
|
2446
2615
|
: null,
|
|
2447
2616
|
replyLanded: true,
|
|
2448
2617
|
phase: livePhase,
|
|
2618
|
+
model: liveOwner?.model ?? null,
|
|
2619
|
+
provider: liveOwner?.provider ?? null,
|
|
2620
|
+
switched: liveOwner?.switched === true,
|
|
2449
2621
|
compaction: activeCompaction,
|
|
2450
2622
|
}
|
|
2451
2623
|
}
|
|
@@ -2472,6 +2644,9 @@ window.__ModuleLoader__.load({
|
|
|
2472
2644
|
amortized: tokens > 0 && elapsedMs !== null && elapsedMs > 0 ? tokens / (elapsedMs / 1000) : null,
|
|
2473
2645
|
replyLanded: false,
|
|
2474
2646
|
phase: livePhase,
|
|
2647
|
+
model: liveOwner?.model ?? null,
|
|
2648
|
+
provider: liveOwner?.provider ?? null,
|
|
2649
|
+
switched: liveOwner?.switched === true,
|
|
2475
2650
|
compaction: activeCompaction,
|
|
2476
2651
|
}
|
|
2477
2652
|
}
|
|
@@ -2936,6 +3111,14 @@ window.__ModuleLoader__.load({
|
|
|
2936
3111
|
return last.running !== false
|
|
2937
3112
|
}, [sessionStats])
|
|
2938
3113
|
const chatNodes = useChat((snapshot) => snapshot.legacy.nodes)
|
|
3114
|
+
/*
|
|
3115
|
+
* The per-step model attribution, computed once and shared by the live reading and the
|
|
3116
|
+
* history so the two can never disagree about which model a step used.
|
|
3117
|
+
*/
|
|
3118
|
+
const models = useMemo(
|
|
3119
|
+
() => modelIndexFromTrajectory(trajectory),
|
|
3120
|
+
[trajectory],
|
|
3121
|
+
)
|
|
2939
3122
|
const live = useLiveReading(
|
|
2940
3123
|
useChat,
|
|
2941
3124
|
geek,
|
|
@@ -2944,6 +3127,7 @@ window.__ModuleLoader__.load({
|
|
|
2944
3127
|
sessionTurnOpen,
|
|
2945
3128
|
sessionRunning,
|
|
2946
3129
|
sessionStats,
|
|
3130
|
+
models,
|
|
2947
3131
|
)
|
|
2948
3132
|
|
|
2949
3133
|
const triggerRef = useRef(null)
|
|
@@ -2987,8 +3171,8 @@ window.__ModuleLoader__.load({
|
|
|
2987
3171
|
() =>
|
|
2988
3172
|
sessionStats === undefined
|
|
2989
3173
|
? undefined
|
|
2990
|
-
: historyFromStats(sessionStats,
|
|
2991
|
-
[sessionStats,
|
|
3174
|
+
: historyFromStats(sessionStats, models),
|
|
3175
|
+
[sessionStats, models],
|
|
2992
3176
|
)
|
|
2993
3177
|
/*
|
|
2994
3178
|
* Fallback for a composition without the statistics target: derive rows from the Chat
|
|
@@ -3512,7 +3696,17 @@ window.__ModuleLoader__.load({
|
|
|
3512
3696
|
live !== null
|
|
3513
3697
|
? h(
|
|
3514
3698
|
'div',
|
|
3515
|
-
{
|
|
3699
|
+
{
|
|
3700
|
+
className: 'dsh-rum-row dsh-rum-live-row',
|
|
3701
|
+
role: 'row',
|
|
3702
|
+
/*
|
|
3703
|
+
* The live row carries the same two highlights as a settled one, so a
|
|
3704
|
+
* model switch is visible the moment the request starts rather than
|
|
3705
|
+
* only once its row is numbered.
|
|
3706
|
+
*/
|
|
3707
|
+
'data-switched': live.switched === true ? 'true' : undefined,
|
|
3708
|
+
'data-lowcache': isLowCacheHit(live) ? 'true' : undefined,
|
|
3709
|
+
},
|
|
3516
3710
|
/*
|
|
3517
3711
|
* A pulsing dot, no word.
|
|
3518
3712
|
*
|
|
@@ -3574,7 +3768,18 @@ window.__ModuleLoader__.load({
|
|
|
3574
3768
|
h('span', { className: 'dsh-rum-col', role: 'cell' }, secondsText(live.ttftMs, t)),
|
|
3575
3769
|
h(
|
|
3576
3770
|
'span',
|
|
3577
|
-
{
|
|
3771
|
+
{
|
|
3772
|
+
className: 'dsh-rum-col dsh-rum-primary',
|
|
3773
|
+
role: 'cell',
|
|
3774
|
+
/*
|
|
3775
|
+
* The same hint the numbered rows carry: which model is answering.
|
|
3776
|
+
* A compaction names its own summarizer; a request names the model
|
|
3777
|
+
* the plan worked out, and falls back to the generic wording while
|
|
3778
|
+
* nothing is known yet — a turn's first step with no header.
|
|
3779
|
+
*/
|
|
3780
|
+
onMouseEnter: (event) => hint.show(modelHintText(live, t), event),
|
|
3781
|
+
onMouseLeave: hint.hide,
|
|
3782
|
+
},
|
|
3578
3783
|
live.compaction !== null && live.compaction !== undefined
|
|
3579
3784
|
? t('compactionSpeed')
|
|
3580
3785
|
: speedText(live.speed, live.estimated === true, t),
|
|
@@ -3606,6 +3811,14 @@ window.__ModuleLoader__.load({
|
|
|
3606
3811
|
'dsh-rum-row dsh-rum-item' +
|
|
3607
3812
|
(entry.row.compaction === true ? ' dsh-rum-compaction-row' : ''),
|
|
3608
3813
|
'data-phase': entry.phase,
|
|
3814
|
+
/*
|
|
3815
|
+
* Two independent highlights. A request that changed the routed model
|
|
3816
|
+
* gets the business colour; one whose cache hit fell below the threshold
|
|
3817
|
+
* gets the warn colour. Both are attributes rather than inline colours so
|
|
3818
|
+
* the stylesheet owns the tokens and the dark-mode behaviour.
|
|
3819
|
+
*/
|
|
3820
|
+
'data-switched': entry.row.switched === true ? 'true' : undefined,
|
|
3821
|
+
'data-lowcache': isLowCacheHit(entry.row) ? 'true' : undefined,
|
|
3609
3822
|
role: 'row',
|
|
3610
3823
|
},
|
|
3611
3824
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recent-usage-metrics",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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",
|