@lancecheney/dsh-deepseek-balance 0.1.0 → 0.1.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/lib/client.js CHANGED
@@ -6,173 +6,91 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
 
9
- const css = ".Dbg1_root{display:inline-flex;align-items:center;gap:6px;height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:18px;padding:0 12px;font-family:var(--dsw-font-family);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);background:0 0;white-space:nowrap;text-decoration:none;cursor:pointer}.Dbg1_root:hover{background:var(--dsw-alias-interactive-bg-hover)}.Dbg1_num{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-weight:500}.Dbg1_sep{color:var(--dsw-alias-label-dimmed)}.Dbg1_chip{border-radius:9px;padding:0 8px;font-size:11px;line-height:18px;font-weight:600}.Dbg1_peak{color:var(--dsw-alias-state-warn-primary)}.Dbg1_offPeak{color:var(--dsw-alias-state-success-primary)}.Dbg1_legacy{color:var(--dsw-alias-label-tertiary)}.Dbg1_dimmed{color:var(--dsw-alias-label-dimmed)}.Dbg1_plus{font-size:9px;line-height:1;vertical-align:super;color:var(--dsw-alias-state-warn-primary);font-weight:700}.Dbg1_unit{font-size:10px}";
10
- const tagId = "@lancecheney/dsh-deepseek-balance/BillingBadge.css";
11
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
12
- const tag = document.createElement("style");
13
- tag.dataset.plugin = "@lancecheney/dsh-deepseek-balance";
14
- tag.dataset.pluginCss = tagId;
15
- tag.textContent = css;
16
- document.head.appendChild(tag);
17
- }
18
- const cssModule = { root: "Dbg1_root", num: "Dbg1_num", sep: "Dbg1_sep", chip: "Dbg1_chip", peak: "Dbg1_peak", offPeak: "Dbg1_offPeak", legacy: "Dbg1_legacy", dimmed: "Dbg1_dimmed", plus: "Dbg1_plus", unit: "Dbg1_unit" };
19
-
20
- const MODEL_META = {
21
- "deepseek-v4-pro": { label: "DeepSeek-V4-Pro" },
22
- "deepseek-v4-flash": { label: "DeepSeek-V4-Flash" }
9
+ const css = ".Dbg1_root{display:inline-flex;align-items:center;gap:6px;height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:18px;padding:0 12px;font-family:var(--dsw-font-family);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);background:0 0;white-space:nowrap;cursor:pointer}.Dbg1_root:hover{background:var(--dsw-alias-interactive-bg-hover)}.Dbg1_num{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-weight:500}.Dbg1_sep{color:var(--dsw-alias-label-dimmed)}.Dbg1_chip{border-radius:9px;padding:0 8px;font-size:11px;line-height:18px;font-weight:600}.Dbg1_peak{color:var(--dsw-alias-state-warn-primary)}.Dbg1_offPeak{color:var(--dsw-alias-state-success-primary)}.Dbg1_legacy{color:var(--dsw-alias-label-tertiary)}.Dbg1_dimmed{color:var(--dsw-alias-label-dimmed)}.Dbg1_plus{font-size:9px;line-height:1;vertical-align:super;color:var(--dsw-alias-state-warn-primary);font-weight:700}.Dbg1_unit{font-size:10px}";
10
+ const css2 = ".Dp1_backdrop{position:fixed;top:0;bottom:0;right:0;background:rgba(0,0,0,.32);z-index:60}.Dp1_panel{position:fixed;top:0;bottom:0;right:0;z-index:61;display:flex;flex-direction:column;background:var(--dsw-alias-bg-base);border-left:1px solid var(--dsw-alias-border-l1);box-shadow:var(--dsw-shadow-lv3);overflow-y:auto}.Dp1_head{display:flex;flex-direction:column;align-items:center;gap:8px;padding:32px 56px 22px;position:relative}.Dp1_avatar{width:64px;height:64px;border-radius:50%;background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:24px;flex:none;border:2px solid var(--dsw-alias-border-l2)}.Dp1_name{font-size:18px;font-weight:600;color:var(--dsw-alias-label-primary)}.Dp1_key{font-family:var(--dsw-font-mono);color:var(--dsw-alias-label-tertiary);font-size:13px}.Dp1_close{position:fixed;top:16px;right:16px;cursor:pointer;background:0 0;border:0;color:var(--dsw-alias-label-secondary);font-size:22px;line-height:1;z-index:62}.Dp1_body{padding:0 56px 36px;display:flex;flex-direction:column;gap:26px}.Dp1_stats{display:flex;align-items:center;justify-content:center;gap:56px;flex-wrap:wrap;padding:18px 0}.Dp1_statItem{display:flex;flex-direction:column;align-items:center;gap:4px}.Dp1_statValue{font-size:18px;font-weight:600;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}.Dp1_statLabel{font-size:12px;color:var(--dsw-alias-label-tertiary)}.Dp1_sepBar{color:var(--dsw-alias-label-dimmed);font-size:14px}.Dp1_balance{display:flex;flex-wrap:wrap;gap:16px;justify-content:center;margin:0 auto}.Dp1_balanceBlock{position:relative;box-sizing:border-box;width:200px;background:rgba(127,127,127,.10);border-radius:14px;padding:10px 14px;display:flex;flex-direction:column;align-items:flex-start;gap:2px;height:80px}.Dp1_balanceBlockLabel{font-size:12px;color:var(--dsw-alias-label-tertiary)}.Dp1_balanceBlockValue{font-size:28px;font-weight:700;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;margin-top:auto}.Dp1_balanceBlock .Dp1_btn{position:absolute;right:12px;top:10px;height:22px;padding:0 8px;font-size:12px}.Dp1_btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:0 0;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:13px}.Dp1_btn:hover{background:var(--dsw-alias-interactive-bg-hover)}.Dp1_btnPrimary{background:#4d6bfe;color:#fff;border:none}.Dp1_section{padding:16px 0}.Dp1_title{font-size:13px;color:var(--dsw-alias-label-secondary);margin:0 0 12px;font-weight:600;letter-spacing:.02em}.Dp1_row{display:flex;align-items:center;gap:8px;padding:7px 0}.Dp1_twoCol{display:grid;grid-template-columns:1fr 1fr;gap:0 80px;align-items:start}.Dp1_rowTitle{flex:1;min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.Dp1_rowTitleGray{color:var(--dsw-alias-label-dimmed)}.Dp1_rowVal{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:13px;flex:none}.Dp1_rank{color:var(--dsw-alias-label-tertiary);width:18px;text-align:right;flex:none;font-size:13px}.Dp1_monthNav{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:14px}.Dp1_monthGrids{display:flex;flex-wrap:wrap;gap:20px}.Dp1_monthGrids .Dp1_monthGroup{flex:1 1 260px;min-width:0}.Dp1_monthGroup{display:flex;flex-direction:column;align-items:center;gap:8px}.Dp1_monthGroupTitle{font-size:12px;color:var(--dsw-alias-label-tertiary)}.Dp1_grid{display:grid;grid-template-columns:repeat(7,1fr);gap:5px;width:100%;max-width:170px}.Dp1_cell{aspect-ratio:1;border-radius:6px;background:rgba(127,127,127,.08);display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--dsw-alias-label-tertiary)}.Dp1_cellFlat{background:#60a5fa;color:#fff}.Dp1_cellPeak{background:#f97316;color:#fff}.Dp1_cellOff{background:#22c55e;color:#fff}.Dp1_cellBlue{color:#fff}.Dp1_legend{display:flex;gap:12px;margin-top:10px;justify-content:center;font-size:11px;color:var(--dsw-alias-label-tertiary);flex-wrap:wrap}.Dp1_dot{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;vertical-align:middle}.Dp1_nameRow{display:flex;align-items:center;gap:6px}.Dp1_edit{border:0;background:0 0;cursor:pointer;color:var(--dsw-alias-label-tertiary);font-size:14px;padding:2px;line-height:1}.Dp1_edit:hover{color:var(--dsw-alias-label-primary)}.Dp1_editRow{display:flex;align-items:center;gap:8px}.Dp1_editInput{height:30px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-fill-l1);color:var(--dsw-alias-label-primary);font-size:13px;width:180px}@media (max-width:640px){.Dbg1_root{margin-top:80px}.Dp1_head{padding:24px 16px 16px}.Dp1_body{padding:0 16px 24px}.Dp1_twoCol{grid-template-columns:1fr;gap:0}.Dp1_stats{gap:20px;padding:12px 0}.Dp1_close{right:12px;top:12px}}";
11
+ const injectCss = (text, tagId) => {
12
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
13
+ const tag = document.createElement("style");
14
+ tag.dataset.plugin = "@lancecheney/dsh-deepseek-balance";
15
+ tag.dataset.pluginCss = tagId;
16
+ tag.textContent = text;
17
+ document.head.appendChild(tag);
18
+ }
23
19
  };
