dsh-consumption-badge 1.2.2 → 1.3.2

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 CHANGED
@@ -2,15 +2,169 @@
2
2
 
3
3
  A **persistent** DSH (DeepSeek Harness) Web UI plugin that puts a live consumption badge
4
4
  next to every assistant message, and opens a detail panel with the session's spend curve.
5
- The UI is **bilingual**: it follows the shell language (Chinese or English) and switches live.
5
+ The UI is **bilingual** (follows the shell language, switches live), costs are estimated for
6
+ **33 model entries across 10 providers**, amounts can be shown in **any of 17 major
7
+ currencies**, and one dropdown picks the **model** you want to look at. All of it lives in
8
+ Settings → Usage.
6
9
 
7
10
  ```
8
- ⛽ 25.6k | ≈¥0.020 | Off-peak
11
+ ⛽ 3.2k | ≈¥0.004 | Off-peak | Balance = ¥42.50
9
12
  ```
10
13
 
11
- - **Collapsed pill** – session-cumulative tokens, estimated cost (always prefixed `≈`), and the
12
- current peak/off-peak tier (red `Peak`, green `Off-peak`), injected into
14
+ - **Collapsed pill** – **that assistant message's own** token count, its **own** estimated cost
15
+ (always prefixed `≈`), its pricing tier (red `Peak`, green `Off-peak`, attributed to the turn's
16
+ request start), and the DeepSeek **account balance**; injected into
13
17
  `conversation.chat.assistant-actions` right after the shipped copy/branch controls.
