@feiyang666/dsh-usage-plugin 1.10.0 → 1.11.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/CHANGELOG.md +16 -0
- package/lib/client.js +89 -22
- package/package.json +1 -1
- package/scripts/release.mjs +4 -3
- package/scripts/upload-asset.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,22 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
+
## v1.11.1 (2026-08-23)
|
|
27
|
+
|
|
28
|
+
- **修复:数据加载后页面空白**:加载动画三目分支误用逗号表达式,只返回最后一个表达式值(`null`),导致加载完成后各子视图全部不渲染;改为将四个子视图作为容器 `div` 的并列子元素,加载完成后正常显示。
|
|
29
|
+
- **计费说明改为面板级固定显示**:「高峰 / 空闲时段说明」固定在用量面板子页签下方,概览、用量日历、缓存命中列表、价格表四个页面切换时始终可见(含数据加载期间)。
|
|
30
|
+
- **当前时段精确显示**:用量面板顶部徽标与价格表页顶部新增「当前 · 工作日高峰时段 / 工作日空闲时段 / 周末空闲时段」动态状态并附说明文字;周末(自 2026-08-23 起)全天按空闲价计费,规则生效前仍按原规则区分。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## v1.11.0 (2026-08-23)
|
|
35
|
+
|
|
36
|
+
- **高峰 / 空闲时段说明**:概览、用量日历、缓存命中列表、价格表四个页面顶部统一新增「高峰 / 空闲时段说明」,清楚区分**工作日高峰**(周一至周五 9:00–12:00、14:00–18:00)、**工作日空闲**(其余时间)与**周末全天空闲**(自 2026-08-23 起按空闲价,此前仍按原规则)。
|
|
37
|
+
- **加载动画**:用量面板首次进入、数据仍在加载时显示转圈加载动画与提示文字,避免白屏无反馈;余额查询加载中同步使用加载动画。
|
|
38
|
+
- **表格适配**:所有表格改为宽度自适应容器(`width:100%`),单元格内容按需换行,不再因内容超宽出现横向滚动条;极窄窗口下容器保留横向滚动兜底。窗口缩放、不同分辨率下查看更舒适。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
26
42
|
## v1.10.0 (2026-08-23)
|
|
27
43
|
|
|
28
44
|
- **新增 DeepSeek 官方模型 `deepseek-v4-flash-vision-exp`(DeepSeek-V4-Flash-Vision-Exp)**:官方价格表、概览「按模型」表、按 API 服务商 × 模型明细、用量日历、缓存命中列表与 PNG 报告均自动按该模型计价;「价格表」页新增该模型一行,`modelKey` 识别优先匹配 `vision`(避免被误归入 `deepseek-v4-flash`)。
|
package/lib/client.js
CHANGED
|
@@ -38,19 +38,20 @@ window.__ModuleLoader__.load({
|
|
|
38
38
|
subtab: { border: "1px solid rgba(128,128,128,.35)", background: "transparent", borderRadius: 6, padding: "5px 14px", fontSize: fs(12), cursor: "pointer", color: "inherit" },
|
|
39
39
|
subtabOn: { border: "1px solid rgba(90,140,255,.5)", background: "rgba(90,140,255,.22)", borderRadius: 6, padding: "5px 14px", fontSize: fs(12), cursor: "pointer", color: "inherit", fontWeight: 600 },
|
|
40
40
|
scroll: { overflowX: "auto", overflowY: "hidden", width: "100%", maxWidth: "100%", minWidth: 0 },
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
// 表格宽度自适应容器(width:100% + 列内容允许换行),不再用 max-content 撑出横向滚动条;
|
|
42
|
+
// 极窄窗口下容器仍保留 overflow-x 兜底。
|
|
43
|
+
tbl: { width: "100%", borderCollapse: "collapse", fontSize: fs(12) },
|
|
44
|
+
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
|
+
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
|
+
td: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
47
|
+
tdFirst: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
48
|
+
tdWrap: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
49
|
+
tdTotal: { textAlign: "right", padding: "7px 8px", fontWeight: 600, borderTop: "1px solid rgba(128,128,128,.35)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
50
|
+
tdTotalFirst: { textAlign: "left", padding: "7px 8px", fontWeight: 600, borderTop: "1px solid rgba(128,128,128,.35)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
51
|
+
tdClick: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", cursor: "pointer" },
|
|
52
|
+
tdClickSel: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", cursor: "pointer", color: "#5a8cff", fontWeight: 600 },
|
|
53
|
+
tdGroup: { textAlign: "left", padding: "7px 8px", fontWeight: 600, background: "rgba(128,128,128,.09)", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
54
|
+
tdGroupR: { textAlign: "right", padding: "7px 8px", fontWeight: 600, background: "rgba(128,128,128,.09)", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
54
55
|
badgeHit: { display: "inline-block", padding: "2px 8px", borderRadius: 999, fontSize: fs(11), fontWeight: 500, background: "rgba(46,204,113,.18)", color: "#2ecc71", whiteSpace: "nowrap" },
|
|
55
56
|
badgePeak: { display: "inline-block", padding: "2px 8px", borderRadius: 999, fontSize: fs(11), fontWeight: 500, background: "rgba(255,152,0,.18)", color: "#ff9800", whiteSpace: "nowrap" },
|
|
56
57
|
badgeValley: { display: "inline-block", padding: "2px 8px", borderRadius: 999, fontSize: fs(11), fontWeight: 500, background: "rgba(90,140,255,.18)", color: "#5a8cff", whiteSpace: "nowrap" },
|
|
@@ -78,9 +79,23 @@ window.__ModuleLoader__.load({
|
|
|
78
79
|
legend: { display: "flex", alignItems: "center", gap: 6, fontSize: fs(11), opacity: 0.65, marginTop: 8, flexWrap: "wrap" },
|
|
79
80
|
legendBar: { display: "inline-block", width: 100, height: 8, borderRadius: 4, background: "linear-gradient(90deg,rgba(128,128,128,.12),rgba(46,134,222,.3),rgba(46,134,222,.85))" },
|
|
80
81
|
numInput: { border: "1px solid rgba(128,128,128,.35)", background: "transparent", borderRadius: 4, padding: "3px 6px", fontSize: fs(12), color: "inherit", width: 78, textAlign: "right" },
|
|
81
|
-
selDayTitle: { fontSize: fs(13), fontWeight: 600, marginTop: 14 }
|
|
82
|
+
selDayTitle: { fontSize: fs(13), fontWeight: 600, marginTop: 14 },
|
|
83
|
+
billHint: { border: "1px solid rgba(90,140,255,.35)", background: "rgba(90,140,255,.08)", borderRadius: 8, padding: "10px 14px", marginTop: 10, fontSize: fs(12), lineHeight: 1.9 },
|
|
84
|
+
billHintTitle: { fontWeight: 600, marginBottom: 4, color: "#5a8cff" },
|
|
85
|
+
nowPeriod: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10, fontSize: fs(12) },
|
|
86
|
+
loading: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, padding: "48px 0", color: "inherit" },
|
|
87
|
+
spinner: { width: 28, height: 28, borderRadius: "50%", border: "3px solid rgba(90,140,255,.22)", borderTopColor: "#5a8cff", animation: "dshUsageSpin .8s linear infinite", flexShrink: 0 },
|
|
88
|
+
loadText: { fontSize: fs(12), opacity: 0.65 }
|
|
82
89
|
};
|
|
83
90
|
|
|
91
|
+
// 注入全局样式(幂等,仅一次):加载动画关键帧等
|
|
92
|
+
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
|
|
93
|
+
var styleSheetEl = document.createElement("style");
|
|
94
|
+
styleSheetEl.id = "dsh-usage-plugin-style";
|
|
95
|
+
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}";
|
|
96
|
+
document.head.appendChild(styleSheetEl);
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
// ── helpers ──
|
|
85
100
|
var pad2 = function (n) { return (n < 10 ? "0" : "") + n; };
|
|
86
101
|
var fmtInt = function (n) { return String(Math.round(n || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); };
|
|
@@ -127,6 +142,24 @@ window.__ModuleLoader__.load({
|
|
|
127
142
|
var t = d.getUTCHours() * 60 + d.getUTCMinutes();
|
|
128
143
|
return (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
|
|
129
144
|
};
|
|
145
|
+
// 返回当前计费时段 { peak, label, desc },区分:工作日高峰 / 工作日空闲 / 周末空闲(周末自 2026-08-23 起全天按空闲价)
|
|
146
|
+
var periodNow = function (ts) {
|
|
147
|
+
ts = ts || Date.now();
|
|
148
|
+
var d = new Date(ts + 8 * 3600 * 1000);
|
|
149
|
+
var wd = d.getUTCDay(); // 0=周日 1=周一 … 6=周六
|
|
150
|
+
var isWeekend = wd === 0 || wd === 6;
|
|
151
|
+
var t = d.getUTCHours() * 60 + d.getUTCMinutes();
|
|
152
|
+
var inPeakHours = (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
|
|
153
|
+
if (isWeekend) {
|
|
154
|
+
if (ts >= WEEKEND_FLAT_AT) return { peak: false, label: "周末空闲时段", desc: "周末(周六、周日)全天按空闲价计费" };
|
|
155
|
+
return inPeakHours
|
|
156
|
+
? { peak: true, label: "周末高峰时段", desc: "2026-08-23 前仍按原规则分峰谷" }
|
|
157
|
+
: { peak: false, label: "周末空闲时段", desc: "2026-08-23 前仍按原规则分峰谷" };
|
|
158
|
+
}
|
|
159
|
+
return inPeakHours
|
|
160
|
+
? { peak: true, label: "工作日高峰时段", desc: "周一至周五 9:00–12:00、14:00–18:00" }
|
|
161
|
+
: { peak: false, label: "工作日空闲时段", desc: "工作日其余时间" };
|
|
162
|
+
};
|
|
130
163
|
// 计费档位选择:auto 用 autoCost(按生效日期自动切换),base 用 baseCost,其余用 peakValleyCost
|
|
131
164
|
// 计费档位显示文案
|
|
132
165
|
var regimeLabel = function (regime) {
|
|
@@ -194,6 +227,27 @@ window.__ModuleLoader__.load({
|
|
|
194
227
|
);
|
|
195
228
|
}
|
|
196
229
|
|
|
230
|
+
// ── 计费时段说明(区分工作日高峰 / 工作日空闲 / 周末全天空闲)──
|
|
231
|
+
function BillingHint(props) {
|
|
232
|
+
return el("div", { style: st.billHint },
|
|
233
|
+
el("div", { style: st.billHintTitle }, "高峰 / 空闲时段说明"),
|
|
234
|
+
el("div", null,
|
|
235
|
+
el("span", { style: st.badgePeak }, "工作日高峰"), " 周一至周五 9:00–12:00、14:00–18:00"
|
|
236
|
+
),
|
|
237
|
+
el("div", null,
|
|
238
|
+
el("span", { style: st.badgeValley }, "空闲时段"), " 工作日其余时间,以及周末(周六、周日)全天(自 2026-08-23 起;此前仍按原规则分峰谷)"
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── 数据加载动画 ──
|
|
244
|
+
function LoadingView(props) {
|
|
245
|
+
return el("div", { style: st.loading },
|
|
246
|
+
el("div", { style: st.spinner }),
|
|
247
|
+
el("div", { style: st.loadText }, props.text || "正在加载用量数据…")
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
197
251
|
// ── long-image report ──
|
|
198
252
|
function drawReport(canvas, records) {
|
|
199
253
|
records = records.slice();
|
|
@@ -1003,6 +1057,10 @@ window.__ModuleLoader__.load({
|
|
|
1003
1057
|
});
|
|
1004
1058
|
|
|
1005
1059
|
return el("div", null,
|
|
1060
|
+
el("div", { style: st.nowPeriod },
|
|
1061
|
+
el("span", { style: periodNow().peak ? st.badgePeak : st.badgeValley }, "当前 · " + periodNow().label),
|
|
1062
|
+
el("span", { style: st.sub }, periodNow().desc)
|
|
1063
|
+
),
|
|
1006
1064
|
el("div", { style: st.sec }, "今日 $ / ¥ 汇率"),
|
|
1007
1065
|
el("div", { style: st.cards, marginTop: 8 },
|
|
1008
1066
|
el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : "暂不可用", hint: fx.date ? ("汇率日期 " + fx.date) : "等待获取" }),
|
|
@@ -1105,12 +1163,16 @@ window.__ModuleLoader__.load({
|
|
|
1105
1163
|
var destDir = destState[0], setDestDir = destState[1];
|
|
1106
1164
|
var canvasNode = null, fileInputNode = null;
|
|
1107
1165
|
var timer = props.timer;
|
|
1166
|
+
// 初始加载中标记:首次请求完成/失败后置 false(后续定时刷新静默进行,避免闪烁)
|
|
1167
|
+
var loadingState = React.useState(true);
|
|
1168
|
+
var loading = loadingState[0], setLoading = loadingState[1];
|
|
1108
1169
|
|
|
1109
1170
|
function refresh() {
|
|
1110
1171
|
api({ action: "list" }).then(function (res) {
|
|
1111
1172
|
setData(res || { records: [], count: 0, dataPath: "", persistOk: false, persistError: "", pricing: null, days: [] });
|
|
1112
1173
|
setError("");
|
|
1113
|
-
|
|
1174
|
+
setLoading(false);
|
|
1175
|
+
}).catch(function (e) { setError(String((e && e.message) || e)); setLoading(false); });
|
|
1114
1176
|
}
|
|
1115
1177
|
|
|
1116
1178
|
React.useEffect(function () {
|
|
@@ -1189,7 +1251,7 @@ window.__ModuleLoader__.load({
|
|
|
1189
1251
|
|
|
1190
1252
|
var records = data.records || [];
|
|
1191
1253
|
var dataPath = data.dataPath || "";
|
|
1192
|
-
var
|
|
1254
|
+
var nowPeriod = periodNow(Date.now());
|
|
1193
1255
|
var effectiveAt = data.effectiveAt || 0;
|
|
1194
1256
|
var nowEffective = effectiveAt ? (Date.now() >= effectiveAt) : true;
|
|
1195
1257
|
return el("div", { style: st.root },
|
|
@@ -1200,7 +1262,7 @@ window.__ModuleLoader__.load({
|
|
|
1200
1262
|
el("div", { style: st.title }, "用量与消耗"),
|
|
1201
1263
|
el("div", { style: st.sub }, "记录插件激活后的每一次模型调用")
|
|
1202
1264
|
),
|
|
1203
|
-
el("span", { style:
|
|
1265
|
+
el("span", { style: nowPeriod.peak ? st.badgePeak : st.badgeValley }, "当前 · " + nowPeriod.label),
|
|
1204
1266
|
el("span", { style: nowEffective ? st.badgeHit : st.badgeValley }, nowEffective ? "新价格已生效" : "新价格未生效")
|
|
1205
1267
|
),
|
|
1206
1268
|
el("div", { style: st.actions },
|
|
@@ -1217,13 +1279,18 @@ window.__ModuleLoader__.load({
|
|
|
1217
1279
|
}, t.t);
|
|
1218
1280
|
})
|
|
1219
1281
|
),
|
|
1282
|
+
el(BillingHint, null),
|
|
1220
1283
|
el("div", { style: st.note, marginTop: 6 },
|
|
1221
1284
|
"计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。"
|
|
1222
1285
|
),
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1286
|
+
loading
|
|
1287
|
+
? el(LoadingView, null)
|
|
1288
|
+
: el("div", null,
|
|
1289
|
+
tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
|
|
1290
|
+
tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
|
|
1291
|
+
tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
|
|
1292
|
+
tab === "prices" ? el(PriceView, { pricing: data.pricing, effectiveAt: data.effectiveAt, fx: data.fx || {}, onFxRefresh: function () { api({ action: "fxRefresh" }).then(refresh).catch(function () {}); }, onChanged: refresh }) : null
|
|
1293
|
+
),
|
|
1227
1294
|
el("div", { style: st.actions, marginTop: 4 },
|
|
1228
1295
|
el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, "导出 CSV"),
|
|
1229
1296
|
el("button", { style: st.btn, onClick: function () { doExport("json"); } }, "导出 JSON"),
|
|
@@ -1372,7 +1439,7 @@ window.__ModuleLoader__.load({
|
|
|
1372
1439
|
actionUrl ? el("a", { style: st.actionLink, href: actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel || "打开服务商凭据页面") : null
|
|
1373
1440
|
)
|
|
1374
1441
|
: null,
|
|
1375
|
-
s.status === "loading" && !d ? el(
|
|
1442
|
+
s.status === "loading" && !d ? el(LoadingView, { text: "正在查询余额…" }) : null,
|
|
1376
1443
|
d && d.ok
|
|
1377
1444
|
? el("div", { style: st.hero },
|
|
1378
1445
|
el("div", { style: st.heroLabel }, heroLabel),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.1",
|
|
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,7 @@ 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 TAG = 'v1.
|
|
7
|
+
const TAG = 'v1.11.1'
|
|
8
8
|
|
|
9
9
|
function getCredential() {
|
|
10
10
|
const input = 'protocol=https\nhost=github.com\n\n'
|
|
@@ -76,8 +76,9 @@ if (created.status !== 201) {
|
|
|
76
76
|
const release = JSON.parse(created.body)
|
|
77
77
|
console.log('release id:', release.id, 'url:', release.html_url)
|
|
78
78
|
|
|
79
|
-
// 打包并上传 tarball
|
|
80
|
-
const
|
|
79
|
+
// 打包并上传 tarball 作为附件(Windows 下 npm 为 npm.cmd 批处理脚本,需经 shell 执行)
|
|
80
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
81
|
+
const pack = spawnSync(npmCmd, ['pack'], { encoding: 'utf8', shell: process.platform === 'win32' })
|
|
81
82
|
const tgzName = (pack.stdout || '').trim().split('\n').pop()
|
|
82
83
|
if (tgzName && fs.existsSync(tgzName)) {
|
|
83
84
|
const buf = fs.readFileSync(tgzName)
|
package/scripts/upload-asset.mjs
CHANGED