20
+ injectCss(css, "@lancecheney/dsh-deepseek-balance/BillingBadge.css");
21
+ injectCss(css2, "@lancecheney/dsh-deepseek-balance/UsagePanel.css");
22
+ const C = { root: "Dbg1_root", num: "Dbg1_num", sep: "Dbg1_sep", chip: "Dbg1_chip", peak: "Dbg1_peak", offPeak: "Dbg1_offPeak", legacy: "Dbg1_legacy", dimmed: "Dbg1_dimmed", plus: "Dbg1_plus", unit: "Dbg1_unit" };
23
+ const P = { backdrop: "Dp1_backdrop", panel: "Dp1_panel", head: "Dp1_head", avatar: "Dp1_avatar", name: "Dp1_name", key: "Dp1_key", close: "Dp1_close", body: "Dp1_body", stats: "Dp1_stats", statItem: "Dp1_statItem", statValue: "Dp1_statValue", statLabel: "Dp1_statLabel", sepBar: "Dp1_sepBar", balance: "Dp1_balance", balanceBlock: "Dp1_balanceBlock", balanceBlockLabel: "Dp1_balanceBlockLabel", balanceBlockValue: "Dp1_balanceBlockValue", btn: "Dp1_btn", btnPrimary: "Dp1_btnPrimary", section: "Dp1_section", title: "Dp1_title", row: "Dp1_row", twoCol: "Dp1_twoCol", rowTitle: "Dp1_rowTitle", rowTitleGray: "Dp1_rowTitleGray", rowVal: "Dp1_rowVal", rank: "Dp1_rank", monthNav: "Dp1_monthNav", monthGrids: "Dp1_monthGrids", monthGroup: "Dp1_monthGroup", monthGroupTitle: "Dp1_monthGroupTitle", grid: "Dp1_grid", cell: "Dp1_cell", cellFlat: "Dp1_cellFlat", cellPeak: "Dp1_cellPeak", cellOff: "Dp1_cellOff", cellBlue: "Dp1_cellBlue", legend: "Dp1_legend", dot: "Dp1_dot", nameRow: "Dp1_nameRow", edit: "Dp1_edit", editRow: "Dp1_editRow", editInput: "Dp1_editInput" };
24
24
 
