balance-dsh 2.0.5 → 2.0.6
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/lib/client.js +68 -48
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -20,7 +20,7 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
左缘与设置按钮严格对齐,高度/颜色一致(金额 600 加粗)。
|
|
21
21
|
布局:[meta + 刻度条(竖排,居中)] [金额(最右)] */
|
|
22
22
|
.balance-footer{position:relative;isolation:isolate;box-sizing:border-box;display:flex;align-items:center;justify-content:flex-start;gap:8px;width:100%;min-width:0;height:42px;margin:0;padding:0 0 0 8px;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;text-decoration:none;cursor:pointer;white-space:nowrap;overflow:visible;-webkit-user-select:none;user-select:none}
|
|
23
|
-
/* 带 meta 行时整块变高:42px 装不下「一行 11px 文字 + 13px
|
|
23
|
+
/* 带 meta 行时整块变高:42px 装不下「一行 11px 文字 + 13px 轨道」,改成自适应。 */
|
|
24
24
|
.balance-footer--stacked{height:auto;min-height:42px;padding-top:5px;padding-bottom:5px}
|
|
25
25
|
/* 整行都是热区(单击统计面板 / 双击用量页 / 右键刷新;交互挂在 .balance-stats-anchor、捕获阶段)。
|
|
26
26
|
不给整行画 hover / active 底色——那圈圆角高亮框在侧边栏里太显眼,
|
|
@@ -105,11 +105,7 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
height:auto,撑得住),而不是把胶囊顶出去。 */
|
|
106
106
|
.balance-footer__timeline-meta{display:inline-flex;align-items:center;gap:6px;row-gap:3px;flex-wrap:wrap;justify-content:center;max-width:100%;font-size:11px;line-height:1;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary,#8a8a8a);margin:0}
|
|
107
107
|
.balance-footer__timeline-meta-item{flex:none;white-space:nowrap}
|
|
108
|
-
/* 「今日 ¥x」独占第一行(flex-basis:100
|
|
109
|
-
不加这条时宽度够就会并成一行,用户看到的顺序就变成「今日 … 空闲 剩 …」,
|
|
110
|
-
得不到他要的「空闲跟剩余时间一栏」(2026-10-30 指定)。
|
|
111
|
-
⚠️ 必须配 text-align:center:占满 100% 之后默认是左对齐,而下面那行 group 是居中的,
|
|
112
|
-
两行就会错开(2026-10-30 反馈「今日消费位置不对」)。 */
|
|
108
|
+
/* 「今日 ¥x」独占第一行(flex-basis:100%)并居中,与下面「空闲 剩 3h57m」那一行左对齐成组。 */
|
|
113
109
|
.balance-footer__timeline-meta-item--today{flex:0 0 100%;text-align:center}
|
|
114
110
|
/* 「今日 ¥x」数据没到时的占位:不可见但占位,高度先占好,数据到了只换文字、不动布局。
|
|
115
111
|
⚠️ 不要改回条件渲染或 display:none —— 那会让 meta 行从 1 行变 2 行,
|
|
@@ -215,6 +211,9 @@ window.__ModuleLoader__.load({
|
|
|
215
211
|
.balance-usage-line svg{flex:none;width:14px;height:14px}
|
|
216
212
|
.balance-usage-line:hover,.balance-usage-line[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-secondary)}
|
|
217
213
|
.balance-usage-label{text-overflow:ellipsis;min-width:0;overflow:hidden}
|
|
214
|
+
/* 金额那一段单独成类:算完之前显示「计算中…」,别先闪一个随后会变的数字。 */
|
|
215
|
+
.balance-usage-cost{white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
216
|
+
.balance-usage-cost--pending{opacity:.72}
|
|
218
217
|
/* ── 消耗明细浮层:规格照抄 DSH 原生「会话统计」面板(.HS2XBG_panel/.title/.titleRule/.details)── */
|
|
219
218
|
.balance-usage-panel{box-sizing:border-box;position:fixed;z-index:1100;border:0;border-radius:var(--dsw-radius-lg,12px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-module-platform,#242526));width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);backdrop-filter:var(--dsw-menu-backdrop-filter,none);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent,0 8px 24px rgba(0,0,0,.32));color:var(--dsw-alias-label-secondary);cursor:default;padding:16px;font-size:12px;line-height:18px;animation:balance-stats-panel-in .12s var(--ds-ease-in-out,ease-out) both}
|
|
220
219
|
.balance-usage-panel__title{display:flex;justify-content:space-between;gap:16px;margin-bottom:8px;color:var(--dsw-alias-label-primary);font-weight:500}
|
|
@@ -380,8 +379,9 @@ window.__ModuleLoader__.load({
|
|
|
380
379
|
.balance-tip-body{margin:0;font-size:13px;line-height:1.7;color:var(--dsw-alias-label-secondary)}
|
|
381
380
|
/* 节假日数据行:右侧是「状态文案 + 立即同步」两段,窄屏时自动换行靠右。 */
|
|
382
381
|
.balance-settings__calendar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}
|
|
383
|
-
.balance-settings__calendar-note{font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary);text-align:right;font-variant-numeric:tabular-nums;max-width:
|
|
382
|
+
.balance-settings__calendar-note{font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary);text-align:right;font-variant-numeric:tabular-nums;max-width:100%;word-break:keep-all}
|
|
384
383
|
.balance-settings__calendar-note--warn{color:var(--dsw-alias-state-warn-primary,#d97706)}
|
|
384
|
+
.balance-settings__calendar-note--ok{color:var(--dsw-alias-state-success-primary,#22c55e)}
|
|
385
385
|
.balance-settings__calendar-note--error{color:var(--dsw-alias-state-error-primary,#e5484d)}
|
|
386
386
|
.balance-tip-ok{padding:5px 14px;border:.5px solid transparent;border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;line-height:1.5;cursor:pointer}
|
|
387
387
|
.balance-tip-ok:hover{opacity:.88}
|
|
@@ -2393,41 +2393,34 @@ function TimelineBand(props) {
|
|
|
2393
2393
|
metaItem(period === "peak" ? "高峰" : "空闲", " balance-footer__timeline-meta-item--" + period, "period"),
|
|
2394
2394
|
];
|
|
2395
2395
|
if (span !== null) tail.push(metaItem("剩 " + formatRemain(span.remainMs), "", "remain"));
|
|
2396
|
-
|
|
2397
|
-
//
|
|
2398
|
-
//
|
|
2399
|
-
//
|
|
2396
|
+
// 「今日 ¥x」是 meta 里的第一项(flex-basis:100%,独占第一行)——
|
|
2397
|
+
// 数据没到之前先摆一个不可见占位,让这一行的高度从一开始就是最终高度 ——
|
|
2398
|
+
// 条件渲染会让整块从 1 行变 2 行,侧栏底部(含设置按钮)整体被顶下 14px,
|
|
2399
|
+
// 那一下重排就是「今日余额弹出来时卡住」。
|
|
2400
2400
|
// 占位只在「还在等」时出现;确认拿不到(tried 且为 null)就整项不渲染,不留空行。
|
|
2401
2401
|
const todayCost = today !== null && Number.isFinite(Number(today.cost)) ? Number(today.cost) : null;
|
|
2402
2402
|
const todayPending = todayCost === null && !todayCache.tried;
|
|
2403
|
+
const metaChildren = [];
|
|
2403
2404
|
if (todayCost !== null || todayPending) {
|
|
2404
|
-
metaChildren.push(
|
|
2405
|
-
|
|
2406
|
-
"
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
(todayCost === null ? " balance-footer__timeline-meta-item--pending" : " balance-footer__timeline-meta-item--in"),
|
|
2411
|
-
children: todayCost === null ? "今日 ¥--" : "今日 ¥" + todayCost.toFixed(2),
|
|
2412
|
-
},
|
|
2413
|
-
"today",
|
|
2414
|
-
),
|
|
2415
|
-
);
|
|
2405
|
+
metaChildren.push(jsx.jsx("span", {
|
|
2406
|
+
className:
|
|
2407
|
+
"balance-footer__timeline-meta-item balance-footer__timeline-meta-item--today" +
|
|
2408
|
+
(todayCost === null ? " balance-footer__timeline-meta-item--pending" : " balance-footer__timeline-meta-item--in"),
|
|
2409
|
+
children: todayCost === null ? "今日 ¥--" : "今日 ¥" + todayCost.toFixed(2),
|
|
2410
|
+
}, "today"));
|
|
2416
2411
|
}
|
|
2417
|
-
metaChildren.push(
|
|
2418
|
-
|
|
2419
|
-
);
|
|
2420
|
-
const metaNode = jsx.jsx("span", {
|
|
2421
|
-
className: "balance-footer__timeline-meta",
|
|
2422
|
-
children: metaChildren,
|
|
2423
|
-
});
|
|
2412
|
+
metaChildren.push(jsx.jsx("span", { className: "balance-footer__timeline-meta-group", children: tail }, "tail"));
|
|
2413
|
+
const metaNode = jsx.jsx("span", { className: "balance-footer__timeline-meta", children: metaChildren });
|
|
2424
2414
|
// 新版把「meta 行 + 刻度条」当结构主体,不存在"关掉只剩金额"的形态,
|
|
2425
2415
|
// 所以这里不看 showPeriodLabel;那个开关只在旧版有意义(见下面的 classic 分支)。
|
|
2426
2416
|
const timelineBand = buildTimeline(timelineNow);
|
|
2427
2417
|
// 刷新时不替换时段模块:刻度条保持原样,刷新反馈只落在右侧金额(半透明 + 浪潮)。
|
|
2428
|
-
// 刻度条在 timeline 内部、meta 下方,宽度跟随侧栏(width:100
|
|
2429
|
-
//
|
|
2418
|
+
// 刻度条在 timeline 内部、meta 下方,宽度跟随侧栏(width:100%)。
|
|
2419
|
+
// 容器是普通 flex(不 wrap),实际排版是两段:
|
|
2420
|
+
// [ 今日 ¥x] ← meta 第一行(独占、居中)
|
|
2421
|
+
// [空闲 剩 3h57m] [¥x • 空闲] ← meta 第二行 + 金额胶囊
|
|
2430
2422
|
// [==== 刻度条(跟随侧栏宽度)====]
|
|
2423
|
+
// 「今日」占满左列宽度并居中,与下面「空闲 剩 3h57m」在左列内部成组。
|
|
2431
2424
|
const periodNode = jsx.jsxs("span", {
|
|
2432
2425
|
className: "balance-footer__timeline",
|
|
2433
2426
|
children: [
|
|
@@ -2481,7 +2474,7 @@ function TimelineBand(props) {
|
|
|
2481
2474
|
return withPanel(jsx.jsxs("div", {
|
|
2482
2475
|
className: "balance-footer balance-footer--stacked",
|
|
2483
2476
|
children: [
|
|
2484
|
-
// 抹除层覆盖整块显示区(meta 行 + 时段条 + 金额):白条经过哪里,哪里才显影
|
|
2477
|
+
// 抹除层覆盖整块显示区(today 行 + meta 行 + 时段条 + 金额):白条经过哪里,哪里才显影
|
|
2485
2478
|
waveNode,
|
|
2486
2479
|
periodNode === null
|
|
2487
2480
|
? jsx.jsx("span", { className: "balance-footer__spacer" })
|
|
@@ -2616,6 +2609,14 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2616
2609
|
// 服务端分桶可能"非 null 但为空"(拿不到该会话事件),此时必须回退本地投影估算,否则金额会显示 ¥0.0000
|
|
2617
2610
|
const splitUsable =
|
|
2618
2611
|
split != null && ((Array.isArray(split.byModel) && split.byModel.length > 0) || (split.totalTokens ?? 0) > 0);
|
|
2612
|
+
// 「这个会话问过服务端没有」——没问过就还在算。
|
|
2613
|
+
// 本地估算此时只是临时值:它把**整段历史**按当前时段价一次性乘(高峰窗口里就是 ×2),
|
|
2614
|
+
// 而服务端是按每条消息实际发生的时段分桶。所以高峰时段点进旧会话必然先高后低
|
|
2615
|
+
// (2026-10-30 实测:先显 ¥107、随即跳到 ¥69,比值 1.55 正好是「一半历史在空闲时段」)。
|
|
2616
|
+
// 与其先闪一个会变的数字,不如先显示「计算中…」,拿到结果后一次到位。
|
|
2617
|
+
const splitAsked =
|
|
2618
|
+
sessionId != null && sessionId !== "" && Object.prototype.hasOwnProperty.call(splitMap, sessionId);
|
|
2619
|
+
const costPending = !splitAsked && usageKey > 0;
|
|
2619
2620
|
const cost = splitUsable ? computeSplitCost(split, p, peakMultiplier) : computeCost(usage, periodAdjustedPricing(p, peakMultiplier));
|
|
2620
2621
|
const peakTokens = splitUsable ? split.peak.inputTokens + split.peak.cacheReadTokens + split.peak.cacheWriteTokens + split.peak.outputTokens : 0;
|
|
2621
2622
|
const offpeakTokens = splitUsable ? split.offpeak.inputTokens + split.offpeak.cacheReadTokens + split.offpeak.cacheWriteTokens + split.offpeak.outputTokens : 0;
|
|
@@ -2642,7 +2643,11 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2642
2643
|
? jsx.jsx(primitives.IconDatabaseOutlineRegular, { key: "icon" })
|
|
2643
2644
|
: null,
|
|
2644
2645
|
jsx.jsx("span", { key: "label", className: "balance-usage-label", children: "本会话消耗" }),
|
|
2645
|
-
jsx.jsx("span", {
|
|
2646
|
+
jsx.jsx("span", {
|
|
2647
|
+
key: "cost",
|
|
2648
|
+
className: "balance-usage-cost" + (costPending ? " balance-usage-cost--pending" : ""),
|
|
2649
|
+
children: costPending ? "计算中…" : "≈ ¥" + cost.toFixed(4),
|
|
2650
|
+
}),
|
|
2646
2651
|
];
|
|
2647
2652
|
|
|
2648
2653
|
// ── 点击展开的明细浮层(结构与 DSH 原生「会话统计」一致:标题 + 分隔线 + dl 两列网格)──
|
|
@@ -2670,6 +2675,13 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2670
2675
|
["计费 tokens", formatTokens(billedTotal) + " tok"],
|
|
2671
2676
|
["模型", model || "未知(按 flash 价估算)"],
|
|
2672
2677
|
];
|
|
2678
|
+
// 金额口径要写明白:算完 = 服务端逐条按发生时段分桶;算完前 / 拿不到 = 本地按当前时段估算。
|
|
2679
|
+
// 估算会把整段历史按「此刻」的峰谷价乘一次,高峰窗口里自然偏高 —— 说清楚,别让它冒充精确值。
|
|
2680
|
+
if (costPending) {
|
|
2681
|
+
costDetails.push(["金额", "计算中(正在取服务端分时段用量)"]);
|
|
2682
|
+
} else if (!splitUsable) {
|
|
2683
|
+
costDetails.push(["金额", "按当前时段估算(服务端未返回分桶)"]);
|
|
2684
|
+
}
|
|
2673
2685
|
if (splitUsable) {
|
|
2674
2686
|
costDetails.push(
|
|
2675
2687
|
["高峰 tokens", formatTokens(peakTokens) + " tok"],
|
|
@@ -2965,19 +2977,30 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2965
2977
|
const [calendarSync, setCalendarSync] = react.useState({ kind: "idle", text: "" });
|
|
2966
2978
|
const calendarBusy = calendarSync.kind === "syncing";
|
|
2967
2979
|
const calendarSummary = (() => {
|
|
2968
|
-
|
|
2980
|
+
// 同步失败:直接把服务端给的原因摆出来,别拿旧数据冒充成功。
|
|
2981
|
+
if (calendarSync.kind === "error") return calendarSync.text;
|
|
2982
|
+
// 只要有数据就一律显示同一句「已同步 N 天 · 上次同步时刻 · 待公布」——
|
|
2983
|
+
// **同步成功之后同样要显示时刻**(2026-10-30 反馈「只有在同步过程中才显示上次同步时间」)。
|
|
2984
|
+
// 旧写法让 ok 分支返回另拼的一句「已更新 2025 / 2026(72 天)(2027 待公布)」:
|
|
2985
|
+
// 既丢了时刻,又把年份列表塞回来,200px 宽里直接折成两行、断在「公布)」中间。
|
|
2986
|
+
// 现在 ok 只负责变色(--ok 绿),文案一律由这里按缓存渲染。
|
|
2969
2987
|
if (calendar.fetchedAt > 0) {
|
|
2970
2988
|
const at = new Date(calendar.fetchedAt);
|
|
2971
2989
|
const stamp =
|
|
2972
2990
|
at.getFullYear() + "-" + String(at.getMonth() + 1).padStart(2, "0") + "-" +
|
|
2973
2991
|
String(at.getDate()).padStart(2, "0") + " " + String(at.getHours()).padStart(2, "0") + ":" +
|
|
2974
2992
|
String(at.getMinutes()).padStart(2, "0");
|
|
2975
|
-
const years = calendar.years.length > 0 ? calendar.years.join(" / ") : "—";
|
|
2976
2993
|
// 只报日期与时刻,不报完整年份列表:那串太长会把左边的标题挤成一字一行。
|
|
2977
2994
|
// 「同步了哪几年」由服务端 days 的键直接决定,用户点「立即同步」那边也能看到。
|
|
2978
|
-
|
|
2995
|
+
// 「2027 待公布」内部用不换行空格(\\u00A0)焊死:卡片窄到 298px 时,
|
|
2996
|
+
// 普通空格会允许断在「2027」和「待公布」之间,把这三个字拆成两行(实测)。
|
|
2997
|
+
// 焊死后只可能断在前面的「 · 」处,两行就是「已同步…时刻」/「· 2027 待公布」。
|
|
2998
|
+
const pending = calendar.missing.length > 0
|
|
2999
|
+
? " · " + calendar.missing.map(String).join("\u00A0/\u00A0") + "\u00A0待公布"
|
|
3000
|
+
: "";
|
|
2979
3001
|
return "已同步 " + calendar.count + " 天 · " + stamp.slice(5) + pending;
|
|
2980
3002
|
}
|
|
3003
|
+
if (calendarSync.kind === "syncing") return "正在同步…";
|
|
2981
3004
|
return "尚未同步,正在用插件内置表。";
|
|
2982
3005
|
})();
|
|
2983
3006
|
const onSyncCalendar = async () => {
|
|
@@ -2986,14 +3009,9 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2986
3009
|
try {
|
|
2987
3010
|
const data = await syncCalendar();
|
|
2988
3011
|
if (data && data.ok === true) {
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
: "";
|
|
2993
|
-
setCalendarSync({
|
|
2994
|
-
kind: "ok",
|
|
2995
|
-
text: "已更新 " + years + "(" + (Number(data.added) || 0) + " 天)" + pending,
|
|
2996
|
-
});
|
|
3012
|
+
// 成功不另拼一句文案:syncCalendar() 内部已经 adoptCalendar(data) 刷新了缓存,
|
|
3013
|
+
// calendarSummary 会带着新的同步时刻自己重渲染;这里只把状态切成 ok(绿色)。
|
|
3014
|
+
setCalendarSync({ kind: "ok", text: "" });
|
|
2997
3015
|
} else {
|
|
2998
3016
|
setCalendarSync({
|
|
2999
3017
|
kind: "error",
|
|
@@ -3473,9 +3491,11 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
3473
3491
|
"balance-settings__calendar-note" +
|
|
3474
3492
|
(calendarSync.kind === "error"
|
|
3475
3493
|
? " balance-settings__calendar-note--error"
|
|
3476
|
-
:
|
|
3477
|
-
? ""
|
|
3478
|
-
:
|
|
3494
|
+
: calendarSync.kind === "ok"
|
|
3495
|
+
? " balance-settings__calendar-note--ok"
|
|
3496
|
+
: calendar.fetchedAt > 0
|
|
3497
|
+
? ""
|
|
3498
|
+
: " balance-settings__calendar-note--warn"),
|
|
3479
3499
|
children: calendarSummary,
|
|
3480
3500
|
}),
|
|
3481
3501
|
jsx.jsx("button", {
|
package/lib/index.js
CHANGED
|
@@ -527,7 +527,7 @@ function formatBalance(balance) {
|
|
|
527
527
|
// | { status:"failed" } (网络失败;官方明确“失败绝不变成 0 余额”)
|
|
528
528
|
// client 是 AccountClientMetadata(version / locale / timezoneOffsetSeconds),
|
|
529
529
|
// 缺字段会在平台侧组装请求头时抛错,所以三个字段都要给。
|
|
530
|
-
const PLUGIN_VERSION = "2.0.
|
|
530
|
+
const PLUGIN_VERSION = "2.0.6";
|
|
531
531
|
|
|
532
532
|
// ─── 检查更新 ───────────────────────────────────────────────────────────────
|
|
533
533
|
// 只查 npm registry 的 latest 标签(一个很小的 JSON),不下载任何东西。
|