18
+ The two numbers are **this turn's, not the session's**: the slot renders once per turn
19
+ (`TurnTailNodeView`) and hands the badge that turn's final `messageId`; the host answers with a
20
+ `messageId → message` map (which carries the turn number) next to the per-turn fold, and the badge
21
+ renders that turn's folded total. Mapping is resolved in two steps so the pill is right even
22
+ before the host half is restarted: the host map first, then the `data-turn-tail` attribute the
23
+ shipped conversation view puts on the turn wrapper. If neither resolves (or the turn has no record
24
+ yet) the pill falls back to the session-wide figures instead of showing a false zero, and its
25
+ tooltip names the scope being shown. The balance segment renders `Balance = ¥42.50` when the
26
+ account API answers, `…` while loading and `—` on failure (the reason rides the tooltip); balance
27
+ uses the same 60 s cache as the modal, refreshed once a minute. The modal itself still shows the
28
+ whole session, unchanged.
29
+ - **Detail modal** (centered, blurred backdrop, `Esc` or backdrop-click to close):
30
+ cumulative tokens with hit/miss/output split, estimated cost, current pricing tier,
31
+ DeepSeek account balance, a **model dropdown** in the title bar, and a **per-turn dual-axis line
32
+ chart** (left axis: tokens, right axis: money) where **each model gets its own colored line** and
33
+ the legend names them; the hover tooltip shows the model (with its color dot), input-hit /
34
+ input-miss / output tokens, the turn's cost and its peak/off-peak flag. Picking a model filters the
35
+ cards + chart to that model and turns the balance hint into "about N tokens left at these prices".
36
+ - **Settings page · Usage** (`settings.section`, id `balance-dsh-consumption`, order 26):
37
+ a top row of four cards — **Total tokens** (with hit/miss/output split, all-history totals),
38
+ **Estimated cost**, **Current window** (peak/off-peak) and **Account balance** (reusing the
39
+ badge's cached balance) — then **all-session history** as one line chart, filterable by time range
40
+ (Today / Last 7 days / Last 30 days / Last 12 months), by workspace
41
+ (All workspaces + every registered workspace + Unassigned), and by an explicit
42
+ calendar range that overrides the dropdown. Bucket size follows the range:
43
+ **hourly for today and any single-day calendar range**, daily for multi-day ranges, and
44
+ monthly for the last 12 months. Left axis (tokens) is hidden until you
45
+ toggle it in the legend; the right axis (¥) is always on. Hovering a point shows its
46
+ total, its peak/off-peak split and the models involved; the footer sums the current
47
+ selection. No data in range → a plain "No usage recorded in this range" note instead of an
48
+ empty chart. The cards are **all-history** totals (independent of the filters); the footer is the
49
+ filtered total, so the two numbers differ by design.
50
+ - **Personalization integration** (auto-detected, no configuration): when
51
+ [`dsh-plugin-personalization`](https://www.npmjs.com/package/dsh-plugin-personalization) is
52
+ installed alongside this plugin, a **Usage popup** block is appended to its settings page —
53
+ same `pp-block` / `pp-field` / `pp-modes` layout and the same `Pill` primitive as the shipped
54
+ personalization rows — offering four backgrounds for the consumption modal:
55
+ **Default / Glass / Custom image / Solid color**.
56
+ - **Default** keeps the themed surface; **Glass** is real `backdrop-filter` glass — both the veil
57
+ behind the modal and the panel itself are theme-derived (`--dsw-alias-bg-base`), so light themes
58
+ get a light haze and dark themes a dark one instead of the panel painting a dark slab over the
59
+ page; **Custom image** takes an image URL *or* a local image plus an **Opacity** slider
60
+ (0–100 %, default 85 %) that sets the background layer's own `opacity` — the same semantic as the
61
+ other personalization background rows, so lowering it fades the image over the themed panel
62
+ instead of dimming it; **Solid color** takes a hex colour and derives a light/dark ink palette so
63
+ the cards, chart and tooltips stay readable (the palette also overrides the tooltip surface).
64
+ - A **Glass** switch appears for Custom image and Solid color, so glass composites with them:
65
+ on an image it blurs the image itself (frosted photo), on a solid it makes the colour
66
+ translucent over a blurred page. A **Glass strength** slider (0–48 px, default 22 px) sets the
67
+ blur radius of the panel. The overlay behind the modal is deliberately never blurred.
68
+ - **Clear** wipes the image, the URL field and the file picker in one go. The image row shows a
69
+ **thumbnail** plus the live save state (Saving… / Saved on this device / `error: …`), and
70
+ failures are logged with a `[consumption-badge]` tag instead of failing silently. The picker is a
71
+ button (Choose image… / Change image…), not a raw file input, so saving never leaves a
72
+ misleading "no file chosen" label behind.
73
+ - Local images are **stored on this machine with no size limit**: the picked file is written as a
74
+ Blob into IndexedDB (`dsh-consumption-assets` → `images`), and the preference only keeps a small
75
+ key — so a 20 MB wallpaper no longer blows the `localStorage` quota. On startup the blob is read
76
+ back into a `blob:` URL for the modal; **Clear** deletes the stored record as well.
77
+ If IndexedDB is unavailable the file is read as a data URL instead, and the row says so.
78
+ - The choice is a local preference (`dsh-consumption:modal-style`) and is applied to the open
79
+ modal immediately. Nothing about the conversation or sidebar backgrounds is touched.
80
+ - **Update notice** (same shape as `dsh-plugin-personalization`'s): ~2.5 s after the page loads the
81
+ plugin asks `https://registry.npmjs.org/dsh-consumption-badge/latest` for the newest version
82
+ (24 h cache, 5 s timeout, fails silently). If the installed version is older — and that version
83
+ was not ignored — a non-blocking card appears in the bottom-right corner with
84
+ **current → latest**, the exact `dsh plugin --profile web update dsh-consumption-badge` command,
85
+ and three actions: **Copy update command**, **Ignore this version** (remembered on this machine)
86
+ and **Remind me later** (this page load only). It never blocks the conversation and never covers
87
+ controls.
88
+
89
+ Everything is an **estimate** for reference only — this plugin is not a billing system of record.
90
+
91
+ ## Models and pricing
92
+
93
+ Costs are estimated from a **flat price table of 31 model entries** (current generations first,
94
+ legacy entries last so old logs still resolve). Unknown ids fall back to a family guess
95
+ (`pro`/`opus`/`max` → the stronger tier, everything else → DeepSeek V3) and are marked as estimates.
96
+
97
+ | Provider | Current | Legacy (kept for old logs) |
98
+ | --- | --- | --- |
99
+ | DeepSeek | `deepseek-flash` = **DeepSeek-V4.1-Flash-1017**, `deepseek-v4-pro` = **DeepSeek-V4-Pro-0813** (official ids/versions) | DeepSeek V3.2 |
100
+ | OpenAI | GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna, o3 | GPT-4o, GPT-4o mini |
101
+ | Anthropic | Claude Opus 5.5, Sonnet 5.5, Haiku 5.5 | Claude Sonnet 4.6, Opus 4.x |
102
+ | Google | Gemini 3.8 Flash, Gemini 3.1 Pro, Gemini 3 Flash | Gemini 2.5 Flash |
103
+ | Alibaba | Qwen3.8 Max, Qwen3.8 Flash | Qwen3.7 Plus |
104
+ | Zhipu | GLM-5.3, GLM-5.1 | GLM-4.6 |
105
+ | Moonshot | Kimi K3.1 Pro, Kimi K3 | Kimi K2.5 |
106
+ | xAI | Grok 4.7 | Grok 4.3 |
107
+ | Meta | Llama 4 Maverick | — |
108
+ | Mistral | Mistral Large 4 | — |
109
+
110
+ Rules that follow from the table:
111
+
112
+ - **Every turn is priced with the model it actually used** — a session that mixed models is not
113
+ priced with a single rate. Sessions, buckets and charts all keep the per-model split.
114
+ - **Only DeepSeek has peak/off-peak pricing** (×2 in the Beijing-time peak windows). Other vendors
115
+ are flat; their card hint says so.
116
+ - **Prices are CNY per 1M tokens.** Non-DeepSeek entries are the vendor's USD list price × 7.1
117
+ (input-cache-read / input / output), taken from OpenRouter's public model catalogue; the two
118
+ harness-native DeepSeek rows keep DeepSeek's official CNY table. Edit `MODELS` at the top of
119
+ `lib/client.js` — each entry carries `price {hit, miss, out}`, `peakPricing`, `legacy` and a chart
120
+ `color`.
121
+ - **Model ids are resolved transparently.** `deepseek-flash` is the id DeepSeek's API currently
122
+ routes to **DeepSeek-V4.1-Flash-1017** and `deepseek-v4-pro` to **DeepSeek-V4-Pro-0813** (per
123
+ `api-docs.deepseek.com`), and deprecated ids (`deepseek-v4-flash*`, `deepseek-chat`) route to the
124
+ same families. Every model label in the UI can therefore be checked against the log: the settings
125
+ cost card, the modal cost card, the legend tooltip and the per-turn tooltip all print
126
+ `Family name (Official MODEL VERSION · log id <id>)`, so a mismatch is visible instead of hidden.
127
+ - The **Model** dropdown (panel title bar *and* Settings → Usage, sharing one preference stored in
128
+ `localStorage: dsh-consumption:model-filter`) lists models **grouped by provider** in
129
+ `<optgroup>`s; it filters the statistics **and** acts as the pricing fallback for turns whose log
130
+ entry carries no model. `All models` turns both behaviours off.
131
+ - Charts draw **one line per model** in that model's color (33 distinct colors), with a legend of
132
+ model chips; the token axis stays a single dashed neutral line.
133
+
134
+ ## Currency
135
+
136
+ The price table is always **CNY per 1M tokens** (that is what DeepSeek bills in); the currency
137
+ setting only decides how amounts are *displayed*. Pick it in **Settings → Usage → Currency**:
138
+
139
+ | Aspect | Behavior |
140
+ | --- | --- |
141
+ | Currencies | CNY (default) plus USD, EUR, GBP, JPY, HKD, TWD, KRW, SGD, AUD, CAD, CHF, INR, RUB, BRL, THB, MXN |
142
+ | What converts | every amount in the plugin: pill (`≈# dsh-consumption-badge
143
+
144
+ A **persistent** DSH (DeepSeek Harness) Web UI plugin that puts a live consumption badge
145
+ next to every assistant message, and opens a detail panel with the session's spend curve.
146
+ The UI is **bilingual** (follows the shell language, switches live) and amounts can be shown in
147
+ **any of 17 major currencies** — pick one in Settings → Usage.
148
+
149
+ ```
150
+ ⛽ 3.2k | ≈¥0.004 | Off-peak | Balance = ¥42.50
151
+ ```
152
+
153
+ - **Collapsed pill** – **that assistant message's own** token count, its **own** estimated cost
154
+ (always prefixed `≈`), its pricing tier (red `Peak`, green `Off-peak`, attributed to the turn's
155
+ request start), and the DeepSeek **account balance**; injected into
156
+ `conversation.chat.assistant-actions` right after the shipped copy/branch controls.
157
+ The two numbers are **this turn's, not the session's**: the slot renders once per turn
158
+ (`TurnTailNodeView`) and hands the badge that turn's final `messageId`; the host answers with a
159
+ `messageId → message` map (which carries the turn number) next to the per-turn fold, and the badge
160
+ renders that turn's folded total. Mapping is resolved in two steps so the pill is right even
161
+ before the host half is restarted: the host map first, then the `data-turn-tail` attribute the
162
+ shipped conversation view puts on the turn wrapper. If neither resolves (or the turn has no record
163
+ yet) the pill falls back to the session-wide figures instead of showing a false zero, and its
164
+ tooltip names the scope being shown. The balance segment renders `Balance = ¥42.50` when the
165
+ account API answers, `…` while loading and `—` on failure (the reason rides the tooltip); balance
166
+ uses the same 60 s cache as the modal, refreshed once a minute. The modal itself still shows the
167
+ whole session, unchanged.
14
168
  - **Detail modal** (centered, blurred backdrop, `Esc` or backdrop-click to close):
15
169
  cumulative tokens with hit/miss/output split, estimated cost, current pricing tier,
16
170
  DeepSeek account balance, and a **per-turn dual-axis line chart**
@@ -71,6 +225,17 @@ The UI is **bilingual**: it follows the shell language (Chinese or English) and
71
225
 
72
226
  Everything is an **estimate** for reference only — this plugin is not a billing system of record.
73
227
 
228
+ ), modal cards and both charts' right axis, the settings cards and footer totals, and the account balance (when the balance API reports CNY) |
229
+ | Rates | 1 CNY = n target currency, cached **6 h**. Primary source `open.er-api.com`, fallback `@fawazahmed0/currency-api` via jsDelivr — fetched through the host route `/api/consumption/rates`, and **directly from the browser** when that route is unavailable (both endpoints send `access-control-allow-origin: *`) |
230
+ | Offline | if no live rate can be fetched, a built-in fallback table is used and the settings hint says so |
231
+ | Preference | `localStorage` key `dsh-consumption:currency`; the settings hint shows the active rate, its source and a timestamp |
232
+ | Unchanged | the pricing table, peak/off-peak rules and every stored record stay in CNY — switching currency never rewrites data |
233
+
234
+ ```
235
+ costCNY = (hit/1e6)*hit + (miss/1e6)*miss + (out/1e6)*out [×2 at peak]
236
+ shown = costCNY × rate[currency]
237
+ ```
238
+
74
239
  ## Localization
75
240
 
76
241
  The plugin ships **Chinese and English** copy and follows the DSH shell language live — no reload,
@@ -191,8 +356,9 @@ The host half registers three exact, read-only routes on the DSH web server:
191
356
 
192
357
  | Route | Returns |
193
358
  | --- | --- |
194
- | `GET /api/consumption/turns?sessionId=<id>` | per-turn `{turn, hit, miss, out, time, model}` (log-length cached) |
359
+ | `GET /api/consumption/turns?sessionId=<id>` | per-turn `{turn, hit, miss, out, time, model}` **plus** per-message `{id, turn, step, hit, miss, out, time, model}` (the `id` is the finalized message id the badge receives as `messageId`); log-length cached |
195
360
  | `GET /api/consumption/account` | current provider/model + whether the key is configured |
361
+ | `GET /api/consumption/rates[?force=1]` | `{ok, base:"CNY", rates:{USD:…}, source, fetchedAt, stale}` — 16 curated currencies, 6 h cache; `stale:true` means the built-in fallback table |
196
362
  | `GET /api/consumption/balance[?force=1]` | `{ok, available, currency, total, granted, toppedUp}`; `is_available:false` renders a red "insufficient balance" |
197
363
 
198
364
  Failures are `{ok:false, code, detail, error}`: `code` is language-neutral
@@ -222,7 +388,7 @@ Install the DSH plugin "dsh-consumption-badge" into my web profile and prove it
222
388
  `{"ok":true,...}`. Verify the client half:
223
389
  `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/plugins/dsh-consumption-badge/client.js`
224
390
  must return `200`.
225
- 5. Open any session and confirm the `⛽ <tokens> | ≈¥<cost> | Peak|Off-peak` pill in the assistant
391
+ 5. Open any session and confirm the `⛽ <tokens> | ≈¥<cost> | Peak|Off-peak | Balance = ¥<total>` pill in the assistant
226
392
  action row; click it and confirm the modal shows the four cards, the dual-axis chart, the
227
393
  balance, and that Esc closes it.
228
394
  6. Keep Settings → Usage open, then switch the shell language between Chinese and English
@@ -255,7 +421,7 @@ plugin by "running" it — a session-scoped dynamic plugin always dies with the
255
421
  - Do not hard-code user-visible English or Chinese in new JSX: call `tt('…')` with the Chinese copy
256
422
  as the key and add the English entry to `DICT_EN`. `check.mjs` fails if any `tt()` key has no
257
423
  English translation or if a translation still contains Chinese.
258
- - Self-check without booting DSH: `node check.mjs` (224 assertions over the route contract, the
424
+ - Self-check without booting DSH: `node check.mjs` (320 assertions over the route contract, the
259
425
  client bundle contract, the pricing/aggregation maths and the bilingual copy). It lives outside
260
426
  the published `files` list.
261
427