25
+ const MODEL_META = { "deepseek-v4-pro": { label: "DeepSeek-V4-Pro" }, "deepseek-v4-flash": { label: "DeepSeek-V4-Flash" } };
25
26
  const FALLBACK_PRICING = {
26
27
  effectiveFrom: "2026-08-17T00:00:00+08:00",
28
+ weekendFrom: "2026-08-23T00:00:00+08:00",
27
29
  currencies: {
28
- CNY: {
29
- symbol: "¥",
30
- models: {
31
- "deepseek-v4-pro": {
32
- legacy: { hit: 0.025, miss: 3.0, output: 6.0 },
33
- peak: { hit: 0.30, miss: 9.0, output: 27.0 },
34
- offPeak: { hit: 0.15, miss: 4.5, output: 13.5 }
35
- },
36
- "deepseek-v4-flash": {
37
- legacy: { hit: 0.02, miss: 1.0, output: 2.0 },
38
- peak: { hit: 0.10, miss: 3.0, output: 9.0 },
39
- offPeak: { hit: 0.05, miss: 1.5, output: 4.5 }
40
- }
41
- }
42
- },
43
- USD: {
44
- symbol: "$",
45
- models: {
46
- "deepseek-v4-pro": {
47
- legacy: { hit: 0.003625, miss: 0.435, output: 0.87 },
48
- peak: { hit: 0.044, miss: 1.32, output: 3.96 },
49
- offPeak: { hit: 0.022, miss: 0.66, output: 1.98 }
50
- },
51
- "deepseek-v4-flash": {
52
- legacy: { hit: 0.0028, miss: 0.14, output: 0.28 },
53
- peak: { hit: 0.014, miss: 0.44, output: 1.32 },
54
- offPeak: { hit: 0.007, miss: 0.22, output: 0.66 }
55
- }
56
- }
57
- }
30
+ CNY: { symbol: "¥", models: { "deepseek-v4-pro": { legacy: { hit: 0.025, miss: 3.0, output: 6.0 }, peak: { hit: 0.30, miss: 9.0, output: 27.0 }, offPeak: { hit: 0.15, miss: 4.5, output: 13.5 } }, "deepseek-v4-flash": { legacy: { hit: 0.02, miss: 1.0, output: 2.0 }, peak: { hit: 0.10, miss: 3.0, output: 9.0 }, offPeak: { hit: 0.05, miss: 1.5, output: 4.5 } }, "deepseek-v4-flash-vision-exp": { legacy: { hit: 0.02, miss: 1.0, output: 2.0 }, peak: { hit: 0.10, miss: 3.0, output: 9.0 }, offPeak: { hit: 0.05, miss: 1.5, output: 4.5 } } } },
31
+ USD: { symbol: "$", models: { "deepseek-v4-pro": { legacy: { hit: 0.003625, miss: 0.435, output: 0.87 }, peak: { hit: 0.044, miss: 1.32, output: 3.96 }, offPeak: { hit: 0.022, miss: 0.66, output: 1.98 } }, "deepseek-v4-flash": { legacy: { hit: 0.0028, miss: 0.14, output: 0.28 }, peak: { hit: 0.014, miss: 0.44, output: 1.32 }, offPeak: { hit: 0.007, miss: 0.22, output: 0.66 } }, "deepseek-v4-flash-vision-exp": { legacy: { hit: 0.0028, miss: 0.14, output: 0.28 }, peak: { hit: 0.014, miss: 0.44, output: 1.32 }, offPeak: { hit: 0.007, miss: 0.22, output: 0.66 } } } }
58
32
  }
59
33
  };
60
34
  const DEFAULT_MODEL = "deepseek-v4-pro";
61
35
 
