@feiyang666/dsh-usage-plugin 1.11.0 → 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 +32 -0
- package/lib/client.js +51 -20
- package/lib/index.js +33 -1
- package/package.json +1 -1
- package/scripts/release.mjs +6 -4
- package/scripts/upload-asset.mjs +1 -1
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`),是插件转向对外开源贡献的形态基础。
|
|
@@ -23,6 +47,14 @@
|
|
|
23
47
|
|
|
24
48
|
---
|
|
25
49
|
|
|
50
|
+
## v1.11.1 (2026-08-23)
|
|
51
|
+
|
|
52
|
+
- **修复:数据加载后页面空白**:加载动画三目分支误用逗号表达式,只返回最后一个表达式值(`null`),导致加载完成后各子视图全部不渲染;改为将四个子视图作为容器 `div` 的并列子元素,加载完成后正常显示。
|
|
53
|
+
- **计费说明改为面板级固定显示**:「高峰 / 空闲时段说明」固定在用量面板子页签下方,概览、用量日历、缓存命中列表、价格表四个页面切换时始终可见(含数据加载期间)。
|
|
54
|
+
- **当前时段精确显示**:用量面板顶部徽标与价格表页顶部新增「当前 · 工作日高峰时段 / 工作日空闲时段 / 周末空闲时段」动态状态并附说明文字;周末(自 2026-08-23 起)全天按空闲价计费,规则生效前仍按原规则区分。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
26
58
|
## v1.11.0 (2026-08-23)
|
|
27
59
|
|
|
28
60
|
- **高峰 / 空闲时段说明**:概览、用量日历、缓存命中列表、价格表四个页面顶部统一新增「高峰 / 空闲时段说明」,清楚区分**工作日高峰**(周一至周五 9:00–12:00、14:00–18:00)、**工作日空闲**(其余时间)与**周末全天空闲**(自 2026-08-23 起按空闲价,此前仍按原规则)。
|
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" },
|
|
@@ -82,6 +84,7 @@ window.__ModuleLoader__.load({
|
|
|
82
84
|
selDayTitle: { fontSize: fs(13), fontWeight: 600, marginTop: 14 },
|
|
83
85
|
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
86
|
billHintTitle: { fontWeight: 600, marginBottom: 4, color: "#5a8cff" },
|
|
87
|
+
nowPeriod: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10, fontSize: fs(12) },
|
|
85
88
|
loading: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, padding: "48px 0", color: "inherit" },
|
|
86
89
|
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
90
|
loadText: { fontSize: fs(12), opacity: 0.65 }
|
|
@@ -91,7 +94,14 @@ window.__ModuleLoader__.load({
|
|
|
91
94
|
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
|
|
92
95
|
var styleSheetEl = document.createElement("style");
|
|
93
96
|
styleSheetEl.id = "dsh-usage-plugin-style";
|
|
94
|
-
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}}";
|
|
95
105
|
document.head.appendChild(styleSheetEl);
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -141,6 +151,24 @@ window.__ModuleLoader__.load({
|
|
|
141
151
|
var t = d.getUTCHours() * 60 + d.getUTCMinutes();
|
|
142
152
|
return (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
|
|
143
153
|
};
|
|
154
|
+
// 返回当前计费时段 { peak, label, desc },区分:工作日高峰 / 工作日空闲 / 周末空闲(周末自 2026-08-23 起全天按空闲价)
|
|
155
|
+
var periodNow = function (ts) {
|
|
156
|
+
ts = ts || Date.now();
|
|
157
|
+
var d = new Date(ts + 8 * 3600 * 1000);
|
|
158
|
+
var wd = d.getUTCDay(); // 0=周日 1=周一 … 6=周六
|
|
159
|
+
var isWeekend = wd === 0 || wd === 6;
|
|
160
|
+
var t = d.getUTCHours() * 60 + d.getUTCMinutes();
|
|
161
|
+
var inPeakHours = (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
|
|
162
|
+
if (isWeekend) {
|
|
163
|
+
if (ts >= WEEKEND_FLAT_AT) return { peak: false, label: "周末空闲时段", desc: "周末(周六、周日)全天按空闲价计费" };
|
|
164
|
+
return inPeakHours
|
|
165
|
+
? { peak: true, label: "周末高峰时段", desc: "2026-08-23 前仍按原规则分峰谷" }
|
|
166
|
+
: { peak: false, label: "周末空闲时段", desc: "2026-08-23 前仍按原规则分峰谷" };
|
|
167
|
+
}
|
|
168
|
+
return inPeakHours
|
|
169
|
+
? { peak: true, label: "工作日高峰时段", desc: "周一至周五 9:00–12:00、14:00–18:00" }
|
|
170
|
+
: { peak: false, label: "工作日空闲时段", desc: "工作日其余时间" };
|
|
171
|
+
};
|
|
144
172
|
// 计费档位选择:auto 用 autoCost(按生效日期自动切换),base 用 baseCost,其余用 peakValleyCost
|
|
145
173
|
// 计费档位显示文案
|
|
146
174
|
var regimeLabel = function (regime) {
|
|
@@ -592,7 +620,6 @@ window.__ModuleLoader__.load({
|
|
|
592
620
|
function setCustomTo(v) { setToDate(v); setPreset("custom"); }
|
|
593
621
|
|
|
594
622
|
return el("div", null,
|
|
595
|
-
el(BillingHint, null),
|
|
596
623
|
el("div", { style: st.calBar },
|
|
597
624
|
el("div", { style: st.seg },
|
|
598
625
|
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
@@ -617,7 +644,7 @@ window.__ModuleLoader__.load({
|
|
|
617
644
|
),
|
|
618
645
|
el("div", { style: st.sec }, "消耗表(按模型)"),
|
|
619
646
|
el("div", { style: st.scroll },
|
|
620
|
-
el("table", { style: st.tbl },
|
|
647
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
621
648
|
el("thead", null, el("tr", null,
|
|
622
649
|
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
623
650
|
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
@@ -657,7 +684,7 @@ window.__ModuleLoader__.load({
|
|
|
657
684
|
),
|
|
658
685
|
el("div", { style: st.sec, marginTop: 14 }, "消耗明细(按 API 服务商 × 模型)"),
|
|
659
686
|
el("div", { style: st.scroll },
|
|
660
|
-
el("table", { style: st.tbl },
|
|
687
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
661
688
|
el("thead", null, el("tr", null,
|
|
662
689
|
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
663
690
|
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
@@ -774,7 +801,6 @@ window.__ModuleLoader__.load({
|
|
|
774
801
|
function goToday() { setYm({ y: nowY, m: nowM }); setSelectedDay(bjKey(Date.now())); }
|
|
775
802
|
|
|
776
803
|
return el("div", null,
|
|
777
|
-
el(BillingHint, null),
|
|
778
804
|
el("div", { style: st.calBar },
|
|
779
805
|
el("button", { style: st.btn, onClick: prevMonth }, "‹ 上月"),
|
|
780
806
|
el("span", { style: { fontSize: fs(14), fontWeight: 600, minWidth: 120, textAlign: "center" } }, y + "年" + (m + 1) + "月"),
|
|
@@ -808,7 +834,7 @@ window.__ModuleLoader__.load({
|
|
|
808
834
|
monthRows.length === 0
|
|
809
835
|
? el("div", { style: st.empty }, "本月暂无记录。")
|
|
810
836
|
: el("div", { style: st.scroll },
|
|
811
|
-
el("table", { style: st.tbl },
|
|
837
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
812
838
|
el("thead", null, el("tr", null,
|
|
813
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 }, "总消耗(自动)")
|
|
814
840
|
)),
|
|
@@ -839,7 +865,7 @@ window.__ModuleLoader__.load({
|
|
|
839
865
|
selRecords.length === 0
|
|
840
866
|
? el("div", { style: st.empty }, "该日无记录。")
|
|
841
867
|
: el("div", { style: st.scroll },
|
|
842
|
-
el("table", { style: st.tbl },
|
|
868
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
843
869
|
el("thead", null, el("tr", null,
|
|
844
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 }, "消耗")
|
|
845
871
|
)),
|
|
@@ -924,7 +950,6 @@ window.__ModuleLoader__.load({
|
|
|
924
950
|
var sums = splitTotals(filtered, regime);
|
|
925
951
|
|
|
926
952
|
return el("div", null,
|
|
927
|
-
el(BillingHint, null),
|
|
928
953
|
el("div", { style: st.calBar },
|
|
929
954
|
el("div", { style: st.seg },
|
|
930
955
|
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
@@ -948,7 +973,7 @@ window.__ModuleLoader__.load({
|
|
|
948
973
|
pageBtn("next", "下一页 ›", curPage >= totalPages, function () { setPage(curPage + 1); })
|
|
949
974
|
),
|
|
950
975
|
el("div", { style: st.scroll },
|
|
951
|
-
el("table", { style: st.tbl },
|
|
976
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
952
977
|
el("thead", null, el("tr", null,
|
|
953
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 }, "消耗")
|
|
954
979
|
)),
|
|
@@ -1041,7 +1066,10 @@ window.__ModuleLoader__.load({
|
|
|
1041
1066
|
});
|
|
1042
1067
|
|
|
1043
1068
|
return el("div", null,
|
|
1044
|
-
el(
|
|
1069
|
+
el("div", { style: st.nowPeriod },
|
|
1070
|
+
el("span", { style: periodNow().peak ? st.badgePeak : st.badgeValley }, "当前 · " + periodNow().label),
|
|
1071
|
+
el("span", { style: st.sub }, periodNow().desc)
|
|
1072
|
+
),
|
|
1045
1073
|
el("div", { style: st.sec }, "今日 $ / ¥ 汇率"),
|
|
1046
1074
|
el("div", { style: st.cards, marginTop: 8 },
|
|
1047
1075
|
el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : "暂不可用", hint: fx.date ? ("汇率日期 " + fx.date) : "等待获取" }),
|
|
@@ -1072,7 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1072
1100
|
: null,
|
|
1073
1101
|
showRegime(regime) === "base"
|
|
1074
1102
|
? el("div", { style: st.scroll },
|
|
1075
|
-
el("table", { style: st.tbl },
|
|
1103
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1076
1104
|
el("thead", null, el("tr", null,
|
|
1077
1105
|
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "缓存命中(输入)"), el("th", { style: st.th }, "输入 · 未命中"), el("th", { style: st.th }, "输出")
|
|
1078
1106
|
)),
|
|
@@ -1080,7 +1108,7 @@ window.__ModuleLoader__.load({
|
|
|
1080
1108
|
)
|
|
1081
1109
|
)
|
|
1082
1110
|
: el("div", { style: st.scroll },
|
|
1083
|
-
el("table", { style: st.tbl },
|
|
1111
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1084
1112
|
el("thead", null, el("tr", null,
|
|
1085
1113
|
el("th", { style: st.thFirst }, "模型"),
|
|
1086
1114
|
el("th", { style: st.th }, "空闲 · 缓存命中"), el("th", { style: st.th }, "空闲 · 输入"), el("th", { style: st.th }, "空闲 · 输出"),
|
|
@@ -1095,7 +1123,7 @@ window.__ModuleLoader__.load({
|
|
|
1095
1123
|
el("div", { style: st.sec, marginTop: 18 }, "第三方平台价格与覆盖状态(2026-08-19 核验)"),
|
|
1096
1124
|
el("div", { style: st.note, marginTop: 6 }, "只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。"),
|
|
1097
1125
|
el("div", { style: st.scroll, marginTop: 8 },
|
|
1098
|
-
el("table", { style: st.tbl },
|
|
1126
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1099
1127
|
el("thead", null, el("tr", null,
|
|
1100
1128
|
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型 / 状态"),
|
|
1101
1129
|
el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输入"), el("th", { style: st.th }, "输出"), el("th", { style: st.thFirst }, "说明")
|
|
@@ -1232,7 +1260,7 @@ window.__ModuleLoader__.load({
|
|
|
1232
1260
|
|
|
1233
1261
|
var records = data.records || [];
|
|
1234
1262
|
var dataPath = data.dataPath || "";
|
|
1235
|
-
var
|
|
1263
|
+
var nowPeriod = periodNow(Date.now());
|
|
1236
1264
|
var effectiveAt = data.effectiveAt || 0;
|
|
1237
1265
|
var nowEffective = effectiveAt ? (Date.now() >= effectiveAt) : true;
|
|
1238
1266
|
return el("div", { style: st.root },
|
|
@@ -1243,7 +1271,7 @@ window.__ModuleLoader__.load({
|
|
|
1243
1271
|
el("div", { style: st.title }, "用量与消耗"),
|
|
1244
1272
|
el("div", { style: st.sub }, "记录插件激活后的每一次模型调用")
|
|
1245
1273
|
),
|
|
1246
|
-
el("span", { style:
|
|
1274
|
+
el("span", { style: nowPeriod.peak ? st.badgePeak : st.badgeValley }, "当前 · " + nowPeriod.label),
|
|
1247
1275
|
el("span", { style: nowEffective ? st.badgeHit : st.badgeValley }, nowEffective ? "新价格已生效" : "新价格未生效")
|
|
1248
1276
|
),
|
|
1249
1277
|
el("div", { style: st.actions },
|
|
@@ -1260,15 +1288,18 @@ window.__ModuleLoader__.load({
|
|
|
1260
1288
|
}, t.t);
|
|
1261
1289
|
})
|
|
1262
1290
|
),
|
|
1291
|
+
el(BillingHint, null),
|
|
1263
1292
|
el("div", { style: st.note, marginTop: 6 },
|
|
1264
1293
|
"计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。"
|
|
1265
1294
|
),
|
|
1266
1295
|
loading
|
|
1267
1296
|
? el(LoadingView, null)
|
|
1268
|
-
: (
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1297
|
+
: el("div", null,
|
|
1298
|
+
tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
|
|
1299
|
+
tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
|
|
1300
|
+
tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
|
|
1301
|
+
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
|
|
1302
|
+
),
|
|
1272
1303
|
el("div", { style: st.actions, marginTop: 4 },
|
|
1273
1304
|
el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, "导出 CSV"),
|
|
1274
1305
|
el("button", { style: st.btn, onClick: function () { doExport("json"); } }, "导出 JSON"),
|
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
|
|
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)
|
package/scripts/upload-asset.mjs
CHANGED