@feiyang666/dsh-usage-plugin 1.11.1 → 1.12.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/CHANGELOG.md +24 -0
- package/lib/client.js +19 -10
- package/lib/index.js +33 -1
- package/package.json +1 -1
- package/scripts/release.mjs +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,30 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## v1.12.2 (2026-08-23)
|
|
12
|
+
|
|
13
|
+
### 修复 / Fixed
|
|
14
|
+
|
|
15
|
+
- **移动端宽表仍会逐字竖排(兜底加固)**(`lib/client.js`):v1.12.1 的修复依赖运行时向 `document.head` **注入全局样式表**(`.dsh-usage-table{min-width:720px}` + `@media (max-width:540px)` 折叠中间列)。在部分移动端运行环境(如经 mobile-remote 进出的手机页面)下该样式表因加载时序 / CSP 未生效,导致 9–11 列宽表每列被压到约 1 字符宽、标题与单元格逐字竖排、数据无法阅读。本版把 `min-width:720px` **直接写进表格的内联样式**(内联样式必然生效),窄屏时表格保持 720px、外层 `overflow-x` 容器横向滑动,彻底不再依赖样式表注入;原有注入样式表与 ≤540px 折叠逻辑保留,作为进一步优化的增强项。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## v1.12.1 (2026-08-23)
|
|
20
|
+
|
|
21
|
+
### 修复 / Fixed
|
|
22
|
+
|
|
23
|
+
- **修复移动端「消耗表」「消耗明细」等宽表列标题逐字竖排的问题**(`lib/client.js`):窄屏下 9–11 列宽表每列被压到极限宽度,`word-break` 把「调用」「API 服务商」「输入·未命中」等表头拆成单字竖排。现为所有用量表格注入响应式 CSS:表格强制 `min-width: 720px`,外层 `overflow-x` 容器正常横向滚动、不再逐字换行;极窄屏(≤ 540px)下对宽表自动隐藏第 4 列至倒数第 2 列,只保留「模型 / 服务商 / 调用 / 总消耗」等主标识列与合计列,价格表等窄表不折叠、保留横向滚动。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## v1.12.0 (2026-08-23)
|
|
28
|
+
|
|
29
|
+
### 新增 / New
|
|
30
|
+
|
|
31
|
+
- **SSE 实时推送端点**(`lib/index.js`):新增 `GET /usage/api/events` 事件流端点,数据变化(新调用记录写入 / 导入 / 清除等,即 `persistNow` 落盘成功)时向订阅者推送 `data: {"type":"changed","at":<时间戳>}`。桌面端数据中心等客户端订阅该流即可**即时**感知用量更新(毫秒级),不再依赖轮询。无订阅者时零开销,插件独立使用完全不受影响。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
11
35
|
## 开源贡献基础:合并 PR #6(@Martin-soaring-dev,2026-08-20)
|
|
12
36
|
|
|
13
37
|
> 本期(以及后续 v1.9.2 / v1.9.3)建立在社区贡献分支 **`Martin-soaring-dev/prepare_for_contribution`** 之上。该分支由用户上传、经你合并(GitHub Pull Request #6,合并 commit `12a52be`),是插件转向对外开源贡献的形态基础。
|
package/lib/client.js
CHANGED
|
@@ -40,7 +40,9 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
scroll: { overflowX: "auto", overflowY: "hidden", width: "100%", maxWidth: "100%", minWidth: 0 },
|
|
41
41
|
// 表格宽度自适应容器(width:100% + 列内容允许换行),不再用 max-content 撑出横向滚动条;
|
|
42
42
|
// 极窄窗口下容器仍保留 overflow-x 兜底。
|
|
43
|
-
|
|
43
|
+
// minWidth:720 直接写在内联样式里(内联样式必然生效),不依赖下方注入的全局样式表:
|
|
44
|
+
// 窄屏时表格保持 720px、外层 st.scroll 容器横向滚动,避免 9–11 列被压到逐字竖排。
|
|
45
|
+
tbl: { width: "100%", minWidth: 720, borderCollapse: "collapse", fontSize: fs(12) },
|
|
44
46
|
th: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
45
47
|
thFirst: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
46
48
|
td: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
@@ -92,7 +94,14 @@ window.__ModuleLoader__.load({
|
|
|
92
94
|
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
|
|
93
95
|
var styleSheetEl = document.createElement("style");
|
|
94
96
|
styleSheetEl.id = "dsh-usage-plugin-style";
|
|
95
|
-
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}"
|
|
97
|
+
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}" +
|
|
98
|
+
// 所有用量表格:保证最小宽度,避免在窄屏被压缩导致标题/单元格逐字竖排;
|
|
99
|
+
// 外层 st.scroll 容器已设 overflow-x:auto,无法完整展示时可横向滚动。
|
|
100
|
+
".dsh-usage-table{min-width:720px}" +
|
|
101
|
+
// 极窄屏 (≤ 540px):对带 collapse-mobile 的宽表隐藏第 4 列至倒数第 2 列,
|
|
102
|
+
// 只保留前 3 个主标识列 + 最后合计列(如 消耗表/明细表 保留 模型/服务商/调用/总消耗),
|
|
103
|
+
// 避免每列被压到极限宽度而逐字竖排。价格表等窄表不折叠,保留横向滚动。
|
|
104
|
+
"@media (max-width:540px){.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none}.dsh-usage-table.collapse-mobile{min-width:320px}}";
|
|
96
105
|
document.head.appendChild(styleSheetEl);
|
|
97
106
|
}
|
|
98
107
|
|
|
@@ -635,7 +644,7 @@ window.__ModuleLoader__.load({
|
|
|
635
644
|
),
|
|
636
645
|
el("div", { style: st.sec }, "消耗表(按模型)"),
|
|
637
646
|
el("div", { style: st.scroll },
|
|
638
|
-
el("table", { style: st.tbl },
|
|
647
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
639
648
|
el("thead", null, el("tr", null,
|
|
640
649
|
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
641
650
|
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
@@ -675,7 +684,7 @@ window.__ModuleLoader__.load({
|
|
|
675
684
|
),
|
|
676
685
|
el("div", { style: st.sec, marginTop: 14 }, "消耗明细(按 API 服务商 × 模型)"),
|
|
677
686
|
el("div", { style: st.scroll },
|
|
678
|
-
el("table", { style: st.tbl },
|
|
687
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
679
688
|
el("thead", null, el("tr", null,
|
|
680
689
|
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
681
690
|
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
@@ -825,7 +834,7 @@ window.__ModuleLoader__.load({
|
|
|
825
834
|
monthRows.length === 0
|
|
826
835
|
? el("div", { style: st.empty }, "本月暂无记录。")
|
|
827
836
|
: el("div", { style: st.scroll },
|
|
828
|
-
el("table", { style: st.tbl },
|
|
837
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
829
838
|
el("thead", null, el("tr", null,
|
|
830
839
|
el("th", { style: st.thFirst }, "日期"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "高峰消耗"), el("th", { style: st.th }, "空闲消耗"), el("th", { style: st.th }, "总消耗(自动)")
|
|
831
840
|
)),
|
|
@@ -856,7 +865,7 @@ window.__ModuleLoader__.load({
|
|
|
856
865
|
selRecords.length === 0
|
|
857
866
|
? el("div", { style: st.empty }, "该日无记录。")
|
|
858
867
|
: el("div", { style: st.scroll },
|
|
859
|
-
el("table", { style: st.tbl },
|
|
868
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
860
869
|
el("thead", null, el("tr", null,
|
|
861
870
|
el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
|
|
862
871
|
)),
|
|
@@ -964,7 +973,7 @@ window.__ModuleLoader__.load({
|
|
|
964
973
|
pageBtn("next", "下一页 ›", curPage >= totalPages, function () { setPage(curPage + 1); })
|
|
965
974
|
),
|
|
966
975
|
el("div", { style: st.scroll },
|
|
967
|
-
el("table", { style: st.tbl },
|
|
976
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
968
977
|
el("thead", null, el("tr", null,
|
|
969
978
|
el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "缓存写入"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "命中率"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
|
|
970
979
|
)),
|
|
@@ -1091,7 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1091
1100
|
: null,
|
|
1092
1101
|
showRegime(regime) === "base"
|
|
1093
1102
|
? el("div", { style: st.scroll },
|
|
1094
|
-
el("table", { style: st.tbl },
|
|
1103
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1095
1104
|
el("thead", null, el("tr", null,
|
|
1096
1105
|
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "缓存命中(输入)"), el("th", { style: st.th }, "输入 · 未命中"), el("th", { style: st.th }, "输出")
|
|
1097
1106
|
)),
|
|
@@ -1099,7 +1108,7 @@ window.__ModuleLoader__.load({
|
|
|
1099
1108
|
)
|
|
1100
1109
|
)
|
|
1101
1110
|
: el("div", { style: st.scroll },
|
|
1102
|
-
el("table", { style: st.tbl },
|
|
1111
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1103
1112
|
el("thead", null, el("tr", null,
|
|
1104
1113
|
el("th", { style: st.thFirst }, "模型"),
|
|
1105
1114
|
el("th", { style: st.th }, "空闲 · 缓存命中"), el("th", { style: st.th }, "空闲 · 输入"), el("th", { style: st.th }, "空闲 · 输出"),
|
|
@@ -1114,7 +1123,7 @@ window.__ModuleLoader__.load({
|
|
|
1114
1123
|
el("div", { style: st.sec, marginTop: 18 }, "第三方平台价格与覆盖状态(2026-08-19 核验)"),
|
|
1115
1124
|
el("div", { style: st.note, marginTop: 6 }, "只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。"),
|
|
1116
1125
|
el("div", { style: st.scroll, marginTop: 8 },
|
|
1117
|
-
el("table", { style: st.tbl },
|
|
1126
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1118
1127
|
el("thead", null, el("tr", null,
|
|
1119
1128
|
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型 / 状态"),
|
|
1120
1129
|
el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输入"), el("th", { style: st.th }, "输出"), el("th", { style: st.thFirst }, "说明")
|
package/lib/index.js
CHANGED
|
@@ -286,10 +286,21 @@ export default {
|
|
|
286
286
|
return undefined
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
// SSE 实时推送:数据变化(新记录写入 / 导入 / 清除等)时通知订阅者。
|
|
290
|
+
// 订阅端点 /usage/api/events。无订阅者时是 no-op,插件独立使用不受影响。
|
|
291
|
+
const sseClients = new Set()
|
|
292
|
+
function notifyChanged() {
|
|
293
|
+
if (sseClients.size === 0) return
|
|
294
|
+
const payload = 'data: {"type":"changed","at":' + Date.now() + '}\n\n'
|
|
295
|
+
for (const res of sseClients) {
|
|
296
|
+
try { res.write(payload) } catch (e) { sseClients.delete(res) }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
289
300
|
function persistNow() {
|
|
290
301
|
if (!dataPath || !persistOk) return Promise.resolve()
|
|
291
302
|
const text = JSON.stringify(records)
|
|
292
|
-
writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).catch(() => {})
|
|
303
|
+
writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).then(() => { notifyChanged() }).catch(() => {})
|
|
293
304
|
return writeChain
|
|
294
305
|
}
|
|
295
306
|
|
|
@@ -1182,6 +1193,27 @@ export default {
|
|
|
1182
1193
|
} catch (e) {
|
|
1183
1194
|
push('route-register-threw: ' + (e && e.stack ? e.stack : msg(e)))
|
|
1184
1195
|
}
|
|
1196
|
+
try {
|
|
1197
|
+
// SSE 实时推送端点:数据变化时向订阅者推送 {"type":"changed"}。
|
|
1198
|
+
// 桌面端数据中心订阅该流即可即时感知用量数据更新。
|
|
1199
|
+
webServer.register({
|
|
1200
|
+
kind: 'exact',
|
|
1201
|
+
path: '/usage/api/events',
|
|
1202
|
+
handler: (req, res) => {
|
|
1203
|
+
res.writeHead(200, {
|
|
1204
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
1205
|
+
'Cache-Control': 'no-cache',
|
|
1206
|
+
Connection: 'keep-alive',
|
|
1207
|
+
'X-Accel-Buffering': 'no',
|
|
1208
|
+
})
|
|
1209
|
+
res.write(':ok\n\n')
|
|
1210
|
+
sseClients.add(res)
|
|
1211
|
+
req.on('close', () => { sseClients.delete(res) })
|
|
1212
|
+
}
|
|
1213
|
+
})
|
|
1214
|
+
} catch (e) {
|
|
1215
|
+
push('events-route-register-threw: ' + msg(e))
|
|
1216
|
+
}
|
|
1185
1217
|
} else {
|
|
1186
1218
|
push('route-not-registered (no webServer)')
|
|
1187
1219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.2",
|
|
4
4
|
"description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
package/scripts/release.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
6
6
|
const REPO = 'feiyang-dev/dsh-usage-plugin'
|
|
7
|
-
const
|
|
7
|
+
const VERSION = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')).version
|
|
8
|
+
const TAG = `v${VERSION}`
|
|
8
9
|
|
|
9
10
|
function getCredential() {
|
|
10
11
|
const input = 'protocol=https\nhost=github.com\n\n'
|
|
@@ -76,9 +77,10 @@ if (created.status !== 201) {
|
|
|
76
77
|
const release = JSON.parse(created.body)
|
|
77
78
|
console.log('release id:', release.id, 'url:', release.html_url)
|
|
78
79
|
|
|
79
|
-
// 打包并上传 tarball 作为附件(Windows 下 npm 为 npm.cmd
|
|
80
|
-
const
|
|
81
|
-
|
|
80
|
+
// 打包并上传 tarball 作为附件(Windows 下 npm 为 npm.cmd 批处理脚本,经 cmd /c 执行)
|
|
81
|
+
const pack = process.platform === 'win32'
|
|
82
|
+
? spawnSync('cmd', ['/c', 'npm', 'pack'], { encoding: 'utf8' })
|
|
83
|
+
: spawnSync('npm', ['pack'], { encoding: 'utf8' })
|
|
82
84
|
const tgzName = (pack.stdout || '').trim().split('\n').pop()
|
|
83
85
|
if (tgzName && fs.existsSync(tgzName)) {
|
|
84
86
|
const buf = fs.readFileSync(tgzName)
|