62
- function beijingDecimalHour(now) {
63
- let hour = 0;
64
- let minute = 0;
65
- try {
66
- const parts = new Intl.DateTimeFormat("en-GB", {
67
- timeZone: "Asia/Shanghai",
68
- hour12: false,
69
- hour: "2-digit",
70
- minute: "2-digit"
71
- }).formatToParts(now);
72
- for (const part of parts) {
73
- if (part.type === "hour") hour = Number(part.value);
74
- if (part.type === "minute") minute = Number(part.value);
75
- }
76
- } catch {
77
- hour = now.getHours();
78
- minute = now.getMinutes();
79
- }
80
- return hour + minute / 60;
81
- }
82
-
83
- function periodFor(now, effectiveFrom) {
84
- const eff = Date.parse(effectiveFrom);
85
- if (Number.isFinite(eff) && now.getTime() < eff) return "legacy";
86
- const h = beijingDecimalHour(now);
87
- return (h >= 9 && h < 12) || (h >= 14 && h < 18) ? "peak" : "offPeak";
88
- }
89
-
90
- function firstBalance(data) {
91
- const infos = data && data.balance_infos;
92
- if (!Array.isArray(infos) || infos.length === 0) return null;
93
- return infos.find((row) => row.currency === "CNY") || infos[0];
94
- }
95
-
96
- function fmtPrice(value) {
97
- const n = Number(value);
98
- return Number.isFinite(n) ? String(n) : String(value);
99
- }
100
-
101
- function fmtMoney(value) {
102
- const n = Number(value);
103
- if (!Number.isFinite(n)) return "—";
104
- if (n <= 0) return "0.00";
105
- if (n < 0.01) return n.toFixed(4);
106
- if (n < 1) return n.toFixed(3);
107
- return n.toFixed(2);
108
- }
109
-
110
- function costOf(usage, p) {
111
- const miss = (usage.uncachedInputTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
112
- const hit = usage.cacheReadTokens ?? 0;
113
- const output = usage.outputTokens ?? 0;
114
- return (miss * p.miss + hit * p.hit + output * p.output) / 1e6;
115
- }
116
-
117
- function sumBuckets(a, b) {
118
- return {
119
- uncachedInputTokens: (a.uncachedInputTokens ?? 0) + (b.uncachedInputTokens ?? 0),
120
- outputTokens: (a.outputTokens ?? 0) + (b.outputTokens ?? 0),
121
- cacheReadTokens: (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0),
122
- cacheWriteTokens: (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0)
123
- };
124
- }
125
-
126
- function translate(dict, key, params) {
127
- let s = dict[key];
128
- if (s === void 0) return key;
129
- if (params) {
130
- for (const [k, v] of Object.entries(params)) s = s.split(`{${k}}`).join(String(v));
36
+ function beijingDecimalHour(now) { let h = 0, m = 0; try { const p = new Intl.DateTimeFormat("en-GB", { timeZone: "Asia/Shanghai", hour12: false, hour: "2-digit", minute: "2-digit" }).formatToParts(now); for (const x of p) { if (x.type === "hour") h = Number(x.value); if (x.type === "minute") m = Number(x.value); } } catch { h = now.getHours(); m = now.getMinutes(); } return h + m / 60; }
37
+ function periodFor(now, effectiveFrom, weekendFrom) { const eff = Date.parse(effectiveFrom); if (Number.isFinite(eff) && now.getTime() < eff) return "legacy"; const wk = Date.parse(weekendFrom); const d = new Date(now.getTime() + 8 * 3600 * 1000); const day = d.getUTCDay(); if ((day === 0 || day === 6) && Number.isFinite(wk) && now.getTime() >= wk) return "offPeak"; const h = beijingDecimalHour(now); return (h >= 9 && h < 12) || (h >= 14 && h < 18) ? "peak" : "offPeak"; }
38
+ function firstBalance(data) { const infos = data && data.balance_infos; if (!Array.isArray(infos) || infos.length === 0) return null; return infos.find((r) => r.currency === "CNY") || infos[0]; }
39
+ function fmtPrice(v) { const n = Number(v); return Number.isFinite(n) ? String(n) : String(v); }
40
+ function fmtMoney(v) { const n = Number(v); if (!Number.isFinite(n)) return ""; if (n <= 0) return "0.00"; if (n < 0.01) return n.toFixed(4); if (n < 1) return n.toFixed(3); return n.toFixed(2); }
41
+ function translate(dict, key, params) { let s = dict[key]; if (s === void 0) return key; if (params) for (const [k, v] of Object.entries(params)) s = s.split(`{${k}}`).join(String(v)); return s; }
42
+ function fmtTokens(n) { if (!Number.isFinite(n)) return "—"; if (n < 1000) return String(Math.round(n)); const scaled = (v) => v >= 100 ? String(Math.round(v)) : String(Math.round(v * 10) / 10); if (n < 1e6) return scaled(n / 1e3) + "K"; return scaled(n / 1e6) + "M"; }
43
+ function fmtDuration(ms) { if (!Number.isFinite(ms) || ms <= 0) return ""; const s = ms / 1e3; if (s < 60) return Math.round(s) + "s"; const w = Math.round(s); const h = Math.floor(w / 3600); const m = Math.floor((w % 3600) / 60); if (h > 0) return h + "h" + m + "m"; return m + "m" + (w % 60) + "s"; }
44
+ function beijingDayKey(ms) { return new Date(ms + 8 * 3600 * 1000).toISOString().slice(0, 10); }
45
+ function beijingMonthKey(ms) { return new Date(ms + 8 * 3600 * 1000).toISOString().slice(0, 7); }
46
+ function findFrame() { if (typeof document === "undefined") return null; const overlay = document.querySelector("[data-shell-overlay]"); return overlay && overlay.parentElement ? overlay.parentElement : null; }
47
+ function sidebarWidthOf(frame) { const col = frame && frame.firstElementChild; return col ? col.getBoundingClientRect().width : null; }
48
+ const noopSubscribe = () => () => {};
49
+ function dayColor(d) { if (!d || d.tokens <= 0) return "none"; if (d.flat >= d.peak && d.flat >= d.offPeak) return "flat"; return d.peak >= d.offPeak ? "peak" : "off"; }
50
+ function tokenBlue(tokens, max) { if (!tokens || tokens <= 0 || max <= 0) return null; const r = tokens / max; if (r < 0.2) return "#93c5fd"; if (r < 0.4) return "#60a5fa"; if (r < 0.6) return "#3b82f6"; if (r < 0.8) return "#2563eb"; return "#1e40af"; }
51
+ function monthGrid(year, month, daysData) {
52
+ const first = new Date(Date.UTC(year, month, 1));
53
+ const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
54
+ const lead = (first.getUTCDay() + 6) % 7;
55
+ const cells = [];
56
+ for (let i = 0; i < lead; i++) cells.push(null);
57
+ for (let d = 1; d <= daysInMonth; d++) {
58
+ const key = `${year}-${String(month + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
59
+ cells.push({ day: d, key, data: daysData[key] });
131
60
  }
132
- return s;
61
+ return cells;
133
62
  }
134
63
 
135
- const noopSubscribe = () => () => {};
136
-
137
64
  function BillingBadge(props) {
138
- const useProjection = props.useProjection;
139
65
  const directory = props.directory;
140
- const usage = useProjection("tokenUsage");
141
- const byPeriod = useProjection("tokenUsageByPeriod");
66
+ const sessionId = props.sessionId;
67
+ const [open, setOpen] = react.useState(false);
142
68
  const [state, setState] = react.useState({ phase: "loading" });
143
69
 
144
70
  react.useEffect(() => {
145
- let alive = true;
146
- let timer;
71
+ let alive = true; let timer;
147
72
  const load = async () => {
148
73
  try {
149
- const [balanceRes, pricingRes] = await Promise.all([
74
+ const usageUrl = sessionId ? `/api/deepseek-usage?session=${encodeURIComponent(sessionId)}` : "/api/deepseek-usage";
75
+ const [balanceRes, pricingRes, usageRes] = await Promise.all([
150
76
  fetch("/api/deepseek-balance", { headers: { accept: "application/json" }, cache: "no-store" }),
151
- fetch("/api/deepseek-pricing", { headers: { accept: "application/json" }, cache: "no-store" })
77
+ fetch("/api/deepseek-pricing", { headers: { accept: "application/json" }, cache: "no-store" }),
78
+ fetch(usageUrl, { headers: { accept: "application/json" }, cache: "no-store" })
152
79
  ]);
153
80
  const balanceData = await balanceRes.json().catch(() => ({}));
154
81
  const pricingData = await pricingRes.json().catch(() => null);
82
+ const usageData = await usageRes.json().catch(() => null);
155
83
  if (!alive) return;
156
- setState({
157
- phase: "ok",
158
- balance: balanceRes.ok ? balanceData : null,
159
- balanceError: balanceRes.ok ? null : (balanceData.error || `HTTP ${balanceRes.status}`),
160
- pricing: pricingData && pricingRes.ok && pricingData.currencies ? pricingData : FALLBACK_PRICING
161
- });
162
- } catch (error) {
163
- if (alive) setState({ phase: "error", message: error instanceof Error ? error.message : String(error) });
164
- }
165
- };
166
- load();
167
- timer = setInterval(load, 60000);
168
- return () => {
169
- alive = false;
170
- clearInterval(timer);
84
+ setState({ phase: "ok", balance: balanceRes.ok ? balanceData : null, pricing: pricingData && pricingRes.ok && pricingData.currencies ? pricingData : FALLBACK_PRICING, usage: usageData && usageData.spend ? usageData : null });
85
+ } catch (e) { if (alive) setState({ phase: "error" }); }
171
86
  };
172
- }, []);
87
+ load(); timer = setInterval(load, 30000);
88
+ return () => { alive = false; clearInterval(timer); };
89
+ }, [sessionId]);
173
90
 
174
91
  const pricing = state.pricing || FALLBACK_PRICING;
175
92
  const effectiveFrom = pricing.effectiveFrom || FALLBACK_PRICING.effectiveFrom;
93
+ const weekendFrom = pricing.weekendFrom || FALLBACK_PRICING.weekendFrom;
176
94
  const balanceRow = state.phase === "ok" && state.balance ? firstBalance(state.balance) : null;
177
95
  const currency = balanceRow && balanceRow.currency ? balanceRow.currency : "CNY";
178
96
  const dict = currency === "USD" ? en : zh;
@@ -181,132 +99,243 @@ window.__ModuleLoader__.load({
181
99
  const symbol = pricingSet.symbol || "¥";
182
100
  const models = pricingSet.models || FALLBACK_PRICING.currencies.CNY.models;
183
101
 
184
- const modelState = react.useSyncExternalStore(
185
- directory ? (fn) => directory.subscribe(fn) : noopSubscribe,
186
- () => (directory ? directory.getSnapshot() : null)
187
- );
102
+ const modelState = react.useSyncExternalStore(directory ? (fn) => directory.subscribe(fn) : noopSubscribe, () => (directory ? directory.getSnapshot() : null));
188
103
  const currentSelection = modelState && modelState.current ? modelState.current : null;
189
104
  const modelId = currentSelection && currentSelection.model ? currentSelection.model : DEFAULT_MODEL;
190
105
  const reasoningEffort = currentSelection ? currentSelection.reasoningEffort : void 0;
191
106
 
192
- const period = periodFor(new Date(), effectiveFrom);
107
+ const period = periodFor(new Date(), effectiveFrom, weekendFrom);
193
108
  const isPeak = period === "peak";
194
109
  const isLegacy = period === "legacy";
195
110
  const model = models[modelId] || models[DEFAULT_MODEL] || FALLBACK_PRICING.currencies.CNY.models[DEFAULT_MODEL];
196
111
  const p = (period === "peak" ? model.peak : period === "offPeak" ? model.offPeak : model.legacy) || model.peak;
197
112
  const outputPrice = fmtPrice(p.output);
198
- const cost = (() => {
199
- if (byPeriod && byPeriod.peak && byPeriod.offPeak) {
200
- if (isLegacy) {
201
- return model.legacy ? costOf(sumBuckets(byPeriod.peak, byPeriod.offPeak), model.legacy) : null;
202
- }
203
- return costOf(byPeriod.peak, model.peak) + costOf(byPeriod.offPeak, model.offPeak);
204
- }
205
- return usage !== undefined && usage !== null ? costOf(usage, p) : null;
206
- })();
207
-
208
- // off -> base price; high (default) -> one +; max -> two +.
113
+ const cost = state.usage && state.usage.spend ? state.usage.spend.total : null;
209
114
  const plus = reasoningEffort === "max" ? "++" : reasoningEffort === "off" ? "" : "+";
210
-
211
- const periodClass = isLegacy ? cssModule.legacy : isPeak ? cssModule.peak : cssModule.offPeak;
115
+ const periodClass = isLegacy ? C.legacy : isPeak ? C.peak : C.offPeak;
212
116
  const periodLabel = isLegacy ? tr("period.legacy") : isPeak ? tr("period.peak") : tr("period.offPeak");
213
117
 
214
- const tooltipParts = [
215
- isLegacy ? tr("period.legacyNote") : tr("period.peakHours"),
216
- isLegacy ? "" : tr("period.offPeakNote"),
217
- "",
218
- `${tr("label.model")}: ${(MODEL_META[modelId] || {}).label || modelId}${reasoningEffort ? ` · ${reasoningEffort}` : ""}`,
219
- tr("reasoning.note")
220
- ].filter((line) => line !== "");
221
- if (cost !== null && usage) {
222
- tooltipParts.push(
223
- "",
224
- `${tr("label.spent")}: ${symbol}${fmtMoney(cost)} (${tr("cost.estimate")})`,
225
- tr("cost.breakdown", {
226
- miss: (usage.uncachedInputTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
227
- hit: usage.cacheReadTokens ?? 0,
228
- output: usage.outputTokens ?? 0
229
- })
230
- );
231
- }
232
- tooltipParts.push("");
233
- for (const [id, info] of Object.entries(models)) {
234
- const meta = MODEL_META[id] || {};
235
- const row = tr("price.row", { sym: symbol, hit: fmtPrice(info.peak.hit), miss: fmtPrice(info.peak.miss), output: fmtPrice(info.peak.output) });
236
- const off = tr("price.row", { sym: symbol, hit: fmtPrice(info.offPeak.hit), miss: fmtPrice(info.offPeak.miss), output: fmtPrice(info.offPeak.output) });
237
- const legacy = info.legacy ? tr("price.row", { sym: symbol, hit: fmtPrice(info.legacy.hit), miss: fmtPrice(info.legacy.miss), output: fmtPrice(info.legacy.output) }) : null;
238
- tooltipParts.push(
239
- `${meta.label || id} — ${tr("period.peak")}: ${row} / ${tr("period.offPeak")}: ${off}${legacy ? ` / ${tr("period.legacy")}: ${legacy}` : ""}`
240
- );
241
- }
242
- const tooltip = tooltipParts.join("\n");
243
-
244
- const sep = (key) => react.createElement("span", { key, className: cssModule.sep, "aria-hidden": true }, "|");
245
-
118
+ const sep = (key) => react.createElement("span", { key, className: C.sep, "aria-hidden": true }, "|");
246
119
  const segs = [];
247
- segs.push(react.createElement("span", { key: "spent" },
248
- tr("label.spent"), " ",
249
- cost === null
250
- ? react.createElement("span", { className: cssModule.num }, "—")
251
- : react.createElement("span", { className: cssModule.num },
252
- react.createElement("span", { className: cssModule.unit }, symbol),
253
- fmtMoney(cost))));
120
+ segs.push(react.createElement("span", { key: "spent" }, tr("label.spent"), " ", react.createElement("span", { className: C.num }, cost === null ? "—" : `${symbol}${fmtMoney(cost)}`)));
254
121
  segs.push(sep("sep1"));
255
- segs.push(react.createElement("span", { key: "balance" },
256
- tr("label.balance"), " ",
257
- balanceRow === null
258
- ? react.createElement("span", { className: cssModule.num }, state.phase === "loading" ? "…" : tr("balance.unavailable"))
259
- : react.createElement("span", { className: cssModule.num },
260
- react.createElement("span", { className: cssModule.unit }, symbol),
261
- fmtMoney(Number(balanceRow.total_balance)))));
122
+ segs.push(react.createElement("span", { key: "balance" }, tr("label.balance"), " ", react.createElement("span", { className: C.num }, balanceRow !== null ? `${symbol}${fmtMoney(Number(balanceRow.total_balance))}` : (state.phase === "loading" ? "…" : tr("balance.unavailable")))));
262
123
  segs.push(sep("sep2"));
263
- segs.push(react.createElement("span", { key: "period", className: `${cssModule.chip} ${periodClass}` }, periodLabel));
124
+ segs.push(react.createElement("span", { key: "period", className: `${C.chip} ${periodClass}` }, periodLabel));
264
125
  segs.push(sep("sep3"));
265
- segs.push(react.createElement("span", { key: "price" },
266
- react.createElement("span", { className: cssModule.unit }, symbol),
267
- outputPrice,
268
- react.createElement("span", { className: cssModule.unit }, tr("price.perM")),
269
- plus !== "" ? react.createElement("span", { className: cssModule.plus }, plus) : null));
126
+ segs.push(react.createElement("span", { key: "price" }, react.createElement("span", { className: C.unit }, symbol), outputPrice, react.createElement("span", { className: C.unit }, "/M"), plus !== "" ? react.createElement("span", { className: C.plus }, plus) : null));
270
127
 
271
- return react.createElement("a", { className: cssModule.root, title: tooltip, href: "https://platform.deepseek.com/usage", target: "_blank", rel: "noreferrer noopener" }, segs);
128
+ return react.createElement(react.Fragment, null,
129
+ react.createElement("button", { type: "button", className: C.root, onClick: () => setOpen(true) }, segs),
130
+ open ? react.createElement(UsagePanel, { sessionId: props.sessionId, currency, symbol, pricing, effectiveFrom, tr, onClose: () => setOpen(false) }) : null
131
+ );
132
+ }
133
+
134
+ function UsagePanel({ currency, symbol, pricing, tr, onClose }) {
135
+ const [usageData, setUsageData] = react.useState(null);
136
+ const [balance, setBalance] = react.useState(null);
137
+ const [sidebarW, setSidebarW] = react.useState(280);
138
+ const [monthIdxPeriod, setMonthIdxPeriod] = react.useState(0);
139
+ const [monthIdxTokens, setMonthIdxTokens] = react.useState(0);
140
+ const [editing, setEditing] = react.useState(false);
141
+ const [draft, setDraft] = react.useState("");
142
+
143
+ react.useEffect(() => {
144
+ let alive = true;
145
+ (async () => {
146
+ try {
147
+ const [u, b] = await Promise.all([
148
+ fetch("/api/deepseek-usage", { headers: { accept: "application/json" }, cache: "no-store" }).then((r) => r.json()),
149
+ fetch("/api/deepseek-balance", { headers: { accept: "application/json" }, cache: "no-store" }).then((r) => r.json())
150
+ ]);
151
+ if (!alive) return;
152
+ setUsageData(u); setBalance(firstBalance(b));
153
+ } catch (e) {}
154
+ })();
155
+ return () => { alive = false; };
156
+ }, []);
157
+
158
+ react.useEffect(() => {
159
+ const frame = findFrame();
160
+ const col = frame && frame.firstElementChild;
161
+ if (!col) return;
162
+ const apply = () => setSidebarW(col.getBoundingClientRect().width);
163
+ apply();
164
+ const ro = new ResizeObserver(apply);
165
+ ro.observe(col);
166
+ return () => ro.disconnect();
167
+ }, []);
168
+
169
+ const months = react.useMemo(() => {
170
+ const set = new Set();
171
+ const cur = beijingMonthKey(Date.now());
172
+ if (usageData && usageData.days) for (const k of Object.keys(usageData.days)) set.add(k.slice(0, 7));
173
+ set.add(cur);
174
+ return [...set].sort();
175
+ }, [usageData]);
176
+ const monthKeyPeriod = months[months.length - 1 - monthIdxPeriod] || beijingMonthKey(Date.now());
177
+ const [yP, mP] = monthKeyPeriod.split("-").map(Number);
178
+ const cellsPeriod = react.useMemo(() => monthGrid(yP, mP - 1, usageData ? usageData.days : {}), [yP, mP, usageData]);
179
+ const monthKeyTokens = months[months.length - 1 - monthIdxTokens] || beijingMonthKey(Date.now());
180
+ const [yT, mT] = monthKeyTokens.split("-").map(Number);
181
+ const cellsTokens = react.useMemo(() => monthGrid(yT, mT - 1, usageData ? usageData.days : {}), [yT, mT, usageData]);
182
+ const maxDayTokens = react.useMemo(() => {
183
+ if (!usageData || !usageData.days) return 0;
184
+ return Object.values(usageData.days).reduce((m, d) => Math.max(m, d.tokens || 0), 0);
185
+ }, [usageData]);
186
+ const pricingSet = (pricing.currencies && pricing.currencies[currency]) || pricing.currencies.CNY;
187
+ const sym = pricingSet.symbol || "¥";
188
+
189
+ const totalSpend = usageData && usageData.spend ? usageData.spend.total : null;
190
+ const todaySpend = usageData && usageData.spend ? usageData.spend.today : null;
191
+
192
+ const modelEffortList = react.useMemo(() => {
193
+ if (!usageData || !usageData.modelEffort) return [];
194
+ return Object.entries(usageData.modelEffort).map(([k, v]) => ({ key: k, tokens: v.tokens })).sort((a, b) => b.tokens - a.tokens);
195
+ }, [usageData]);
196
+
197
+ const cellFor = (cell) => {
198
+ if (!cell) return react.createElement("div", { key: "b" + Math.random(), className: P.cell });
199
+ const color = dayColor(cell.data);
200
+ const cls = color === "flat" ? P.cell + " " + P.cellFlat : color === "peak" ? P.cell + " " + P.cellPeak : color === "off" ? P.cell + " " + P.cellOff : P.cell;
201
+ return react.createElement("div", { key: cell.key, className: cls, title: cell.key }, cell.day);
202
+ };
203
+ const tokenCellFor = (cell) => {
204
+ if (!cell) return react.createElement("div", { key: "t" + Math.random(), className: P.cell });
205
+ const tokens = cell.data ? cell.data.tokens : 0;
206
+ const color = tokenBlue(tokens, maxDayTokens);
207
+ return react.createElement("div", { key: cell.key, className: P.cell + (color ? " " + P.cellBlue : ""), style: color ? { background: color } : void 0, title: cell.key }, cell.day);
208
+ };
209
+
210
+ const modelLabel = (k) => { const [m, e] = k.split("|"); return `${(MODEL_META[m] || {}).label || m}${e ? " · " + e : ""}`; };
211
+ const stat = (label, value) => react.createElement("div", { className: P.statItem }, react.createElement("div", { className: P.statValue }, value), react.createElement("div", { className: P.statLabel }, label));
212
+ const sepBar = (key) => react.createElement("span", { key, className: P.sepBar }, "|");
213
+ const tokenName = usageData && usageData.tokenName ? usageData.tokenName : "API key";
214
+ const saveName = async () => {
215
+ const name = draft.trim() || "API key";
216
+ try {
217
+ const r = await fetch("/api/deepseek-balance/token-name", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ tokenName: name }) });
218
+ const d = await r.json();
219
+ setUsageData((u) => (u ? { ...u, tokenName: d.tokenName } : u));
220
+ } catch {}
221
+ setEditing(false);
222
+ };
223
+
224
+ return react.createElement(react.Fragment, null,
225
+ react.createElement("div", { className: P.backdrop, style: { left: sidebarW + "px" }, onClick: onClose }),
226
+ react.createElement("div", { className: P.panel, style: { left: sidebarW + "px" } },
227
+ react.createElement("div", { className: P.head },
228
+ react.createElement("div", { className: P.avatar }, tokenName.slice(0, 2).toUpperCase()),
229
+ editing ? react.createElement("div", { className: P.editRow }, react.createElement("input", { className: P.editInput, value: draft, onChange: (e) => setDraft(e.target.value), autoFocus: true }), react.createElement("button", { className: P.btn + " " + P.btnPrimary, onClick: saveName }, tr("panel.save"))) : react.createElement("div", { className: P.nameRow }, react.createElement("span", { className: P.name }, tokenName), react.createElement("button", { className: P.edit, onClick: () => { setDraft(tokenName); setEditing(true); } }, "✎")),
230
+ react.createElement("div", { className: P.key }, usageData && usageData.apiKeyPreview ? usageData.apiKeyPreview : tr("balance.unavailable")),
231
+ react.createElement("button", { className: P.close, onClick: onClose }, "×")
232
+ ),
233
+ react.createElement("div", { className: P.body },
234
+ react.createElement("div", { className: P.stats },
235
+ stat(tr("stat.total"), usageData ? fmtTokens(usageData.totalTokens) : "…"), sepBar("s1"),
236
+ stat(tr("stat.dailyPeak"), usageData ? fmtTokens(usageData.dailyPeakTokens) : "…"), sepBar("s2"),
237
+ stat(tr("stat.longest"), usageData ? fmtDuration(usageData.longestChatMs) : "…"), sepBar("s3"),
238
+ stat(tr("stat.todayTokens"), usageData && usageData.days && usageData.days[beijingDayKey(Date.now())] ? fmtTokens(usageData.days[beijingDayKey(Date.now())].tokens) : "…"), sepBar("s4"),
239
+ stat(tr("stat.longStreak"), usageData ? usageData.longestStreak + tr("unit.day") : "…")
240
+ ),
241
+ react.createElement("div", { className: P.balance },
242
+ react.createElement("div", { className: P.balanceBlock },
243
+ react.createElement("div", { className: P.balanceBlockLabel }, tr("stat.today")),
244
+ react.createElement("div", { className: P.balanceBlockValue }, todaySpend !== null ? `${sym}${fmtMoney(todaySpend)}` : "…")
245
+ ),
246
+ react.createElement("div", { className: P.balanceBlock },
247
+ react.createElement("div", { className: P.balanceBlockLabel }, tr("stat.cumulative")),
248
+ react.createElement("div", { className: P.balanceBlockValue }, totalSpend !== null ? `${sym}${fmtMoney(totalSpend)}` : "…")
249
+ ),
250
+ react.createElement("div", { className: P.balanceBlock },
251
+ react.createElement("div", { className: P.balanceBlockLabel }, tr("label.balance")),
252
+ react.createElement("div", { className: P.balanceBlockValue }, balance ? `${sym}${fmtMoney(Number(balance.total_balance))}` : "…"),
253
+ react.createElement("button", { className: P.btn + " " + P.btnPrimary, onClick: () => window.open("https://platform.deepseek.com/usage", "_blank", "noopener") }, tr("panel.topup"))
254
+ )
255
+ ),
256
+ react.createElement("div", { className: P.section },
257
+ react.createElement("h3", { className: P.title }, tr("panel.month")),
258
+ react.createElement("div", { className: P.monthGrids },
259
+ react.createElement("div", { className: P.monthGroup },
260
+ react.createElement("div", { className: P.monthNav },
261
+ react.createElement("button", { className: P.btn, disabled: monthIdxPeriod >= months.length - 1, onClick: () => setMonthIdxPeriod(monthIdxPeriod + 1) }, "‹"),
262
+ react.createElement("span", { className: P.rowVal }, tr("panel.monthPeriod") + " · " + monthKeyPeriod),
263
+ react.createElement("button", { className: P.btn, disabled: monthIdxPeriod <= 0, onClick: () => setMonthIdxPeriod(monthIdxPeriod - 1) }, "›")
264
+ ),
265
+ react.createElement("div", { className: P.grid }, cellsPeriod.map(cellFor)),
266
+ react.createElement("div", { className: P.legend },
267
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "#60a5fa" } }), tr("legend.flat")),
268
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "#f97316" } }), tr("legend.peak")),
269
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "#22c55e" } }), tr("legend.off")),
270
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "rgba(127,127,127,.15)" } }), tr("legend.none"))
271
+ )
272
+ ),
273
+ react.createElement("div", { className: P.monthGroup },
274
+ react.createElement("div", { className: P.monthNav },
275
+ react.createElement("button", { className: P.btn, disabled: monthIdxTokens >= months.length - 1, onClick: () => setMonthIdxTokens(monthIdxTokens + 1) }, "‹"),
276
+ react.createElement("span", { className: P.rowVal }, tr("panel.monthTokens") + " · " + monthKeyTokens),
277
+ react.createElement("button", { className: P.btn, disabled: monthIdxTokens <= 0, onClick: () => setMonthIdxTokens(monthIdxTokens - 1) }, "›")
278
+ ),
279
+ react.createElement("div", { className: P.grid }, cellsTokens.map(tokenCellFor)),
280
+ react.createElement("div", { className: P.legend },
281
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "#93c5fd" } }), tr("legend.less")),
282
+ react.createElement("span", null, react.createElement("span", { className: P.dot, style: { background: "#1e40af" } }), tr("legend.more"))
283
+ )
284
+ )
285
+ )
286
+ ),
287
+ react.createElement("div", { className: P.twoCol },
288
+ react.createElement("div", { className: P.section },
289
+ react.createElement("h3", { className: P.title }, tr("panel.topSessions")),
290
+ (usageData ? (usageData.sessions || []).slice(0, 8) : []).map((s, i) => react.createElement("div", { key: s.id, className: P.row },
291
+ react.createElement("span", { className: P.rank }, String(i + 1)),
292
+ react.createElement("span", { className: P.rowTitle + (s.archived ? " " + P.rowTitleGray : "") }, s.title || s.id),
293
+ react.createElement("span", { className: P.rowVal }, fmtTokens(s.tokens))
294
+ ))
295
+ ),
296
+ react.createElement("div", { className: P.section },
297
+ react.createElement("h3", { className: P.title }, tr("panel.peakSessions")),
298
+ (usageData ? [...(usageData.sessions || [])].sort((a, b) => b.peakTokens - a.peakTokens).slice(0, 5) : []).map((s, i) => react.createElement("div", { key: s.id, className: P.row },
299
+ react.createElement("span", { className: P.rank }, String(i + 1)),
300
+ react.createElement("span", { className: P.rowTitle }, s.title || s.id),
301
+ react.createElement("span", { className: P.rowVal }, fmtTokens(s.peakTokens))
302
+ ))
303
+ )
304
+ ),
305
+ react.createElement("div", { className: P.twoCol },
306
+ react.createElement("div", { className: P.section },
307
+ react.createElement("h3", { className: P.title }, tr("panel.modelEffort")),
308
+ modelEffortList.map((m) => react.createElement("div", { key: m.key, className: P.row },
309
+ react.createElement("span", { className: P.rowTitle }, modelLabel(m.key)),
310
+ react.createElement("span", { className: P.rowVal }, fmtTokens(m.tokens))
311
+ ))
312
+ )
313
+ )
314
+ )
315
+ )
316
+ );
272
317
  }
273
318
 
274
319
  const NS = "deepseek-balance";
275
320
  const zh = {
276
- "label.spent": "消耗",
277
- "label.balance": "余额",
278
- "label.model": "模型",
279
- "period.peak": "高峰",
280
- "period.offPeak": "空闲",
281
- "period.legacy": "平价",
282
- "period.legacyNote": "当前为调价前平价(8/17 前)",
283
- "period.peakHours": "高峰:09:00–12:00、14:00–18:00(北京时间)",
284
- "period.offPeakNote": "空闲:其余时段",
321
+ "label.spent": "消耗", "label.balance": "余额", "label.model": "模型",
322
+ "period.peak": "高峰", "period.offPeak": "空闲", "period.legacy": "平价",
285
323
  "balance.unavailable": "余额不可用",
286
- "price.perM": "/M",
287
- "price.unit": "输出",
288
- "price.row": "命中{sym}{hit}/未命中{sym}{miss}/输出{sym}{output}",
289
- "cost.estimate": "按当前时段价格估算",
290
- "cost.breakdown": "输入(未命中) {miss} · 输入(命中) {hit} · 输出 {output} tokens",
291
- "reasoning.note": "思考链按输出 token 计费;+ / ++ 表示 high / max 思考会额外增加输出量"
324
+ "panel.stats": "统计", "panel.balance": "余额与消耗", "panel.topup": "跳转API官网", "panel.save": "保存",
325
+ "panel.topSessions": "消耗最多的对话", "panel.month": "每日分布", "panel.monthPeriod": "每日时段分布", "panel.monthTokens": "每日 Token 消耗", "panel.peakSessions": "高峰消耗排行", "panel.modelEffort": "模型与思考强度",
326
+ "stat.total": "累计 Token", "stat.dailyPeak": "每日峰值", "stat.longest": "最长聊天", "stat.longStreak": "最长连续", "stat.todayTokens": "今日 Token", "stat.today": "今日消耗", "stat.cumulative": "累计消耗",
327
+ "unit.day": "",
328
+ "legend.flat": "平价", "legend.peak": "高峰", "legend.off": "空闲", "legend.none": "无数据", "legend.less": "少", "legend.more": "多"
292
329
  };
293
330
  const en = {
294
- "label.spent": "Spent",
295
- "label.balance": "Balance",
296
- "label.model": "Model",
297
- "period.peak": "Peak",
298
- "period.offPeak": "Off-peak",
299
- "period.legacy": "Flat",
300
- "period.legacyNote": "Pre-change flat pricing (before Aug 17)",
301
- "period.peakHours": "Peak: 01:00–04:00, 06:00–10:00 UTC",
302
- "period.offPeakNote": "Off-peak: all other hours",
331
+ "label.spent": "Spent", "label.balance": "Balance", "label.model": "Model",
332
+ "period.peak": "Peak", "period.offPeak": "Off-peak", "period.legacy": "Flat",
303
333
  "balance.unavailable": "balance unavailable",
304
- "price.perM": "/M",
305
- "price.unit": "output",
306
- "price.row": "hit {sym}{hit}/miss {sym}{miss}/output {sym}{output}",
307
- "cost.estimate": "estimated at the current period's price",
308
- "cost.breakdown": "input(miss) {miss} · input(hit) {hit} · output {output} tokens",
309
- "reasoning.note": "Reasoning tokens are billed as output; + / ++ mark high / max effort which adds extra output"
334
+ "panel.stats": "Stats", "panel.balance": "Balance & Spend", "panel.topup": "API site", "panel.save": "Save",
335
+ "panel.topSessions": "Top conversations", "panel.month": "Daily", "panel.monthPeriod": "Daily period", "panel.monthTokens": "Daily token usage", "panel.peakSessions": "Peak-hour ranking", "panel.modelEffort": "Model & effort",
336
+ "stat.total": "Total tokens", "stat.dailyPeak": "Daily peak", "stat.longest": "Longest chat", "stat.longStreak": "Longest streak", "stat.todayTokens": "Today tokens", "stat.today": "Today", "stat.cumulative": "Total",
337
+ "unit.day": "d",
338
+ "legend.flat": "Flat", "legend.peak": "Peak", "legend.off": "Off-peak", "legend.none": "None", "legend.less": "Less", "legend.more": "More"
310
339
  };
311
340
 
312
341
  const inject = ["slots", "locale"];
@@ -319,12 +348,8 @@ window.__ModuleLoader__.load({
319
348
  inject: (sessionId) => {
320
349
  const models = ctx.get("modelDirectories");
321
350
  let directory;
322
- if (models && typeof models.directoryFor === "function") {
323
- try {
324
- directory = models.directoryFor(sessionId);
325
- } catch {}
326
- }
327
- return { directory: directory ? directory.store : void 0 };
351
+ if (models && typeof models.directoryFor === "function") { try { directory = models.directoryFor(sessionId); } catch {} }
352
+ return { directory: directory ? directory.store : void 0, sessionId };
328
353
  }
329
354
  }, BillingBadge));
330
355
  }