@feiyang666/dsh-usage-plugin 1.10.0 → 1.11.0
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 +8 -0
- package/lib/client.js +65 -20
- 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,14 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
+
## v1.11.0 (2026-08-23)
|
|
27
|
+
|
|
28
|
+
- **高峰 / 空闲时段说明**:概览、用量日历、缓存命中列表、价格表四个页面顶部统一新增「高峰 / 空闲时段说明」,清楚区分**工作日高峰**(周一至周五 9:00–12:00、14:00–18:00)、**工作日空闲**(其余时间)与**周末全天空闲**(自 2026-08-23 起按空闲价,此前仍按原规则)。
|
|
29
|
+
- **加载动画**:用量面板首次进入、数据仍在加载时显示转圈加载动画与提示文字,避免白屏无反馈;余额查询加载中同步使用加载动画。
|
|
30
|
+
- **表格适配**:所有表格改为宽度自适应容器(`width:100%`),单元格内容按需换行,不再因内容超宽出现横向滚动条;极窄窗口下容器保留横向滚动兜底。窗口缩放、不同分辨率下查看更舒适。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
26
34
|
## v1.10.0 (2026-08-23)
|
|
27
35
|
|
|
28
36
|
- **新增 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,22 @@ 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
|
+
loading: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, padding: "48px 0", color: "inherit" },
|
|
86
|
+
spinner: { width: 28, height: 28, borderRadius: "50%", border: "3px solid rgba(90,140,255,.22)", borderTopColor: "#5a8cff", animation: "dshUsageSpin .8s linear infinite", flexShrink: 0 },
|
|
87
|
+
loadText: { fontSize: fs(12), opacity: 0.65 }
|
|
82
88
|
};
|
|
83
89
|
|
|
90
|
+
// 注入全局样式(幂等,仅一次):加载动画关键帧等
|
|
91
|
+
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
|
|
92
|
+
var styleSheetEl = document.createElement("style");
|
|
93
|
+
styleSheetEl.id = "dsh-usage-plugin-style";
|
|
94
|
+
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}";
|
|
95
|
+
document.head.appendChild(styleSheetEl);
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
// ── helpers ──
|
|
85
99
|
var pad2 = function (n) { return (n < 10 ? "0" : "") + n; };
|
|
86
100
|
var fmtInt = function (n) { return String(Math.round(n || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); };
|
|
@@ -194,6 +208,27 @@ window.__ModuleLoader__.load({
|
|
|
194
208
|
);
|
|
195
209
|
}
|
|
196
210
|
|
|
211
|
+
// ── 计费时段说明(区分工作日高峰 / 工作日空闲 / 周末全天空闲)──
|
|
212
|
+
function BillingHint(props) {
|
|
213
|
+
return el("div", { style: st.billHint },
|
|
214
|
+
el("div", { style: st.billHintTitle }, "高峰 / 空闲时段说明"),
|
|
215
|
+
el("div", null,
|
|
216
|
+
el("span", { style: st.badgePeak }, "工作日高峰"), " 周一至周五 9:00–12:00、14:00–18:00"
|
|
217
|
+
),
|
|
218
|
+
el("div", null,
|
|
219
|
+
el("span", { style: st.badgeValley }, "空闲时段"), " 工作日其余时间,以及周末(周六、周日)全天(自 2026-08-23 起;此前仍按原规则分峰谷)"
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── 数据加载动画 ──
|
|
225
|
+
function LoadingView(props) {
|
|
226
|
+
return el("div", { style: st.loading },
|
|
227
|
+
el("div", { style: st.spinner }),
|
|
228
|
+
el("div", { style: st.loadText }, props.text || "正在加载用量数据…")
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
197
232
|
// ── long-image report ──
|
|
198
233
|
function drawReport(canvas, records) {
|
|
199
234
|
records = records.slice();
|
|
@@ -557,6 +592,7 @@ window.__ModuleLoader__.load({
|
|
|
557
592
|
function setCustomTo(v) { setToDate(v); setPreset("custom"); }
|
|
558
593
|
|
|
559
594
|
return el("div", null,
|
|
595
|
+
el(BillingHint, null),
|
|
560
596
|
el("div", { style: st.calBar },
|
|
561
597
|
el("div", { style: st.seg },
|
|
562
598
|
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
@@ -738,6 +774,7 @@ window.__ModuleLoader__.load({
|
|
|
738
774
|
function goToday() { setYm({ y: nowY, m: nowM }); setSelectedDay(bjKey(Date.now())); }
|
|
739
775
|
|
|
740
776
|
return el("div", null,
|
|
777
|
+
el(BillingHint, null),
|
|
741
778
|
el("div", { style: st.calBar },
|
|
742
779
|
el("button", { style: st.btn, onClick: prevMonth }, "‹ 上月"),
|
|
743
780
|
el("span", { style: { fontSize: fs(14), fontWeight: 600, minWidth: 120, textAlign: "center" } }, y + "年" + (m + 1) + "月"),
|
|
@@ -887,6 +924,7 @@ window.__ModuleLoader__.load({
|
|
|
887
924
|
var sums = splitTotals(filtered, regime);
|
|
888
925
|
|
|
889
926
|
return el("div", null,
|
|
927
|
+
el(BillingHint, null),
|
|
890
928
|
el("div", { style: st.calBar },
|
|
891
929
|
el("div", { style: st.seg },
|
|
892
930
|
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
@@ -1003,6 +1041,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1041
|
});
|
|
1004
1042
|
|
|
1005
1043
|
return el("div", null,
|
|
1044
|
+
el(BillingHint, null),
|
|
1006
1045
|
el("div", { style: st.sec }, "今日 $ / ¥ 汇率"),
|
|
1007
1046
|
el("div", { style: st.cards, marginTop: 8 },
|
|
1008
1047
|
el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : "暂不可用", hint: fx.date ? ("汇率日期 " + fx.date) : "等待获取" }),
|
|
@@ -1105,12 +1144,16 @@ window.__ModuleLoader__.load({
|
|
|
1105
1144
|
var destDir = destState[0], setDestDir = destState[1];
|
|
1106
1145
|
var canvasNode = null, fileInputNode = null;
|
|
1107
1146
|
var timer = props.timer;
|
|
1147
|
+
// 初始加载中标记:首次请求完成/失败后置 false(后续定时刷新静默进行,避免闪烁)
|
|
1148
|
+
var loadingState = React.useState(true);
|
|
1149
|
+
var loading = loadingState[0], setLoading = loadingState[1];
|
|
1108
1150
|
|
|
1109
1151
|
function refresh() {
|
|
1110
1152
|
api({ action: "list" }).then(function (res) {
|
|
1111
1153
|
setData(res || { records: [], count: 0, dataPath: "", persistOk: false, persistError: "", pricing: null, days: [] });
|
|
1112
1154
|
setError("");
|
|
1113
|
-
|
|
1155
|
+
setLoading(false);
|
|
1156
|
+
}).catch(function (e) { setError(String((e && e.message) || e)); setLoading(false); });
|
|
1114
1157
|
}
|
|
1115
1158
|
|
|
1116
1159
|
React.useEffect(function () {
|
|
@@ -1220,10 +1263,12 @@ window.__ModuleLoader__.load({
|
|
|
1220
1263
|
el("div", { style: st.note, marginTop: 6 },
|
|
1221
1264
|
"计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。"
|
|
1222
1265
|
),
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1266
|
+
loading
|
|
1267
|
+
? el(LoadingView, null)
|
|
1268
|
+
: (tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
|
|
1269
|
+
tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
|
|
1270
|
+
tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
|
|
1271
|
+
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),
|
|
1227
1272
|
el("div", { style: st.actions, marginTop: 4 },
|
|
1228
1273
|
el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, "导出 CSV"),
|
|
1229
1274
|
el("button", { style: st.btn, onClick: function () { doExport("json"); } }, "导出 JSON"),
|
|
@@ -1372,7 +1417,7 @@ window.__ModuleLoader__.load({
|
|
|
1372
1417
|
actionUrl ? el("a", { style: st.actionLink, href: actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel || "打开服务商凭据页面") : null
|
|
1373
1418
|
)
|
|
1374
1419
|
: null,
|
|
1375
|
-
s.status === "loading" && !d ? el(
|
|
1420
|
+
s.status === "loading" && !d ? el(LoadingView, { text: "正在查询余额…" }) : null,
|
|
1376
1421
|
d && d.ok
|
|
1377
1422
|
? el("div", { style: st.hero },
|
|
1378
1423
|
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.0",
|
|
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.0'
|
|
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,spawnSync 需要完整命令名)
|
|
80
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
81
|
+
const pack = spawnSync(npmCmd, ['pack'], { encoding: 'utf8' })
|
|
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