dsh-token-use 0.2.0 → 0.2.1
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.en.md +8 -0
- package/README.md +8 -0
- package/client/client.js +259 -77
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -84,6 +84,14 @@ pnpm run build # regenerate client/client.js (= tree-shaken ECharts + cli
|
|
|
84
84
|
|
|
85
85
|
`client/client.js` is a committed build artifact, so users install nothing and build nothing.
|
|
86
86
|
|
|
87
|
+
**Development loop** (with this repo linked into a profile: `dsh plugin --profile web add /path/to/dsh-token-use`):
|
|
88
|
+
|
|
89
|
+
- Edit `client/src.js` → `pnpm run build` → **the panel in the browser hot-reloads by itself** (the host stat-polls every plugin row's client bundle, pushes `rebuilt` over the SSE channel `/plugins/events`, and the browser swaps it in) — no `dsh web` restart and no page refresh; measured swap latency is under a second.
|
|
90
|
+
- Edit `lib/*.js` (the host half, e.g. `pricing.js`) → still needs a `dsh web` restart.
|
|
91
|
+
- Hot reload requires an **idempotent `apply()`**: a reload brings a new fiber in, and re-registering the same locale namespace or slot on top of the previous one throws, which takes the whole settings section down until the page is refreshed. The dictionaries, the rail stylesheet and the `settings.section` registration here all take over from the previous fiber, so reloads are safe.
|
|
92
|
+
|
|
93
|
+
The UI is built with DSH's own component library `@deepseek-ai/dsh-client-ui-primitives` (`Button` / `Pill` / `Input` / `Menu` / `Tooltip` / `StateDot` and its icon set): a packaged plugin just `require`s it, and each control degrades to a native one on a shell that predates it. When previewing a dynamic cordis plugin in creator mode — where external imports are unavailable — use `window.__DSH_MODULES__.import("@deepseek-ai/dsh-client-ui-primitives")` instead (the module is one of the shell's static modules; a normal Web GUI has no `window.__DSH_MODULES__`).
|
|
94
|
+
|
|
87
95
|
Published to npm as `dsh-token-use`; its `repository` field points back here, which is how the official market links the package and shows download counts. To cut a release: bump `version`, run `npm publish`, and users pick it up with `dsh plugin update` or the market's update button.
|
|
88
96
|
|
|
89
97
|
## Field definitions
|
package/README.md
CHANGED
|
@@ -84,6 +84,14 @@ pnpm run build # 重新生成 client/client.js(= 精简 ECharts + clien
|
|
|
84
84
|
|
|
85
85
|
`client/client.js` 是已提交的构建产物,使用者无需安装依赖或构建。
|
|
86
86
|
|
|
87
|
+
**开发环路**(把本仓库以 `link:` 装进 profile 后:`dsh plugin --profile web add /path/to/dsh-token-use`):
|
|
88
|
+
|
|
89
|
+
- 改 `client/src.js` → `pnpm run build` → **浏览器里的面板自动热更新**(宿主侧会轮询每个插件行的 client bundle,变化时通过 SSE `/plugins/events` 推 `rebuilt`,浏览器侧热替换),**不用重启 `dsh web`、也不用刷新页面**;实测换装延迟 < 1s。
|
|
90
|
+
- 改 `lib/*.js`(宿主侧,如 `pricing.js`)→ 仍需重启 `dsh web`。
|
|
91
|
+
- **热重载要求 `apply()` 幂等**:重载会把新 fiber 装进来,若它直接重复注册同一个 locale 命名空间 / 槽位就会抛错,整个设置分区会消失直到刷新页面。本插件的字典、导航样式与 `settings.section` 注册都是「接管式」的(先退掉旧注册再登记),因此可以安全热重载。
|
|
92
|
+
|
|
93
|
+
界面用 DSH 自带的组件库 `@deepseek-ai/dsh-client-ui-primitives`(`Button` / `Pill` / `Input` / `Menu` / `Tooltip` / `StateDot` 与图标集):打包插件里直接 `require("@deepseek-ai/dsh-client-ui-primitives")` 即可;老版本 shell 上组件缺失时逐项降级为原生控件。**创造模式预览动态 cordis 插件**时无法引入外部依赖,可改用 `window.__DSH_MODULES__.import("@deepseek-ai/dsh-client-ui-primitives")`(该模块属于 shell 的静态模块,普通 Web GUI 里没有 `window.__DSH_MODULES__`)。
|
|
94
|
+
|
|
87
95
|
已发布到 npm(包名 `dsh-token-use`,`repository` 指回本仓库,因此官方市场会自动关联并显示下载量)。升级流程:改 `version` → `npm publish` → 用户 `dsh plugin update` 或市场一键更新。
|
|
88
96
|
|
|
89
97
|
## 字段口径
|
package/client/client.js
CHANGED
|
@@ -27,18 +27,48 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
27
27
|
var h = react.createElement;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* The
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* The DSH UI primitives: controls, icons and the floating layers every other
|
|
31
|
+
* plugin builds with, so this panel uses the shell's own components instead of
|
|
32
|
+
* re-inventing them. A packaged plugin resolves the module like any dependency
|
|
33
|
+
* (it is one of the shell's static modules); when a shell predates a component
|
|
34
|
+
* the small fallbacks below keep the panel usable rather than blank.
|
|
34
35
|
*/
|
|
35
|
-
var
|
|
36
|
+
var ui = {};
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
-
if (primitives !== null && typeof primitives === "object" && typeof primitives.Tooltip === "function") tooltip = primitives.Tooltip;
|
|
38
|
+
ui = require("@deepseek-ai/dsh-client-ui-primitives") || {};
|
|
39
39
|
} catch (error) {
|
|
40
|
-
|
|
40
|
+
ui = {};
|
|
41
41
|
}
|
|
42
|
+
var component = function (name, fallback) {
|
|
43
|
+
return typeof ui[name] === "function" ? ui[name] : fallback;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Tooltip is the right layer for the ⓘ: it positions in viewport coordinates,
|
|
47
|
+
* clamps to the window and flips sides, so an anchor at the panel's right edge
|
|
48
|
+
* still shows the whole note. (HoverCard always opens to the *right* of its
|
|
49
|
+
* anchor and never clamps, so there it gets cut off by the window.)
|
|
50
|
+
*/
|
|
51
|
+
var tooltip = component("Tooltip", null);
|
|
52
|
+
var stateDot = component("StateDot", null);
|
|
53
|
+
var menu = component("Menu", null);
|
|
54
|
+
var icon = function (name) {
|
|
55
|
+
return typeof ui[name] === "function" ? ui[name] : null;
|
|
56
|
+
};
|
|
57
|
+
var iconQuestion = icon("IconQuestionOutline14");
|
|
58
|
+
var iconChevron = icon("IconChevronDownOutline14");
|
|
59
|
+
var iconRefresh = icon("IconRefreshOutline14");
|
|
60
|
+
var iconGauge = icon("IconGaugeOutline16");
|
|
61
|
+
var iconTrend = icon("IconDataOutline16");
|
|
62
|
+
/** Chip and Button degrade into styled native controls on an older shell. */
|
|
63
|
+
var chip = component("Pill", function (props) {
|
|
64
|
+
return h("button", { type: "button", className: "dshtu_seg", "data-on": props.active === true, onClick: props.onClick, disabled: props.disabled === true }, props.children);
|
|
65
|
+
});
|
|
66
|
+
var button = component("Button", function (props) {
|
|
67
|
+
return h("button", { type: "button", className: "dshtu_btn", onClick: props.onClick, disabled: props.disabled === true }, props.icon, props.children);
|
|
68
|
+
});
|
|
69
|
+
var input = component("Input", function (props) {
|
|
70
|
+
return h("input", { type: props.type, value: props.value, onChange: props.onChange, className: "dshtu_input" });
|
|
71
|
+
});
|
|
42
72
|
|
|
43
73
|
var NS = "settings.tokenUsage";
|
|
44
74
|
var inject = ["slots", "locale"];
|
|
@@ -86,6 +116,8 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
86
116
|
auxiliaryNote: "另有 {count} 次辅助调用({detail})只计次数、不计金额:token 数在服务端,本地取不到。",
|
|
87
117
|
auxSearch: "联网搜索",
|
|
88
118
|
auxTitle: "会话标题",
|
|
119
|
+
copy: "复制说明",
|
|
120
|
+
copied: "已复制",
|
|
89
121
|
reasonExcluded: "非 DeepSeek 模型",
|
|
90
122
|
reasonUnmatched: "未匹配到官方定价",
|
|
91
123
|
unitOffPeak: "空闲",
|
|
@@ -142,6 +174,8 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
142
174
|
auxiliaryNote: "A further {count} auxiliary calls ({detail}) are counted but not priced: their tokens exist only server-side.",
|
|
143
175
|
auxSearch: "web search",
|
|
144
176
|
auxTitle: "session titles",
|
|
177
|
+
copy: "Copy note",
|
|
178
|
+
copied: "Copied",
|
|
145
179
|
reasonExcluded: "not a DeepSeek model",
|
|
146
180
|
reasonUnmatched: "no matching official price",
|
|
147
181
|
unitOffPeak: "off-peak",
|
|
@@ -156,7 +190,7 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
156
190
|
retry: "Retry"
|
|
157
191
|
};
|
|
158
192
|
|
|
159
|
-
var css = ".dshtu_wrap{max-width:860px;display:flex;flex-direction:column;gap:14px;padding:2px 2px 4px;color:var(--dsw-alias-label-primary);font-size:13px}.dshtu_card{background:var(--dsw-alias-bg-layer-3);border-radius:14px;box-shadow:var(--dsw-elevation-stroke);padding:14px 16px}.dshtu_bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dshtu_bar label{color:var(--dsw-alias-label-tertiary);font-size:12px}.dshtu_bar
|
|
193
|
+
var css = ".dshtu_wrap{max-width:860px;display:flex;flex-direction:column;gap:14px;padding:2px 2px 4px;color:var(--dsw-alias-label-primary);font-size:13px}.dshtu_card{background:var(--dsw-alias-bg-layer-3);border-radius:14px;box-shadow:var(--dsw-elevation-stroke);padding:14px 16px}.dshtu_bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dshtu_bar label{color:var(--dsw-alias-label-tertiary);font-size:12px}.dshtu_bar .dshtu_select,.dshtu_bar .dshtu_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:4px 8px;font-size:12.5px}.dshtu_bar .dshtu_select{width:168px;text-overflow:ellipsis;white-space:nowrap}.dshtu_chips{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}.dshtu_subBar{margin-top:12px}.dshtu_pick{display:inline-flex;align-items:center;gap:4px;max-width:230px}.dshtu_pickLabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dshtu_metaIcon{display:inline-flex;align-items:center;margin-right:5px;vertical-align:-2px;color:var(--dsw-alias-label-tertiary)}.dshtu_cardTitle{display:inline-flex;align-items:center}.dshtu_foot{display:flex;align-items:center;gap:6px}.dshtu_statLabel{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}.dshtu_field{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.dshtu_seg{display:inline-flex;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;overflow:hidden}.dshtu_seg button{border:0;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;padding:4px 10px;font-size:12.5px}.dshtu_seg button[data-on=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dshtu_totalsMeta{margin-top:12px}.dshtu_grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:8px}.dshtu_stat{background:var(--dsw-alias-bg-module-platform);border-radius:10px;padding:10px 12px}.dshtu_stat b{display:block;font-size:16px;line-height:24px;font-variant-numeric:tabular-nums}.dshtu_stat span{color:var(--dsw-alias-label-tertiary);font-size:12px}.dshtu_stat[data-main=true]{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,var(--dsw-alias-bg-module-platform))}.dshtu_stat[data-main=true] b{color:var(--dsw-alias-state-business-primary);font-size:20px}.dshtu_table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}.dshtu_table th,.dshtu_table td{text-align:right;padding:6px 8px;border-bottom:.5px solid var(--dsw-alias-border-l2);font-weight:400}.dshtu_table th:first-child,.dshtu_table td:first-child{text-align:left}.dshtu_table th{color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}.dshtu_table td:first-child{overflow-wrap:anywhere;max-width:300px;color:var(--dsw-alias-label-secondary)}.dshtu_meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.dshtu_err{color:var(--dsw-alias-state-error-primary);display:flex;align-items:center;gap:10px}.dshtu_chart{width:100%;height:260px}.dshtu_info{display:inline-flex;align-items:center;justify-content:center;margin-left:6px;color:var(--dsw-alias-label-tertiary);cursor:help}.dshtu_info:hover{color:var(--dsw-alias-state-business-primary)}.dshtu_pop{display:block;max-width:420px;font-size:12px;font-weight:400;line-height:18px;text-align:left;white-space:normal}.dshtu_pop .dshtu_sep{margin:0 5px;opacity:.55}.dshtu_nd{color:var(--dsw-alias-label-tertiary)}";
|
|
160
194
|
|
|
161
195
|
function trimNum(v, decimals) {
|
|
162
196
|
var f = v.toFixed(decimals);
|
|
@@ -212,6 +246,68 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
212
246
|
return idx >= 0 ? (text.slice(idx + 1) || text) : text;
|
|
213
247
|
}
|
|
214
248
|
|
|
249
|
+
/** A row of the shell's pills for one small enum (range, unit, chart window). */
|
|
250
|
+
function chips(options, active, onPick) {
|
|
251
|
+
return options.map(function (option) {
|
|
252
|
+
return h(chip, {
|
|
253
|
+
key: option.value,
|
|
254
|
+
active: option.value === active,
|
|
255
|
+
onClick: function () { onPick(option.value); }
|
|
256
|
+
}, option.label);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Model filter. The shell's Menu (the same popover the composer picks models
|
|
262
|
+
* with) anchored on a pill; a styled native select on a shell without it.
|
|
263
|
+
*/
|
|
264
|
+
function ModelPicker(props) {
|
|
265
|
+
var t = props.t;
|
|
266
|
+
var _a = useState(false), open = _a[0], setOpen = _a[1];
|
|
267
|
+
if (menu === null) {
|
|
268
|
+
return h("select", {
|
|
269
|
+
className: "dshtu_select",
|
|
270
|
+
value: props.model,
|
|
271
|
+
onChange: function (event) { props.onChange(event.target.value); }
|
|
272
|
+
}, [h("option", { key: "", value: "" }, t("modelAll"))].concat(props.models.map(function (name) {
|
|
273
|
+
return h("option", { key: name, value: name }, name);
|
|
274
|
+
})));
|
|
275
|
+
}
|
|
276
|
+
var items = [{ id: "", label: t("modelAll") }].concat(props.models.map(function (name) {
|
|
277
|
+
return { id: name, label: name };
|
|
278
|
+
}));
|
|
279
|
+
return h(menu, {
|
|
280
|
+
open: open,
|
|
281
|
+
align: "start",
|
|
282
|
+
side: "bottom",
|
|
283
|
+
portal: true,
|
|
284
|
+
compact: true,
|
|
285
|
+
items: items,
|
|
286
|
+
selectedId: props.model,
|
|
287
|
+
onSelect: function (id) { props.onChange(id); setOpen(false); },
|
|
288
|
+
onClose: function () { setOpen(false); },
|
|
289
|
+
anchor: h(chip, { active: props.model !== "", onClick: function () { setOpen(!open); } },
|
|
290
|
+
h("span", { className: "dshtu_pick" },
|
|
291
|
+
h("span", { className: "dshtu_pickLabel" }, props.model === "" ? t("modelAll") : props.model),
|
|
292
|
+
iconChevron === null ? null : h(iconChevron, {})))
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The money card's ⓘ: the shell's own icon inside the shell's Tooltip, with a
|
|
298
|
+
* plain title attribute as the only fallback on a shell without it.
|
|
299
|
+
*/
|
|
300
|
+
function noteTrigger(t, notes) {
|
|
301
|
+
var glyph = h("i", { className: "dshtu_info" }, iconQuestion === null ? "i" : h(iconQuestion, {}));
|
|
302
|
+
if (tooltip === null) return h("i", { className: "dshtu_info", title: notes.text }, "i");
|
|
303
|
+
return h(tooltip, {
|
|
304
|
+
label: h("div", { className: "dshtu_pop" }, notes.node),
|
|
305
|
+
side: "top",
|
|
306
|
+
delayMs: 120,
|
|
307
|
+
maxWidth: 420
|
|
308
|
+
}, glyph);
|
|
309
|
+
}
|
|
310
|
+
|
|
215
311
|
var BUCKET_KEYS = ["calls", "input", "output", "cacheRead", "cacheWrite", "reasoning", "total", "cost"];
|
|
216
312
|
|
|
217
313
|
/** Project tables read as directory names; same-named projects merge, full path rides the title. */
|
|
@@ -245,7 +341,9 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
245
341
|
h("thead", null,
|
|
246
342
|
h("tr", null,
|
|
247
343
|
h("th", null, keyLabel),
|
|
248
|
-
h("th",
|
|
344
|
+
h("th", null, tooltip === null
|
|
345
|
+
? h("span", { title: t("costTipLong") }, t("cost") + "*")
|
|
346
|
+
: h(tooltip, { label: t("costTipLong"), side: "top", delayMs: 250 }, h("span", null, t("cost") + "*"))),
|
|
249
347
|
h("th", null, t("total")),
|
|
250
348
|
h("th", null, t("input")),
|
|
251
349
|
h("th", null, t("output")),
|
|
@@ -256,9 +354,14 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
256
354
|
var row = rows[key];
|
|
257
355
|
var note = notes === undefined ? undefined : notes[key];
|
|
258
356
|
var blank = note !== undefined && note.blank === true;
|
|
357
|
+
var tip = note === undefined ? t("costTip") : note.tip;
|
|
358
|
+
var money = blank ? "—" : fmtMoney(row.cost);
|
|
259
359
|
return h("tr", { key: key },
|
|
260
360
|
h("td", { title: titles !== undefined && titles[key] !== undefined ? titles[key] : undefined }, key),
|
|
261
|
-
h("td", { className: blank ? "dshtu_nd" : undefined
|
|
361
|
+
h("td", { className: blank ? "dshtu_nd" : undefined },
|
|
362
|
+
tooltip === null
|
|
363
|
+
? h("span", { title: tip }, money)
|
|
364
|
+
: h(tooltip, { label: tip, side: "top", delayMs: 250 }, h("span", { className: "dshtu_moneyCell" }, money))),
|
|
262
365
|
h("td", null, fmtValue(row.total, unit)),
|
|
263
366
|
h("td", null, fmtValue(row.input, unit)),
|
|
264
367
|
h("td", null, fmtValue(row.output, unit)),
|
|
@@ -441,29 +544,27 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
441
544
|
return h("div", { className: "dshtu_card dshtu_meta" }, t("trendEmpty"));
|
|
442
545
|
}
|
|
443
546
|
var rangeButton = function (value) {
|
|
444
|
-
return h(
|
|
547
|
+
return h(chip, {
|
|
445
548
|
key: value,
|
|
446
|
-
|
|
549
|
+
active: range === value,
|
|
447
550
|
onClick: function () { if (props.onRange !== undefined) props.onRange(value); }
|
|
448
551
|
}, value + t("daysSuffix"));
|
|
449
552
|
};
|
|
450
553
|
return h("div", { className: "dshtu_card" },
|
|
451
554
|
h("div", { className: "dshtu_bar" },
|
|
452
|
-
h("span", { className: "dshtu_meta" },
|
|
555
|
+
h("span", { className: "dshtu_meta dshtu_cardTitle" },
|
|
556
|
+
iconTrend === null ? null : h("span", { className: "dshtu_metaIcon" }, h(iconTrend, {})),
|
|
557
|
+
t("trend") + " · " + shown.from + " ~ " + shown.to + " · " + t("cost") + " " + fmtMoney(cost)),
|
|
453
558
|
h("span", { style: { flex: "1" } }),
|
|
454
|
-
h("span", { className: "
|
|
559
|
+
h("span", { className: "dshtu_chips" }, rangeButton(7), rangeButton(30), rangeButton(90))),
|
|
455
560
|
h("div", { className: "dshtu_chart", ref: holder }));
|
|
456
561
|
}
|
|
457
562
|
|
|
458
|
-
/** Money stat card: the headline amount plus the
|
|
563
|
+
/** Money stat card: the headline amount plus the ⓘ that reveals the notes. */
|
|
459
564
|
function moneyBox(t, label, value, notes) {
|
|
460
|
-
var icon = h("i", { className: "dshtu_info", "aria-label": t("costTip") }, "i");
|
|
461
|
-
var trigger = tooltip === null
|
|
462
|
-
? h("i", { className: "dshtu_info", title: t("costTipLong") }, "i")
|
|
463
|
-
: h(tooltip, { label: h("div", { className: "dshtu_pop" }, notes), side: "top", delayMs: 120, maxWidth: 420 }, icon);
|
|
464
565
|
return h("div", { className: "dshtu_stat", "data-main": true },
|
|
465
566
|
h("b", null, fmtMoney(value)),
|
|
466
|
-
h("span",
|
|
567
|
+
h("span", { className: "dshtu_statLabel" }, label, noteTrigger(t, notes)));
|
|
467
568
|
}
|
|
468
569
|
|
|
469
570
|
/** Rate line behind one model's amount: cache-hit / cache-miss / output. */
|
|
@@ -488,45 +589,68 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
488
589
|
return notes;
|
|
489
590
|
}
|
|
490
591
|
|
|
491
|
-
/**
|
|
592
|
+
/**
|
|
593
|
+
* Where the amounts come from, and what they leave out. Returns both the
|
|
594
|
+
* rendered lines (for the hover layer) and the same text flattened, which is
|
|
595
|
+
* what the layer's copy button puts on the clipboard.
|
|
596
|
+
*/
|
|
492
597
|
function pricingNotes(t, data) {
|
|
493
598
|
var pricing = data.pricing || {};
|
|
494
599
|
var notes = data.modelPricing || {};
|
|
600
|
+
var nodes = [];
|
|
601
|
+
var texts = [];
|
|
602
|
+
var push = function (key, node, text, spaced) {
|
|
603
|
+
nodes.push(h("div", { key: key, style: spaced === true ? { marginTop: "5px" } : undefined }, node));
|
|
604
|
+
texts.push(text);
|
|
605
|
+
};
|
|
495
606
|
var bits = [];
|
|
496
607
|
if (pricing.builtIn === true) {
|
|
497
|
-
|
|
608
|
+
var offline = pricing.error ? t("priceBuiltIn").replace("{error}", pricing.error) : t("priceOffline");
|
|
609
|
+
bits.push({ node: h("span", { key: "src" }, offline), text: offline });
|
|
498
610
|
} else if (pricing.source) {
|
|
499
|
-
|
|
611
|
+
var shown = String(pricing.source).replace(/^https?:\/\//, "");
|
|
612
|
+
bits.push({ node: h("span", { key: "src" }, t("priceSource") + " " + shown), text: t("priceSource") + " " + pricing.source });
|
|
613
|
+
}
|
|
614
|
+
if (pricing.fetchedAt > 0) {
|
|
615
|
+
var fetched = t("priceFetched").replace("{time}", fmtTime(pricing.fetchedAt));
|
|
616
|
+
bits.push({ node: h("span", { key: "at" }, fetched), text: fetched });
|
|
617
|
+
}
|
|
618
|
+
if (pricing.nextRefreshAt > 0) {
|
|
619
|
+
var next = t("priceNext").replace("{time}", fmtTime(pricing.nextRefreshAt));
|
|
620
|
+
bits.push({ node: h("span", { key: "next" }, next), text: next });
|
|
500
621
|
}
|
|
501
|
-
if (pricing.fetchedAt > 0) bits.push(h("span", { key: "at" }, t("priceFetched").replace("{time}", fmtTime(pricing.fetchedAt))));
|
|
502
|
-
if (pricing.nextRefreshAt > 0) bits.push(h("span", { key: "next" }, t("priceNext").replace("{time}", fmtTime(pricing.nextRefreshAt))));
|
|
503
622
|
if (pricing.peak) {
|
|
504
|
-
|
|
623
|
+
var peak = t("pricePeak").replace("{ranges}", pricing.peak.hoursUtc.map(function (range) { return range[0] + "-" + range[1]; }).join(" / "));
|
|
624
|
+
bits.push({ node: h("span", { key: "peak" }, peak), text: peak });
|
|
625
|
+
}
|
|
626
|
+
if (pricing.peakParsed === false) {
|
|
627
|
+
bits.push({ node: h("span", { key: "peakAssumed" }, t("pricePeakAssumed")), text: t("pricePeakAssumed") });
|
|
505
628
|
}
|
|
506
|
-
if (pricing.peakParsed === false) bits.push(h("span", { key: "peakAssumed" }, t("pricePeakAssumed")));
|
|
507
629
|
var meta = [];
|
|
508
630
|
bits.forEach(function (bit, index) {
|
|
509
631
|
if (index > 0) meta.push(h("span", { className: "dshtu_sep", key: "sep" + index }, "·"));
|
|
510
|
-
meta.push(bit);
|
|
632
|
+
meta.push(bit.node);
|
|
511
633
|
});
|
|
634
|
+
push("text", t("costTipLong"), t("costTipLong"));
|
|
635
|
+
push("meta", meta, bits.map(function (bit) { return bit.text; }).join(" · "), true);
|
|
512
636
|
var skipped = Object.keys(notes).filter(function (name) { return notes[name].status !== "priced"; });
|
|
513
|
-
var lines = [
|
|
514
|
-
h("div", { key: "text" }, t("costTipLong")),
|
|
515
|
-
h("div", { key: "meta", style: { marginTop: "5px" } }, meta),
|
|
516
|
-
];
|
|
517
637
|
if (skipped.length > 0) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
638
|
+
var reason = function (name) { return notes[name].status === "excluded" ? t("reasonExcluded") : t("reasonUnmatched"); };
|
|
639
|
+
push("skipped", t("notPriced") + ":" + skipped.map(function (name) {
|
|
640
|
+
return name + "(" + reason(name) + ")";
|
|
641
|
+
}).join("、"), t("notPriced") + ": " + skipped.map(function (name) {
|
|
642
|
+
return name + " (" + reason(name) + ")";
|
|
643
|
+
}).join(", "), true);
|
|
521
644
|
}
|
|
522
645
|
var auxiliary = data.auxiliary;
|
|
523
646
|
if (auxiliary && auxiliary.total > 0) {
|
|
524
647
|
var detail = [];
|
|
525
648
|
if (auxiliary.search > 0) detail.push(t("auxSearch") + " " + auxiliary.search);
|
|
526
649
|
if (auxiliary.title > 0) detail.push(t("auxTitle") + " " + auxiliary.title);
|
|
527
|
-
|
|
650
|
+
var auxiliaryText = t("auxiliaryNote").replace("{count}", auxiliary.total).replace("{detail}", detail.join(" / "));
|
|
651
|
+
push("auxiliary", auxiliaryText, auxiliaryText, true);
|
|
528
652
|
}
|
|
529
|
-
return
|
|
653
|
+
return { node: nodes, text: texts.join("\n") };
|
|
530
654
|
}
|
|
531
655
|
|
|
532
656
|
function Tab(props) {
|
|
@@ -608,19 +732,16 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
608
732
|
try { localStorage.setItem("dshtu.unit", next); } catch (err) {}
|
|
609
733
|
};
|
|
610
734
|
|
|
611
|
-
var seg = function (value, label) {
|
|
612
|
-
return h("button", { key: value, "data-on": unit === value, onClick: function () { switchUnit(value); } }, label);
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
var modeTab = function (value, label) {
|
|
616
|
-
return h("button", { key: value, "data-on": mode === value, onClick: function () { setMode(value); setTick(tick + 1); } }, label);
|
|
617
|
-
};
|
|
618
|
-
|
|
619
735
|
if (error) {
|
|
620
736
|
return h("div", { className: "dshtu_wrap" },
|
|
621
737
|
h("div", { className: "dshtu_card dshtu_err" },
|
|
622
738
|
h("span", null, t("error") + ": " + error),
|
|
623
|
-
h(
|
|
739
|
+
h(button, {
|
|
740
|
+
variant: "outline",
|
|
741
|
+
size: "sm",
|
|
742
|
+
icon: iconRefresh === null ? null : h(iconRefresh, {}),
|
|
743
|
+
onClick: function () { setError(null); setTick(tick + 1); }
|
|
744
|
+
}, t("retry"))));
|
|
624
745
|
}
|
|
625
746
|
if (!data) {
|
|
626
747
|
return h("div", { className: "dshtu_wrap" },
|
|
@@ -639,25 +760,36 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
639
760
|
metaParts.push(t("scanPending"));
|
|
640
761
|
}
|
|
641
762
|
metaParts.push(t("updated").replace("{time}", fmtTime(data.updatedAt)));
|
|
763
|
+
var scanState = data.scan && data.scan.error ? "error" : (data.scan && data.scan.done ? "done" : "ongoing");
|
|
642
764
|
return h("div", { className: "dshtu_wrap" },
|
|
643
765
|
h("div", { className: "dshtu_card" },
|
|
644
766
|
h("div", { className: "dshtu_bar" },
|
|
645
767
|
h("label", null, t("filterLabel")),
|
|
646
|
-
h("span", { className: "
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
mode === "
|
|
768
|
+
h("span", { className: "dshtu_chips" }, chips([
|
|
769
|
+
{ value: "day", label: t("filterDay") },
|
|
770
|
+
{ value: "month", label: t("filterMonth") },
|
|
771
|
+
{ value: "all", label: t("filterAll") }
|
|
772
|
+
], mode, function (value) { setMode(value); setTick(tick + 1); })),
|
|
773
|
+
mode === "day" ? h(input, { type: "date", value: day, onChange: function (event) { setDay(event.target.value); setTick(tick + 1); } }) : null,
|
|
774
|
+
mode === "month" ? h(input, { type: "month", value: month, onChange: function (event) { setMonth(event.target.value); setTick(tick + 1); } }) : null,
|
|
652
775
|
h("span", { className: "dshtu_field" },
|
|
653
776
|
h("label", null, t("modelLabel")),
|
|
654
|
-
h(
|
|
655
|
-
|
|
656
|
-
|
|
777
|
+
h(ModelPicker, {
|
|
778
|
+
t: t,
|
|
779
|
+
model: model,
|
|
780
|
+
models: models,
|
|
781
|
+
onChange: function (value) { setModel(value); setTick(tick + 1); }
|
|
782
|
+
}))),
|
|
783
|
+
h("div", { className: "dshtu_bar dshtu_subBar" },
|
|
784
|
+
h("span", { className: "dshtu_meta" },
|
|
785
|
+
iconGauge === null ? null : h("span", { className: "dshtu_metaIcon" }, h(iconGauge, {})),
|
|
786
|
+
t("totals")),
|
|
657
787
|
h("span", { style: { flex: "1" } }),
|
|
658
788
|
h("label", null, t("unitLabel")),
|
|
659
|
-
h("span", { className: "
|
|
660
|
-
|
|
789
|
+
h("span", { className: "dshtu_chips" }, chips([
|
|
790
|
+
{ value: "zh", label: t("unitZh") },
|
|
791
|
+
{ value: "en", label: t("unitEn") }
|
|
792
|
+
], unit, switchUnit))),
|
|
661
793
|
h("div", { className: "dshtu_grid" },
|
|
662
794
|
statBox(t, t("total"), totals.total, unit, true),
|
|
663
795
|
statBox(t, t("input"), totals.input, unit),
|
|
@@ -674,7 +806,9 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
674
806
|
var projects = shortenProjects(data.byProject);
|
|
675
807
|
return table(t, t("byProject"), projects.rows, unit, projects.titles);
|
|
676
808
|
})(),
|
|
677
|
-
h("div", { className: "
|
|
809
|
+
h("div", { className: "dshtu_foot" },
|
|
810
|
+
stateDot === null ? null : h(stateDot, { state: scanState, size: 8 }),
|
|
811
|
+
h("span", { className: "dshtu_meta" }, metaParts.join(" · "))));
|
|
678
812
|
}
|
|
679
813
|
|
|
680
814
|
/**
|
|
@@ -694,35 +828,83 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
|
|
|
694
828
|
+ ";mask:url(\"" + NAV_GLYPH + "\") center/16px 16px no-repeat;"
|
|
695
829
|
+ "-webkit-mask:url(\"" + NAV_GLYPH + "\") center/16px 16px no-repeat}";
|
|
696
830
|
|
|
831
|
+
/**
|
|
832
|
+
* Handles the newest fiber owns. A client-plugin hot reload re-applies this
|
|
833
|
+
* module, and the replacement fiber must take the rail style and the settings
|
|
834
|
+
* slot over from the fiber it replaces — otherwise the section would vanish
|
|
835
|
+
* after every rebuild until the page is reloaded.
|
|
836
|
+
*/
|
|
837
|
+
var railStyle = null;
|
|
838
|
+
var sectionOwner = null;
|
|
839
|
+
var localeOwner = null;
|
|
840
|
+
|
|
697
841
|
function apply(ctx) {
|
|
698
842
|
ctx.effect(function () {
|
|
699
|
-
|
|
843
|
+
// A reloaded fiber replaces the dictionary it finds: re-registering the
|
|
844
|
+
// same namespace on top of a still-registered one is what makes a hot
|
|
845
|
+
// reload fail, and a failure here would take the whole panel down with it.
|
|
846
|
+
if (localeOwner !== null) {
|
|
847
|
+
try { localeOwner(); } catch (ignored) {}
|
|
848
|
+
localeOwner = null;
|
|
849
|
+
}
|
|
850
|
+
var dispose = null;
|
|
851
|
+
try {
|
|
852
|
+
dispose = ctx.locale.register(NS, { zh: zh, en: en });
|
|
853
|
+
} catch (error) {
|
|
854
|
+
console.error("[dsh-token-use] dictionary registration failed", error);
|
|
855
|
+
}
|
|
856
|
+
localeOwner = typeof dispose === "function" ? dispose : null;
|
|
857
|
+
return function () {
|
|
858
|
+
if (localeOwner === dispose) localeOwner = null;
|
|
859
|
+
if (typeof dispose === "function") dispose();
|
|
860
|
+
};
|
|
700
861
|
}, "dsh-token-use: dictionaries");
|
|
701
862
|
ctx.effect(function () {
|
|
702
|
-
var existing = document.getElementById("dshtu-nav-style");
|
|
703
|
-
if (existing !== null) return function () {};
|
|
704
863
|
var style = document.createElement("style");
|
|
705
864
|
style.id = "dshtu-nav-style";
|
|
706
865
|
style.textContent = navGlyphCss;
|
|
707
866
|
document.head.appendChild(style);
|
|
708
|
-
|
|
867
|
+
railStyle = style;
|
|
868
|
+
return function () {
|
|
869
|
+
style.remove();
|
|
870
|
+
if (railStyle === style) railStyle = null;
|
|
871
|
+
};
|
|
709
872
|
}, "dsh-token-use: rail glyph");
|
|
710
873
|
var t = ctx.locale.bind(NS);
|
|
711
874
|
ctx.slots.inject("settings.section", function () {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
875
|
+
var register = function () {
|
|
876
|
+
return ctx.slots.register({
|
|
877
|
+
name: "settings.section",
|
|
878
|
+
id: "token-usage",
|
|
879
|
+
order: 90,
|
|
880
|
+
// The settings rail picks its glyph by section id (`navIcon(id)`) and
|
|
881
|
+
// falls back to the settings gear for every id it does not know, so
|
|
882
|
+
// this label carries a marker the stylesheet hangs our own chart glyph
|
|
883
|
+
// on: `navGlyphCss` hides the gear and masks a bar chart into its place.
|
|
884
|
+
// If a future shell changes that markup the selector stops matching and
|
|
885
|
+
// the gear simply stays — never two glyphs at once.
|
|
886
|
+
label: function () { return h("span", { className: "dshtu_navlabel" }, t("tab")); },
|
|
887
|
+
locale: NS,
|
|
888
|
+
inject: function () { return { t: t }; }
|
|
889
|
+
}, function (props) { return h(Tab, props); });
|
|
890
|
+
};
|
|
891
|
+
var dispose;
|
|
892
|
+
try {
|
|
893
|
+
dispose = register();
|
|
894
|
+
} catch (error) {
|
|
895
|
+
// The reloaded fiber can land while the previous registration is still
|
|
896
|
+
// on the ledger: retire it and claim the slot for this fiber.
|
|
897
|
+
if (sectionOwner !== null) {
|
|
898
|
+
try { sectionOwner(); } catch (ignored) {}
|
|
899
|
+
sectionOwner = null;
|
|
900
|
+
}
|
|
901
|
+
dispose = register();
|
|
902
|
+
}
|
|
903
|
+
sectionOwner = dispose;
|
|
904
|
+
return function () {
|
|
905
|
+
if (sectionOwner === dispose) sectionOwner = null;
|
|
906
|
+
dispose();
|
|
907
|
+
};
|
|
726
908
|
});
|
|
727
909
|
}
|
|
728
910
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-token-use",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Real-time token usage and cost dashboard for DeepSeek Harness — live totals (input/output/cache/reasoning), estimated spend per model/day/project from DeepSeek's official prices (refreshed daily, peak/off-peak aware), model/day/month/project filters and a smooth trend chart. · 实时 Token 用量与消费金额仪表盘:按模型/按天/按月/按项目查看用量与估算金额(取 DeepSeek 官网定价,每日自动更新,区分高峰/空闲),附趋势曲线。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|