@cassius0924/dsh-usage-dashboard 0.3.0 → 0.4.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/README.md +10 -1
- package/lib/client.js +2288 -645
- package/lib/client.js.map +4 -4
- package/lib/index.js +236 -91
- package/lib/index.js.map +3 -3
- package/lib/types/client/api.d.ts +2 -1
- package/lib/types/client/boundary.d.ts +28 -0
- package/lib/types/client/budget.d.ts +16 -0
- package/lib/types/client/cache.d.ts +1 -1
- package/lib/types/client/chart-focus.d.ts +35 -0
- package/lib/types/client/charts.d.ts +23 -5
- package/lib/types/client/context.d.ts +10 -0
- package/lib/types/client/dashboard.d.ts +5 -2
- package/lib/types/client/dom.d.ts +23 -0
- package/lib/types/client/export.d.ts +9 -0
- package/lib/types/client/freshness.d.ts +5 -0
- package/lib/types/client/i18n.d.ts +15 -0
- package/lib/types/client/index.d.ts +0 -5
- package/lib/types/client/locales.d.ts +245 -0
- package/lib/types/client/metric.d.ts +12 -0
- package/lib/types/client/store.d.ts +7 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/contract.d.ts +92 -0
- package/lib/types/index.d.ts +9 -6
- package/lib/types/usage.d.ts +10 -10
- package/package.json +4 -2
package/lib/client.js
CHANGED
|
@@ -27,23 +27,74 @@ __export(index_exports, {
|
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
29
|
|
|
30
|
-
// src/client/
|
|
31
|
-
var
|
|
30
|
+
// src/client/boundary.tsx
|
|
31
|
+
var import_react = require("react");
|
|
32
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
|
+
var PREFIX = "dsh-usage-dashboard:";
|
|
34
|
+
function clearPluginStorage() {
|
|
35
|
+
if (typeof localStorage === "undefined") return;
|
|
36
|
+
try {
|
|
37
|
+
for (const key of Object.keys(localStorage)) {
|
|
38
|
+
if (key.startsWith(PREFIX)) localStorage.removeItem(key);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
var ErrorBoundary = class extends import_react.Component {
|
|
44
|
+
state = { message: null };
|
|
45
|
+
static getDerivedStateFromError(error) {
|
|
46
|
+
return { message: error?.message ?? String(error) };
|
|
47
|
+
}
|
|
48
|
+
componentDidCatch(error, info) {
|
|
49
|
+
console.error("[dsh-usage-dashboard] render failed", error, info.componentStack);
|
|
50
|
+
}
|
|
51
|
+
reset = () => {
|
|
52
|
+
clearPluginStorage();
|
|
53
|
+
location.reload();
|
|
54
|
+
};
|
|
55
|
+
render() {
|
|
56
|
+
const { message } = this.state;
|
|
57
|
+
if (message === null) return this.props.children;
|
|
58
|
+
if (this.props.silent === true) return null;
|
|
59
|
+
const { t } = this.props;
|
|
60
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dq-balance", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dq-card", children: [
|
|
61
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dq-card-title", children: t("boundary.title") }),
|
|
62
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dq-empty", children: t("boundary.body") }),
|
|
63
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dq-crash", children: message }),
|
|
64
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dq-refresh-btn", onClick: this.reset, children: t("boundary.reset") })
|
|
65
|
+
] }) });
|
|
66
|
+
}
|
|
67
|
+
};
|
|
32
68
|
|
|
33
69
|
// src/client/cache.ts
|
|
34
|
-
var LS_VERSION =
|
|
35
|
-
function loadPersisted(storageKey) {
|
|
70
|
+
var LS_VERSION = 2;
|
|
71
|
+
function loadPersisted(storageKey, isUsable) {
|
|
36
72
|
if (typeof localStorage === "undefined") return null;
|
|
73
|
+
const key = `${storageKey}:v${LS_VERSION}`;
|
|
37
74
|
try {
|
|
38
|
-
const raw = localStorage.getItem(
|
|
75
|
+
const raw = localStorage.getItem(key);
|
|
39
76
|
if (raw === null) return null;
|
|
40
77
|
const parsed = JSON.parse(raw);
|
|
41
78
|
if (parsed.data === void 0 || typeof parsed.at !== "number") return null;
|
|
79
|
+
if (isUsable !== void 0 && !isUsable(parsed.data)) {
|
|
80
|
+
localStorage.removeItem(key);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
42
83
|
return { data: parsed.data, at: parsed.at };
|
|
43
84
|
} catch {
|
|
44
85
|
return null;
|
|
45
86
|
}
|
|
46
87
|
}
|
|
88
|
+
function pruneOldVersions(storageKey) {
|
|
89
|
+
if (typeof localStorage === "undefined") return;
|
|
90
|
+
try {
|
|
91
|
+
const current = `${storageKey}:v${LS_VERSION}`;
|
|
92
|
+
for (const key of Object.keys(localStorage)) {
|
|
93
|
+
if (key.startsWith(`${storageKey}:v`) && key !== current) localStorage.removeItem(key);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
47
98
|
function persist(storageKey, hit) {
|
|
48
99
|
if (typeof localStorage === "undefined") return;
|
|
49
100
|
try {
|
|
@@ -51,8 +102,9 @@ function persist(storageKey, hit) {
|
|
|
51
102
|
} catch {
|
|
52
103
|
}
|
|
53
104
|
}
|
|
54
|
-
function createCache(ttlMs, storageKey) {
|
|
55
|
-
|
|
105
|
+
function createCache(ttlMs, storageKey, isUsable) {
|
|
106
|
+
if (storageKey !== void 0) pruneOldVersions(storageKey);
|
|
107
|
+
let hit = storageKey === void 0 ? null : loadPersisted(storageKey, isUsable);
|
|
56
108
|
return {
|
|
57
109
|
get: () => hit,
|
|
58
110
|
getFresh: () => hit !== null && Date.now() - hit.at < ttlMs ? hit.data : null,
|
|
@@ -63,6 +115,9 @@ function createCache(ttlMs, storageKey) {
|
|
|
63
115
|
};
|
|
64
116
|
}
|
|
65
117
|
|
|
118
|
+
// src/contract.ts
|
|
119
|
+
var USAGE_WINDOW_DAYS = [7, 30, 90, 365];
|
|
120
|
+
|
|
66
121
|
// src/client/api.ts
|
|
67
122
|
var BALANCE_TTL_MS = 6e4;
|
|
68
123
|
var USAGE_TTL_MS = 5 * 6e4;
|
|
@@ -73,8 +128,14 @@ async function getJson(path, cache) {
|
|
|
73
128
|
}
|
|
74
129
|
return res.json();
|
|
75
130
|
}
|
|
76
|
-
var
|
|
77
|
-
|
|
131
|
+
var usageIsUsable = (res) => {
|
|
132
|
+
if (res.ok !== true) return false;
|
|
133
|
+
const data = res.data;
|
|
134
|
+
return data !== void 0 && Array.isArray(data.daily) && Array.isArray(data.hourly) && Array.isArray(data.heatmap) && Array.isArray(data.models) && Array.isArray(data.sessions) && typeof data.sessionCount === "number" && data.coverage?.scope === "local-dsh-session-logs" && typeof data.coverage.scannedSessions === "number" && typeof data.coverage.skippedRecords === "number" && Array.isArray(data.windows) && data.windows.length === USAGE_WINDOW_DAYS.length && USAGE_WINDOW_DAYS.every((days) => data.windows.some((window2) => window2.days === days)) && data.windows.every((window2) => Array.isArray(window2.daily) && Array.isArray(window2.hourly) && Array.isArray(window2.models) && Array.isArray(window2.sessions)) && data.totals !== void 0 && typeof data.totals.cacheSavings === "number" && data.summary?.today !== void 0 && Array.isArray(data.pricing?.tiers) && data.peakSplit?.peak !== void 0;
|
|
135
|
+
};
|
|
136
|
+
var balanceIsUsable = (res) => res.ok === true && Array.isArray(res.data?.balances);
|
|
137
|
+
var balanceCache = createCache(BALANCE_TTL_MS, "dsh-usage-dashboard:balance", balanceIsUsable);
|
|
138
|
+
var usageCache = createCache(USAGE_TTL_MS, "dsh-usage-dashboard:usage", usageIsUsable);
|
|
78
139
|
var getCachedBalance = () => balanceCache.get()?.data ?? null;
|
|
79
140
|
var getCachedUsage = () => usageCache.get()?.data ?? null;
|
|
80
141
|
var getCachedUsageAt = () => usageCache.get()?.at ?? null;
|
|
@@ -85,109 +146,830 @@ var subscribeUsage = (fn) => {
|
|
|
85
146
|
usageListeners.delete(fn);
|
|
86
147
|
};
|
|
87
148
|
};
|
|
149
|
+
var balanceInflight = null;
|
|
150
|
+
var usageInflight = null;
|
|
88
151
|
async function fetchBalance(force = false) {
|
|
89
152
|
if (!force) {
|
|
90
153
|
const hit = balanceCache.getFresh();
|
|
91
154
|
if (hit !== null) return hit;
|
|
155
|
+
if (balanceInflight !== null) return balanceInflight;
|
|
92
156
|
}
|
|
93
157
|
const suffix = force ? "?refresh=1" : "";
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
158
|
+
const task = getJson(`/api/dsh-usage-dashboard/balance${suffix}`, force ? "no-store" : "default").then((res) => {
|
|
159
|
+
if (res.ok) balanceCache.put(res);
|
|
160
|
+
return res;
|
|
161
|
+
});
|
|
162
|
+
if (!force) {
|
|
163
|
+
balanceInflight = task.finally(() => {
|
|
164
|
+
balanceInflight = null;
|
|
165
|
+
});
|
|
166
|
+
return balanceInflight;
|
|
167
|
+
}
|
|
168
|
+
return task;
|
|
97
169
|
}
|
|
98
170
|
async function fetchUsage(force = false) {
|
|
99
171
|
if (!force) {
|
|
100
172
|
const hit = usageCache.getFresh();
|
|
101
173
|
if (hit !== null) return hit;
|
|
174
|
+
if (usageInflight !== null) return usageInflight;
|
|
102
175
|
}
|
|
103
176
|
const suffix = force ? "?refresh=1" : "";
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
177
|
+
const task = getJson(`/api/dsh-usage-dashboard/usage${suffix}`, force ? "no-store" : "default").then((res) => {
|
|
178
|
+
if (res.ok) {
|
|
179
|
+
usageCache.put(res);
|
|
180
|
+
for (const listener of [...usageListeners]) listener();
|
|
181
|
+
}
|
|
182
|
+
return res;
|
|
183
|
+
});
|
|
184
|
+
if (!force) {
|
|
185
|
+
usageInflight = task.finally(() => {
|
|
186
|
+
usageInflight = null;
|
|
187
|
+
});
|
|
188
|
+
return usageInflight;
|
|
108
189
|
}
|
|
109
|
-
return
|
|
190
|
+
return task;
|
|
191
|
+
}
|
|
192
|
+
var sessionUsageInflight = /* @__PURE__ */ new Map();
|
|
193
|
+
async function fetchSessionUsage(sessionId, force = false) {
|
|
194
|
+
if (typeof sessionId !== "string" || sessionId === "") {
|
|
195
|
+
return { ok: false, error: "\u7F3A\u5C11\u4F1A\u8BDD id" };
|
|
196
|
+
}
|
|
197
|
+
if (!force) {
|
|
198
|
+
const inflight = sessionUsageInflight.get(sessionId);
|
|
199
|
+
if (inflight !== void 0) return inflight;
|
|
200
|
+
}
|
|
201
|
+
const task = getJson(`/api/dsh-usage-dashboard/session?id=${encodeURIComponent(sessionId)}`, "no-store");
|
|
202
|
+
if (force) return task;
|
|
203
|
+
const tracked = task.finally(() => {
|
|
204
|
+
if (sessionUsageInflight.get(sessionId) === tracked) sessionUsageInflight.delete(sessionId);
|
|
205
|
+
});
|
|
206
|
+
sessionUsageInflight.set(sessionId, tracked);
|
|
207
|
+
return tracked;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/client/dashboard.tsx
|
|
211
|
+
var import_react4 = require("react");
|
|
212
|
+
|
|
213
|
+
// src/client/budget.ts
|
|
214
|
+
var BEIJING_OFFSET_MS = 8 * 60 * 60 * 1e3;
|
|
215
|
+
function budgetSnapshot(spentValue, budgetValue, nowMs = Date.now()) {
|
|
216
|
+
const spent = Number.isFinite(spentValue) ? Math.max(0, spentValue) : 0;
|
|
217
|
+
const budget = Number.isFinite(budgetValue) ? Math.max(0, budgetValue) : 0;
|
|
218
|
+
const beijingNow = new Date(nowMs + BEIJING_OFFSET_MS);
|
|
219
|
+
const year = beijingNow.getUTCFullYear();
|
|
220
|
+
const month = beijingNow.getUTCMonth();
|
|
221
|
+
const start = Date.UTC(year, month, 1) - BEIJING_OFFSET_MS;
|
|
222
|
+
const end = Date.UTC(year, month + 1, 1) - BEIJING_OFFSET_MS;
|
|
223
|
+
const elapsedRatio = Math.min(1, Math.max(1 / (end - start), (nowMs - start) / (end - start)));
|
|
224
|
+
const forecast = spent / elapsedRatio;
|
|
225
|
+
const ratio = budget > 0 ? spent / budget : 0;
|
|
226
|
+
const remaining = budget - spent;
|
|
227
|
+
const forecastOver = budget > 0 ? Math.max(0, forecast - budget) : 0;
|
|
228
|
+
const status = ratio >= 1 ? "over" : ratio >= 0.8 || forecastOver > 0 ? "risk" : "healthy";
|
|
229
|
+
return { spent, budget, ratio, remaining, forecast, forecastOver, status };
|
|
110
230
|
}
|
|
111
231
|
|
|
112
232
|
// src/client/charts.tsx
|
|
113
|
-
var
|
|
114
|
-
|
|
233
|
+
var import_react3 = require("react");
|
|
234
|
+
|
|
235
|
+
// src/client/chart-focus.ts
|
|
236
|
+
function nextChartFocus(current, count, key, horizontalStep = 1) {
|
|
237
|
+
if (count <= 0) return 0;
|
|
238
|
+
const index = Math.min(Math.max(0, current), count - 1);
|
|
239
|
+
if (key === "Home") return 0;
|
|
240
|
+
if (key === "End") return count - 1;
|
|
241
|
+
const step = key === "ArrowLeft" || key === "ArrowRight" ? Math.max(1, horizontalStep) : 1;
|
|
242
|
+
const direction = key === "ArrowLeft" || key === "ArrowUp" ? -1 : 1;
|
|
243
|
+
return Math.min(Math.max(0, index + direction * step), count - 1);
|
|
244
|
+
}
|
|
245
|
+
function lastPopulatedIndex(values) {
|
|
246
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
247
|
+
if (values[index] > 0) return index;
|
|
248
|
+
}
|
|
249
|
+
return Math.max(0, values.length - 1);
|
|
250
|
+
}
|
|
251
|
+
function nextPinnedIndex(current, tapped) {
|
|
252
|
+
return current === tapped ? null : tapped;
|
|
253
|
+
}
|
|
254
|
+
function isTapGesture(dx, dy, dt, moveThreshold = 10, timeThreshold = 500) {
|
|
255
|
+
return Math.hypot(dx, dy) <= moveThreshold && dt <= timeThreshold;
|
|
256
|
+
}
|
|
257
|
+
function stackedTotals(series, count) {
|
|
258
|
+
return Array.from({ length: count }, (_, i) => series.reduce((sum, s) => sum + (s.bars[i]?.value ?? 0), 0));
|
|
259
|
+
}
|
|
260
|
+
function maxOrOne(values) {
|
|
261
|
+
let max = 0;
|
|
262
|
+
for (const v of values) if (v > max) max = v;
|
|
263
|
+
return max > 0 ? max : 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/client/i18n.tsx
|
|
267
|
+
var import_react2 = require("react");
|
|
268
|
+
|
|
269
|
+
// src/client/locales.ts
|
|
270
|
+
var NS = "dsh-usage-dashboard";
|
|
271
|
+
var zh = {
|
|
272
|
+
"meta.locale": "zh",
|
|
273
|
+
"nav.quota": "\u989D\u5EA6",
|
|
274
|
+
"widget.title": "DeepSeek \u989D\u5EA6",
|
|
275
|
+
"common.unknownModel": "\u672A\u77E5\u6A21\u578B",
|
|
276
|
+
"common.calls": "{count} \u6B21\u8C03\u7528",
|
|
277
|
+
"common.callsShort": "{count} \u6B21",
|
|
278
|
+
"common.sessions": "{count} \u4E2A\u4F1A\u8BDD",
|
|
279
|
+
"common.records": "{count} \u6761",
|
|
280
|
+
"common.rows": "{count} \u884C",
|
|
281
|
+
"common.input": "\u8F93\u5165",
|
|
282
|
+
"common.output": "\u8F93\u51FA",
|
|
283
|
+
"common.cacheHit": "\u7F13\u5B58\u547D\u4E2D",
|
|
284
|
+
"common.status": "\u72B6\u6001",
|
|
285
|
+
"common.available": "\u53EF\u7528",
|
|
286
|
+
"common.unavailable": "\u4E0D\u53EF\u7528",
|
|
287
|
+
"common.justNow": "\u521A\u521A",
|
|
288
|
+
"common.minutesAgo": "{count} \u5206\u949F\u524D",
|
|
289
|
+
"common.hoursAgo": "{count} \u5C0F\u65F6\u524D",
|
|
290
|
+
"common.daysAgo": "{count} \u5929\u524D",
|
|
291
|
+
"common.listSeparator": "\u3001",
|
|
292
|
+
"common.period": "\u3002",
|
|
293
|
+
"delta.noBaselineTitle": "{label}\u6CA1\u6709\u7528\u91CF\uFF0C\u65E0\u4ECE\u5BF9\u6BD4",
|
|
294
|
+
"delta.noBaseline": "\u65E0\u5BF9\u6BD4",
|
|
295
|
+
"delta.flat": "\u6301\u5E73",
|
|
296
|
+
"period.volume": "{tokens} tokens \xB7 {calls}",
|
|
297
|
+
"budget.unset": "\u8FD8\u6CA1\u8BBE\u7F6E\u6708\u5EA6\u9884\u7B97\uFF0C\u65E0\u6CD5\u63D0\u524D\u5224\u65AD\u662F\u5426\u4F1A\u8D85\u652F\u3002",
|
|
298
|
+
"budget.configure": "\u8BBE\u7F6E\u9884\u7B97",
|
|
299
|
+
"budget.over": "\u5DF2\u8D85\u9884\u7B97 \xA5{amount}",
|
|
300
|
+
"budget.forecastOver": "\u7167\u5F53\u524D\u901F\u5EA6\uFF0C\u9884\u8BA1\u8D85\u51FA \xA5{amount}",
|
|
301
|
+
"budget.forecast": "\u7167\u5F53\u524D\u901F\u5EA6\uFF0C\u9884\u8BA1\u6708\u5E95 \xA5{amount}",
|
|
302
|
+
"budget.title": "\u672C\u6708\u9884\u7B97",
|
|
303
|
+
"budget.progressLabel": "\u672C\u6708\u9884\u7B97\u4F7F\u7528\u8FDB\u5EA6",
|
|
304
|
+
"budget.used": "\u5DF2\u4F7F\u7528 {percent}%\uFF0C{forecast}",
|
|
305
|
+
"budget.remaining": "\u5269\u4F59 \xA5{amount}",
|
|
306
|
+
"budget.exceeded": "\u8D85\u51FA \xA5{amount}",
|
|
307
|
+
"pricing.title": "\u8BA1\u4EF7\u8BF4\u660E",
|
|
308
|
+
"pricing.currentPeak": "\u5F53\u524D \u9AD8\u5CF0\u65F6\u6BB5",
|
|
309
|
+
"pricing.currentOffPeak": "\u5F53\u524D \u95F2\u65F6",
|
|
310
|
+
"pricing.model": "\u6A21\u578B",
|
|
311
|
+
"pricing.period": "\u65F6\u6BB5",
|
|
312
|
+
"pricing.unitPrice": "\u5355\u4EF7",
|
|
313
|
+
"pricing.inputCacheHit": "\u8F93\u5165\xB7\u7F13\u5B58\u547D\u4E2D",
|
|
314
|
+
"pricing.inputCacheMiss": "\u8F93\u5165\xB7\u672A\u547D\u4E2D",
|
|
315
|
+
"pricing.output": "\u8F93\u51FA",
|
|
316
|
+
"pricing.peak": "\u9AD8\u5CF0",
|
|
317
|
+
"pricing.fixed": "\u56FA\u5B9A",
|
|
318
|
+
"pricing.offPeak": "\u95F2\u65F6",
|
|
319
|
+
"pricing.unit": "\u5355\u4F4D\uFF1A{currency} / \u767E\u4E07 tokens\u3002",
|
|
320
|
+
"pricing.splitNote": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF0C\u5176\u4F59\u4E3A\u95F2\u65F6\uFF08\u4EF7\u683C\u51CF\u534A\uFF09\uFF1B{date} \u4E4B\u524D\u7684\u7528\u91CF\u4ECD\u6309\u65E7\u4EF7\u4F30\u7B97\u3002",
|
|
321
|
+
"pricing.switchNote": "{date} 00:00 \u8D77\u6539\u4E3A\u5CF0\u8C37\u5B9A\u4EF7\uFF08\u9AD8\u5CF0 {windows}\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\uFF09\uFF0C\u5C4A\u65F6\u672C\u9875\u4F1A\u6309\u6BCF\u6761\u8BB0\u5F55\u7684\u65F6\u95F4\u81EA\u52A8\u5206\u6BB5\u8BA1\u4EF7\u3002",
|
|
322
|
+
"pricing.unknown": "\u672A\u77E5\u6A21\u578B\u6309 deepseek-v4-pro \u8BA1\u4EF7\u3002",
|
|
323
|
+
"coverage.noRange": "\u6682\u65E0\u6709\u6548\u7528\u91CF\u65F6\u95F4",
|
|
324
|
+
"coverage.title": "\u7EDF\u8BA1\u8303\u56F4",
|
|
325
|
+
"coverage.gaps": "\u5B58\u5728\u7F3A\u53E3",
|
|
326
|
+
"coverage.complete": "\u672C\u673A\u8BFB\u53D6\u5B8C\u6574",
|
|
327
|
+
"coverage.brief": "\u626B\u63CF {scanned} / {listed} \u4E2A\u4F1A\u8BDD \xB7 {records} \u6761\u7528\u91CF\u8BB0\u5F55",
|
|
328
|
+
"coverage.scanned": "\u6210\u529F\u626B\u63CF",
|
|
329
|
+
"coverage.failed": "\u8BFB\u53D6\u5931\u8D25",
|
|
330
|
+
"coverage.skipped": "\u8DF3\u8FC7\u8BB0\u5F55",
|
|
331
|
+
"coverage.time": "\u8BB0\u5F55\u65F6\u95F4\uFF1A{range}",
|
|
332
|
+
"coverage.scope": "\u8FD9\u91CC\u53EA\u7EDF\u8BA1\u5F53\u524D\u8BBE\u5907\u4E0A\u7684 DSH \u4F1A\u8BDD\u65E5\u5FD7\uFF0C\u4E0D\u5305\u542B\u5176\u4ED6\u8BBE\u5907\u3001DeepSeek \u5E73\u53F0\u76F4\u63A5\u8C03\u7528\u6216\u5DF2\u5220\u9664\u7684\u672C\u5730\u65E5\u5FD7\u3002",
|
|
333
|
+
"coverage.warning": "\u672C\u6B21\u56DE\u653E\u6709\u65E5\u5FD7\u65E0\u6CD5\u8BFB\u53D6\u6216\u7528\u91CF\u8BB0\u5F55\u683C\u5F0F\u5F02\u5E38\uFF0C\u9875\u9762\u6C47\u603B\u53EF\u80FD\u4F4E\u4E8E\u5B9E\u9645\u6D88\u8017\uFF1B\u53EF\u5237\u65B0\u91CD\u8BD5\uFF0C\u5E76\u4EE5 DeepSeek \u5B98\u65B9\u5E73\u53F0\u8D26\u5355\u4E3A\u51C6\u3002",
|
|
334
|
+
"export.title": "\u5BFC\u51FA\u5168\u90E8\u672C\u673A DSH \u4F1A\u8BDD\u65E5\u5FD7\u6570\u636E\uFF08\u4E0D\u53D7\u7EDF\u8BA1\u5468\u671F\u5F71\u54CD\uFF09",
|
|
335
|
+
"export.done": "\u5DF2\u5BFC\u51FA",
|
|
336
|
+
"export.action": "\u5BFC\u51FA",
|
|
337
|
+
"export.menuLabel": "\u9009\u62E9\u5BFC\u51FA\u683C\u5F0F",
|
|
338
|
+
"export.dailyCsv": "\u9010\u5929 CSV",
|
|
339
|
+
"export.modelCsv": "\u9010\u6A21\u578B CSV",
|
|
340
|
+
"export.fullJson": "\u5B8C\u6574 JSON",
|
|
341
|
+
"export.jsonHint": "\u542B\u56FE\u8868\u4E0E\u6392\u884C",
|
|
342
|
+
"cache.empty": "\u8FD8\u6CA1\u6709 prompt token \u53EF\u7EDF\u8BA1\u7F13\u5B58\u547D\u4E2D\u3002",
|
|
343
|
+
"cache.rate": "\u7F13\u5B58\u547D\u4E2D\u7387",
|
|
344
|
+
"cache.saved": "\u5DF2\u8282\u7701\u8D39\u7528\uFF08\u4F30\u7B97\uFF09",
|
|
345
|
+
"cache.allMiss": "\u82E5\u5168\u90E8\u672A\u547D\u4E2D",
|
|
346
|
+
"cache.aria": "\u7F13\u5B58\u547D\u4E2D {hit}%\uFF0C\u672A\u547D\u4E2D {miss}%",
|
|
347
|
+
"cache.hitLegend": "\u7F13\u5B58\u547D\u4E2D {tokens}",
|
|
348
|
+
"cache.missLegend": "\u672A\u547D\u4E2D {tokens}\uFF08\u6309\u672A\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF09",
|
|
349
|
+
"cache.lowHint": "\u547D\u4E2D\u7387\u504F\u4F4E\uFF1A\u9891\u7E41\u6539\u52A8 system prompt / \u5DE5\u5177\u5B9A\u4E49\u4F1A\u8BA9\u524D\u7F00\u7F13\u5B58\u5931\u6548\uFF0C\u628A\u7A33\u5B9A\u5185\u5BB9\u653E\u5728\u5BF9\u8BDD\u6700\u524D\u9762\u80FD\u63D0\u9AD8\u547D\u4E2D\u7387\u3002",
|
|
350
|
+
"cache.goodHint": "\u524D\u7F00\u7F13\u5B58\u628A\u91CD\u590D\u7684 prompt \u524D\u7F00\u6309\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF0C\u662F\u8FD9\u4EFD\u8D26\u5355\u4E0A\u6700\u5927\u7684\u7701\u94B1\u6760\u6746\u3002",
|
|
351
|
+
"peak.empty": "\u8FD8\u6CA1\u6709\u53EF\u6309\u65F6\u6BB5\u5F52\u7C7B\u7684\u7528\u91CF\u3002",
|
|
352
|
+
"peak.aria": "\u9AD8\u5CF0\u65F6\u6BB5 {peak}%\uFF0C\u95F2\u65F6 {offPeak}%",
|
|
353
|
+
"peak.peakLegend": "\u9AD8\u5CF0 {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
354
|
+
"peak.offPeakLegend": "\u95F2\u65F6 {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
355
|
+
"peak.newPrice": "\u540C\u6837\u7528\u91CF\u5728\u65B0\u4EF7\u4E0B",
|
|
356
|
+
"peak.increase": "\u8F83\u73B0\u4EF7 +{percent}%",
|
|
357
|
+
"peak.shift": "\u82E5\u9AD8\u5CF0\u7528\u91CF\u90FD\u632A\u5230\u95F2\u65F6",
|
|
358
|
+
"peak.saving": "\u53EF\u7701 \xA5{amount}",
|
|
359
|
+
"peak.schedule": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\u3002",
|
|
360
|
+
"peak.activeHint": "\u628A\u6279\u91CF\u3001\u53EF\u5EF6\u540E\u7684\u4EFB\u52A1\u653E\u5230\u95F2\u65F6\u8DD1\uFF0C\u540C\u6837\u7684 token \u53EA\u8981\u4E00\u534A\u7684\u94B1\u3002",
|
|
361
|
+
"peak.futureHint": "\u65B0\u4EF7 {date} 00:00 \u751F\u6548\uFF1B\u4E0A\u9762\u4E24\u4E2A\u6570\u5B57\u662F\u6309\u4F60\u5DF2\u6709\u7684\u5168\u90E8\u7528\u91CF\u91CD\u7B97\u7684\u3002",
|
|
362
|
+
"sessions.empty": "\u8FD8\u6CA1\u6709\u4EA7\u751F\u8D39\u7528\u7684\u4F1A\u8BDD\u3002",
|
|
363
|
+
"sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
|
|
364
|
+
"sessions.share": "\u5360 {percent}%",
|
|
365
|
+
"sessions.more": "\u5171 {count} \u4E2A\u4F1A\u8BDD\u6709\u7528\u91CF\uFF0C\u4E0A\u9762\u662F\u6700\u8D35\u7684 {shown} \u4E2A\u3002",
|
|
366
|
+
"sessions.fallbackTitle": "\u4F1A\u8BDD {id}",
|
|
367
|
+
"models.empty": "\u8FD8\u6CA1\u6709\u53EF\u5F52\u7C7B\u7684\u6A21\u578B\u7528\u91CF\u3002",
|
|
368
|
+
"models.shareAria": "{name} \u5360\u603B\u8D39\u7528 {percent}%",
|
|
369
|
+
"models.detail": "{tokens} tokens \xB7 {calls} \xB7 \u8F93\u5165 {input} / \u8F93\u51FA {output} / \u7F13\u5B58 {cache}",
|
|
370
|
+
"models.all": "\u5168\u90E8\u6A21\u578B",
|
|
371
|
+
"models.selected": "\u6A21\u578B \xD7{count}",
|
|
372
|
+
"models.filterTitle": "\u7B5B\u9009\u56FE\u8868\u4E2D\u663E\u793A\u7684\u6A21\u578B",
|
|
373
|
+
"models.filterLabel": "\u7B5B\u9009\u56FE\u8868\u6A21\u578B",
|
|
374
|
+
"window.year": "\u8FD1 1 \u5E74",
|
|
375
|
+
"window.days": "\u8FD1 {days} \u5929",
|
|
376
|
+
"window.groupLabel": "\u7EDF\u8BA1\u5468\u671F",
|
|
377
|
+
"window.viewTitle": "\u67E5\u770B{window}\u7684\u7528\u91CF\u4E0E\u6392\u884C",
|
|
378
|
+
"window.yearButton": "1 \u5E74",
|
|
379
|
+
"window.daysButton": "{days} \u5929",
|
|
380
|
+
"metric.tokens": "tokens",
|
|
381
|
+
"metric.usage": "\u7528\u91CF",
|
|
382
|
+
"metric.cost": "\u8D39\u7528",
|
|
383
|
+
"metric.calls": "\u8C03\u7528",
|
|
384
|
+
"metric.groupLabel": "\u56FE\u8868\u6307\u6807",
|
|
385
|
+
"metric.compareTitle": "\u6309{metric}\u6BD4\u8F83",
|
|
386
|
+
"chart.dailyTitle": "{window} \xB7 \u9010\u5929{metric}",
|
|
387
|
+
"chart.hourlyTitle": "{window} \xB7 {metric}\u6309\u5C0F\u65F6\u5206\u5E03\uFF080\u201323 \u70B9\uFF09",
|
|
388
|
+
"chart.peak": "\u5CF0\u503C {value}",
|
|
389
|
+
"chart.dimensionLabel": "\u5207\u6362\u56FE\u8868\u7EF4\u5EA6",
|
|
390
|
+
"chart.daily": "\u9010\u5929",
|
|
391
|
+
"chart.hourly": "\u9010\u5C0F\u65F6",
|
|
392
|
+
"chart.noWindowData": "{window}\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\uFF0C\u6362\u4E2A\u66F4\u957F\u5468\u671F\u770B\u770B\u3002",
|
|
393
|
+
"chart.heatmapTitle": "\u8FD1\u4E00\u5E74 \xB7 \u6BCF\u65E5\u7528\u91CF\u70ED\u529B\u56FE",
|
|
394
|
+
"chart.empty": "\u8FD8\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u3002\u5728 DSH \u91CC\u8DD1\u4E00\u8F6E\u5BF9\u8BDD\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u9010\u5929 / \u9010\u5C0F\u65F6\u7EDF\u8BA1\u4E0E\u8D39\u7528\u4F30\u7B97\u3002",
|
|
395
|
+
"chart.hourPoint": "{hour} \u70B9",
|
|
396
|
+
"chart.tooltip": "{head} \xB7 {tokens} tokens \xB7 \xA5{cost} \xB7 {calls}",
|
|
397
|
+
"chart.less": "\u5C11",
|
|
398
|
+
"chart.more": "\u591A",
|
|
399
|
+
"chart.month1": "1\u6708",
|
|
400
|
+
"chart.month2": "2\u6708",
|
|
401
|
+
"chart.month3": "3\u6708",
|
|
402
|
+
"chart.month4": "4\u6708",
|
|
403
|
+
"chart.month5": "5\u6708",
|
|
404
|
+
"chart.month6": "6\u6708",
|
|
405
|
+
"chart.month7": "7\u6708",
|
|
406
|
+
"chart.month8": "8\u6708",
|
|
407
|
+
"chart.month9": "9\u6708",
|
|
408
|
+
"chart.month10": "10\u6708",
|
|
409
|
+
"chart.month11": "11\u6708",
|
|
410
|
+
"chart.month12": "12\u6708",
|
|
411
|
+
"number.tenThousand": "{value}\u4E07",
|
|
412
|
+
"number.hundredMillion": "{value}\u4EBF",
|
|
413
|
+
"fresh.none": "\u5C1A\u65E0\u6210\u529F\u8BB0\u5F55",
|
|
414
|
+
"fresh.justNow": "\u521A\u521A\u66F4\u65B0",
|
|
415
|
+
"fresh.minutes": "\u66F4\u65B0\u4E8E {count} \u5206\u949F\u524D",
|
|
416
|
+
"fresh.hours": "\u66F4\u65B0\u4E8E {count} \u5C0F\u65F6\u524D",
|
|
417
|
+
"fresh.days": "\u66F4\u65B0\u4E8E {count} \u5929\u524D",
|
|
418
|
+
"sync.first": "\u9996\u6B21\u540C\u6B65\u4E2D",
|
|
419
|
+
"sync.syncing": "\u540C\u6B65\u4E2D \xB7 \u5F53\u524D\u6570\u636E{age}",
|
|
420
|
+
"sync.fresh": "\u5DF2\u540C\u6B65 \xB7 {age}",
|
|
421
|
+
"sync.cached": "\u7F13\u5B58\u6570\u636E \xB7 {age}",
|
|
422
|
+
"sync.fallback": "\u7F13\u5B58\u56DE\u9000 \xB7 {age}",
|
|
423
|
+
"sync.error": "\u540C\u6B65\u5931\u8D25 \xB7 \u6682\u65E0\u53EF\u663E\u793A\u7684\u7528\u91CF",
|
|
424
|
+
"status.neverSynced": "\u8FD8\u6CA1\u6709\u6210\u529F\u540C\u6B65\u8FC7\u7528\u91CF\u6570\u636E",
|
|
425
|
+
"status.usageTime": "\u7528\u91CF\u6570\u636E\u65F6\u95F4\uFF1A{time}",
|
|
426
|
+
"status.forceRefresh": "\u5F3A\u5236\u5237\u65B0\uFF08\u7ED5\u8FC7\u7F13\u5B58\uFF09",
|
|
427
|
+
"status.refreshing": "\u5237\u65B0\u4E2D",
|
|
428
|
+
"status.refresh": "\u5237\u65B0",
|
|
429
|
+
"error.balanceLoad": "\u4F59\u989D\u52A0\u8F7D\u5931\u8D25",
|
|
430
|
+
"error.usageLoad": "\u7528\u91CF\u52A0\u8F7D\u5931\u8D25",
|
|
431
|
+
"error.refreshCached": "\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u5C55\u793A\u7F13\u5B58\u6570\u636E\uFF08{error}\uFF09",
|
|
432
|
+
"error.balanceRefreshCached": "\u4F59\u989D\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u5C55\u793A\u7F13\u5B58\u4F59\u989D\uFF08{error}\uFF09",
|
|
433
|
+
"error.usageRefreshCached": "\u7528\u91CF\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u5C55\u793A\u7F13\u5B58\u7528\u91CF\uFF08{error}\uFF09",
|
|
434
|
+
"error.query": "\u67E5\u8BE2\u5931\u8D25",
|
|
435
|
+
"error.credentialsUnavailable": "\u51ED\u8BC1\u670D\u52A1\u4E0D\u53EF\u7528",
|
|
436
|
+
"error.credentialRead": "\u8BFB\u53D6 API Key \u5931\u8D25\uFF1A{detail}",
|
|
437
|
+
"error.noApiKey": "\u672A\u914D\u7F6E DEEPSEEK_API_KEY\uFF08\u53EF\u5728\u300C\u8BBE\u7F6E \u2192 \u6A21\u578B\u300D\u4E2D\u586B\u5199\uFF09",
|
|
438
|
+
"error.balanceRequest": "\u8BF7\u6C42\u4F59\u989D\u63A5\u53E3\u5931\u8D25\uFF1A{detail}",
|
|
439
|
+
"error.balanceHttp": "\u4F59\u989D\u63A5\u53E3\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP {status}\uFF09",
|
|
440
|
+
"error.balanceParse": "\u89E3\u6790\u4F59\u989D\u54CD\u5E94\u5931\u8D25",
|
|
441
|
+
"error.persistenceUnavailable": "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528",
|
|
442
|
+
"error.sessionList": "\u8BFB\u53D6\u4F1A\u8BDD\u5217\u8868\u5931\u8D25\uFF1A{detail}",
|
|
443
|
+
"error.sessionLogRead": "\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A{detail}",
|
|
444
|
+
"error.missingSessionId": "\u7F3A\u5C11\u4F1A\u8BDD id",
|
|
445
|
+
"alert.lowTitle": "\u4F59\u989D {balance} {currency} \u5DF2\u4F4E\u4E8E\u9884\u8B66\u7EBF {threshold}",
|
|
446
|
+
"alert.runway": "\uFF0C\u6309\u8FD1\u671F\u7528\u91CF\u4F30\u8BA1\u8FD8\u80FD\u6491 {days}",
|
|
447
|
+
"alert.topUp": "\u53BB\u5145\u503C",
|
|
448
|
+
"balance.title": "\u8D26\u6237\u4F59\u989D",
|
|
449
|
+
"balance.remaining": "\u5269\u4F59\u4F59\u989D",
|
|
450
|
+
"balance.breakdownTitle": "\u67E5\u770B\u5145\u503C\u4E0E\u8D60\u9001\u989D\u5EA6\u660E\u7EC6",
|
|
451
|
+
"balance.toppedUp": "\u5145\u503C\u989D\u5EA6 {amount}",
|
|
452
|
+
"balance.granted": "\u8D60\u9001\u989D\u5EA6 {amount}",
|
|
453
|
+
"balance.runway": "\u9884\u8BA1\u53EF\u7528",
|
|
454
|
+
"balance.runwayBreakdownTitle": "\u67E5\u770B\u9884\u8BA1\u53EF\u7528\u5929\u6570\u8BF4\u660E",
|
|
455
|
+
"balance.days": "{count} \u5929",
|
|
456
|
+
"balance.overYear": "> 365 \u5929",
|
|
457
|
+
"balance.noRunway": "\u8FD1 {days} \u5929\u6CA1\u6709\u7528\u91CF\uFF0C\u65E0\u6CD5\u4F30\u7B97",
|
|
458
|
+
"balance.runwayTitle": "\u6309\u8FD1 {days} \u5929\u65E5\u5747 \xA5{cost} \u4F30\u7B97\uFF08\u542B\u65E0\u7528\u91CF\u7684\u65E5\u5B50\uFF1B\u4ECA\u5929\u5C1A\u672A\u8FC7\u5B8C\uFF09",
|
|
459
|
+
"balance.empty": "\u8BFB\u4E0D\u5230\u4F59\u989D\u3002\u8BF7\u786E\u8BA4\u300C\u8BBE\u7F6E \u2192 \u6A21\u578B\u300D\u91CC\u5DF2\u586B DEEPSEEK_API_KEY\uFF0C\u7136\u540E\u70B9\u53F3\u4E0A\u89D2\u5237\u65B0\u3002",
|
|
460
|
+
"sessionUsage.title": "\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017\uFF08\u8D39\u7528\u4E3A\u4F30\u7B97\uFF09",
|
|
461
|
+
"sessionUsage.cost": "\u672C\u4F1A\u8BDD\u8D39\u7528",
|
|
462
|
+
"sessionUsage.tokens": "\u672C\u4F1A\u8BDD tokens",
|
|
463
|
+
"sessionUsage.calls": "\u672C\u4F1A\u8BDD\u8C03\u7528",
|
|
464
|
+
"sessionUsage.range": "\u6D3B\u8DC3\u65F6\u95F4 {range}",
|
|
465
|
+
"sessionUsage.empty": "\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u4EA7\u751F\u7528\u91CF\u3002\u53D1\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u8FD9\u4E2A\u4F1A\u8BDD\u4E13\u5C5E\u7684\u8D39\u7528\u4E0E\u6A21\u578B\u660E\u7EC6\u3002",
|
|
466
|
+
"overview.title": "\u6D88\u8017\u6982\u89C8\uFF08\u8D39\u7528\u4E3A\u4F30\u7B97\uFF09",
|
|
467
|
+
"overview.today": "\u4ECA\u65E5",
|
|
468
|
+
"overview.month": "\u672C\u6708",
|
|
469
|
+
"overview.total": "\u7D2F\u8BA1",
|
|
470
|
+
"overview.vsYesterday": "\u8F83\u6628\u65E5",
|
|
471
|
+
"overview.vsLastMonth": "\u8F83\u4E0A\u6708\u540C\u671F",
|
|
472
|
+
"overview.empty": "\u8FD8\u6CA1\u6709\u53EF\u7EDF\u8BA1\u7684\u6D88\u8017\u3002",
|
|
473
|
+
"usage.trendTitle": "\u7528\u91CF\u8D8B\u52BF\uFF08tokens\uFF09",
|
|
474
|
+
"usage.windowInput": "{window}\u8F93\u5165",
|
|
475
|
+
"usage.modelCalls": "\u6A21\u578B\u8C03\u7528",
|
|
476
|
+
"heatmap.title": "\u7528\u91CF\u70ED\u529B\u56FE",
|
|
477
|
+
"peak.title": "\u9AD8\u5CF0 / \u95F2\u65F6\u5206\u5E03",
|
|
478
|
+
"cache.title": "\u7F13\u5B58\u547D\u4E2D\u4E0E\u8282\u7701",
|
|
479
|
+
"models.title": "\u6A21\u578B\u6210\u672C\u6392\u884C \xB7 {window}",
|
|
480
|
+
"sessions.title": "\u4F1A\u8BDD\u6210\u672C\u6392\u884C \xB7 {window}",
|
|
481
|
+
"links.title": "\u5FEB\u6377\u5165\u53E3",
|
|
482
|
+
"links.usage": "\u67E5\u770B\u989D\u5EA6 / \u7528\u91CF",
|
|
483
|
+
"links.apiKey": "\u751F\u6210 API Key",
|
|
484
|
+
"links.status": "\u670D\u52A1\u72B6\u6001",
|
|
485
|
+
"settings.title": "\u8BBE\u7F6E",
|
|
486
|
+
"settings.widget": "\u5728\u5176\u4ED6\u9875\u9762\u663E\u793A\u53F3\u4E0B\u89D2\u60AC\u6D6E\u989D\u5EA6\u7A97\u53E3",
|
|
487
|
+
"settings.widgetHint": "\u5F53\u524D\u989D\u5EA6\u9875\u5DF2\u5C55\u793A\u5B8C\u6574\u6570\u636E\uFF0C\u60AC\u6D6E\u7A97\u4F1A\u81EA\u52A8\u9690\u85CF\uFF1B\u5207\u56DE Chat \u6216 Trajectory \u540E\u6062\u590D\u3002",
|
|
488
|
+
"settings.lowBalance": "\u4F59\u989D\u9884\u8B66\u7EBF",
|
|
489
|
+
"settings.lowBalanceOn": "\u4F59\u989D\u4F4E\u4E8E {amount} {currency} \u65F6\uFF0C\u8FD9\u91CC\u548C\u60AC\u6D6E\u7A97\u90FD\u4F1A\u8F6C\u4E3A\u8B66\u793A\u8272\u3002\u586B 0 \u5173\u95ED\u3002",
|
|
490
|
+
"settings.lowBalanceOff": "\u5DF2\u5173\u95ED\u4F59\u989D\u9884\u8B66\u3002\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u6570\u5F00\u542F\u3002",
|
|
491
|
+
"settings.monthlyBudget": "\u6708\u5EA6\u9884\u7B97",
|
|
492
|
+
"settings.monthlyBudgetOn": "\u6309\u5317\u4EAC\u65F6\u95F4\u81EA\u7136\u6708\u8DDF\u8E2A {amount} CNY \u9884\u7B97\uFF0C\u5E76\u5728\u6D88\u8017\u6982\u89C8\u9884\u6D4B\u6708\u5E95\u82B1\u8D39\u3002\u586B 0 \u5173\u95ED\u3002",
|
|
493
|
+
"settings.monthlyBudgetOff": "\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u91D1\u989D\uFF0C\u6D88\u8017\u6982\u89C8\u4F1A\u663E\u793A\u8FDB\u5EA6\u4E0E\u6708\u5E95\u9884\u6D4B\u3002",
|
|
494
|
+
"widget.updatedNow": "\u521A\u521A\u66F4\u65B0",
|
|
495
|
+
"widget.stale": "{minutes} \u5206\u949F\u524D\u7684\u6570\u636E\uFF0C\u70B9 \u21BB \u66F4\u65B0",
|
|
496
|
+
"widget.loading": "\u67E5\u8BE2\u4E2D\u2026",
|
|
497
|
+
"widget.lowTitle": "\u4F59\u989D\u4F4E\u4E8E\u9884\u8B66\u7EBF {amount}",
|
|
498
|
+
"widget.todayCostTitle": "\u4ECA\u65E5\u6D88\u8017\uFF08\u4F30\u7B97\uFF09\xB7 {age}",
|
|
499
|
+
"widget.todayCost": "\u4ECA\u65E5\u6D88\u8017",
|
|
500
|
+
"widget.noBalance": "\u6682\u65E0\u4F59\u989D\u6570\u636E",
|
|
501
|
+
"widget.dragCard": "\u62D6\u52A8\u5230\u56DB\u4E2A\u89D2\u843D",
|
|
502
|
+
"widget.dragGrip": "\u62D6\u52A8\u4EE5\u79FB\u52A8",
|
|
503
|
+
"widget.refresh": "\u5237\u65B0\u4F59\u989D\u4E0E\u4ECA\u65E5\u7528\u91CF",
|
|
504
|
+
"widget.expand": "\u5C55\u5F00",
|
|
505
|
+
"widget.collapse": "\u6536\u8D77",
|
|
506
|
+
"widget.expandAria": "\u5C55\u5F00\u989D\u5EA6\u7A97\u53E3",
|
|
507
|
+
"widget.collapseAria": "\u6536\u8D77\u989D\u5EA6\u7A97\u53E3",
|
|
508
|
+
"boundary.title": "\u989D\u5EA6\u9762\u677F\u51FA\u9519\u4E86",
|
|
509
|
+
"boundary.body": "\u6E32\u67D3\u65F6\u629B\u4E86\u5F02\u5E38\uFF0C\u901A\u5E38\u662F\u6D4F\u89C8\u5668\u91CC\u5B58\u7740\u65E7\u7248\u672C\u7684\u7F13\u5B58\u6570\u636E\u3002\u6E05\u6389\u672C\u63D2\u4EF6\u7684\u672C\u5730\u6570\u636E\u518D\u91CD\u65B0\u52A0\u8F7D\u5C31\u80FD\u6062\u590D\u3002",
|
|
510
|
+
"boundary.reset": "\u6E05\u9664\u672C\u5730\u6570\u636E\u5E76\u91CD\u65B0\u52A0\u8F7D"
|
|
511
|
+
};
|
|
512
|
+
var en = {
|
|
513
|
+
"meta.locale": "en",
|
|
514
|
+
"nav.quota": "Usage",
|
|
515
|
+
"widget.title": "DeepSeek Usage",
|
|
516
|
+
"common.unknownModel": "Unknown model",
|
|
517
|
+
"common.calls": "{count} calls",
|
|
518
|
+
"common.callsShort": "{count} calls",
|
|
519
|
+
"common.sessions": "{count} sessions",
|
|
520
|
+
"common.records": "{count} records",
|
|
521
|
+
"common.rows": "{count} rows",
|
|
522
|
+
"common.input": "Input",
|
|
523
|
+
"common.output": "Output",
|
|
524
|
+
"common.cacheHit": "Cache hit",
|
|
525
|
+
"common.status": "Status",
|
|
526
|
+
"common.available": "Available",
|
|
527
|
+
"common.unavailable": "Unavailable",
|
|
528
|
+
"common.justNow": "Just now",
|
|
529
|
+
"common.minutesAgo": "{count}m ago",
|
|
530
|
+
"common.hoursAgo": "{count}h ago",
|
|
531
|
+
"common.daysAgo": "{count}d ago",
|
|
532
|
+
"common.listSeparator": ", ",
|
|
533
|
+
"common.period": ".",
|
|
534
|
+
"delta.noBaselineTitle": "No usage in {label}, so there is no baseline",
|
|
535
|
+
"delta.noBaseline": "No baseline",
|
|
536
|
+
"delta.flat": "No change",
|
|
537
|
+
"period.volume": "{tokens} tokens \xB7 {calls}",
|
|
538
|
+
"budget.unset": "Set a monthly budget to see whether current spending is on track.",
|
|
539
|
+
"budget.configure": "Set budget",
|
|
540
|
+
"budget.over": "Over budget by \xA5{amount}",
|
|
541
|
+
"budget.forecastOver": "At this pace, \xA5{amount} over by month-end",
|
|
542
|
+
"budget.forecast": "At this pace, \xA5{amount} by month-end",
|
|
543
|
+
"budget.title": "Monthly budget",
|
|
544
|
+
"budget.progressLabel": "Monthly budget usage",
|
|
545
|
+
"budget.used": "{percent}% used. {forecast}",
|
|
546
|
+
"budget.remaining": "\xA5{amount} remaining",
|
|
547
|
+
"budget.exceeded": "\xA5{amount} over",
|
|
548
|
+
"pricing.title": "Pricing details",
|
|
549
|
+
"pricing.currentPeak": "Peak now",
|
|
550
|
+
"pricing.currentOffPeak": "Off-peak now",
|
|
551
|
+
"pricing.model": "Model",
|
|
552
|
+
"pricing.period": "Period",
|
|
553
|
+
"pricing.unitPrice": "Rate",
|
|
554
|
+
"pricing.inputCacheHit": "Input \xB7 cache hit",
|
|
555
|
+
"pricing.inputCacheMiss": "Input \xB7 cache miss",
|
|
556
|
+
"pricing.output": "Output",
|
|
557
|
+
"pricing.peak": "Peak",
|
|
558
|
+
"pricing.fixed": "Fixed",
|
|
559
|
+
"pricing.offPeak": "Off-peak",
|
|
560
|
+
"pricing.unit": "Unit: {currency} / million tokens. ",
|
|
561
|
+
"pricing.splitNote": "Peak periods are {windows} Beijing time; off-peak rates are half price. Usage before {date} is still estimated at the old rates. ",
|
|
562
|
+
"pricing.switchNote": "Time-of-use pricing starts at 00:00 on {date} (peak: {windows}; off-peak is half price). This page will price each record by its timestamp. ",
|
|
563
|
+
"pricing.unknown": "Unknown models use deepseek-v4-pro rates.",
|
|
564
|
+
"coverage.noRange": "No valid usage timestamps",
|
|
565
|
+
"coverage.title": "Coverage",
|
|
566
|
+
"coverage.gaps": "Gaps found",
|
|
567
|
+
"coverage.complete": "Local logs complete",
|
|
568
|
+
"coverage.brief": "Scanned {scanned} / {listed} sessions \xB7 {records} usage records",
|
|
569
|
+
"coverage.scanned": "Scanned",
|
|
570
|
+
"coverage.failed": "Read failures",
|
|
571
|
+
"coverage.skipped": "Skipped records",
|
|
572
|
+
"coverage.time": "Record range: {range}",
|
|
573
|
+
"coverage.scope": "Only DSH session logs on this device are included. Other devices, direct DeepSeek Platform calls, and deleted local logs are excluded.",
|
|
574
|
+
"coverage.warning": "Some logs could not be read or contained invalid usage records, so totals may be lower than actual spend. Refresh to retry and use the DeepSeek Platform bill as the source of truth.",
|
|
575
|
+
"export.title": "Export all local DSH session-log data (independent of the selected range)",
|
|
576
|
+
"export.done": "Exported",
|
|
577
|
+
"export.action": "Export",
|
|
578
|
+
"export.menuLabel": "Choose export format",
|
|
579
|
+
"export.dailyCsv": "Daily CSV",
|
|
580
|
+
"export.modelCsv": "Model CSV",
|
|
581
|
+
"export.fullJson": "Full JSON",
|
|
582
|
+
"export.jsonHint": "Charts and rankings included",
|
|
583
|
+
"cache.empty": "No prompt tokens are available for cache analysis yet.",
|
|
584
|
+
"cache.rate": "Cache hit rate",
|
|
585
|
+
"cache.saved": "Estimated savings",
|
|
586
|
+
"cache.allMiss": "Cost with no hits",
|
|
587
|
+
"cache.aria": "{hit}% cache hits, {miss}% misses",
|
|
588
|
+
"cache.hitLegend": "Cache hits {tokens}",
|
|
589
|
+
"cache.missLegend": "Misses {tokens} (charged at miss rate)",
|
|
590
|
+
"cache.lowHint": "Low hit rate: frequent changes to the system prompt or tool definitions invalidate prefix caching. Put stable content first to improve reuse.",
|
|
591
|
+
"cache.goodHint": "Prefix caching charges repeated prompt prefixes at the hit rate, making it the largest saving lever on this bill.",
|
|
592
|
+
"peak.empty": "No usage can be classified by pricing period yet.",
|
|
593
|
+
"peak.aria": "{peak}% peak, {offPeak}% off-peak",
|
|
594
|
+
"peak.peakLegend": "Peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
595
|
+
"peak.offPeakLegend": "Off-peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
596
|
+
"peak.newPrice": "Same usage at new rates",
|
|
597
|
+
"peak.increase": "+{percent}% vs current rates",
|
|
598
|
+
"peak.shift": "If all peak usage moved off-peak",
|
|
599
|
+
"peak.saving": "Save \xA5{amount}",
|
|
600
|
+
"peak.schedule": "Peak periods are {windows} Beijing time; off-peak rates are half price. ",
|
|
601
|
+
"peak.activeHint": "Run batch or delay-tolerant work off-peak to pay half as much for the same tokens.",
|
|
602
|
+
"peak.futureHint": "New rates start at 00:00 on {date}; both figures recalculate all existing usage.",
|
|
603
|
+
"sessions.empty": "No sessions have incurred cost yet.",
|
|
604
|
+
"sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
|
|
605
|
+
"sessions.share": "{percent}% of total",
|
|
606
|
+
"sessions.more": "{count} sessions have usage; the {shown} most expensive are shown above.",
|
|
607
|
+
"sessions.fallbackTitle": "Session {id}",
|
|
608
|
+
"models.empty": "No model usage can be classified yet.",
|
|
609
|
+
"models.shareAria": "{name} accounts for {percent}% of total cost",
|
|
610
|
+
"models.detail": "{tokens} tokens \xB7 {calls} \xB7 input {input} / output {output} / cache {cache}",
|
|
611
|
+
"models.all": "All models",
|
|
612
|
+
"models.selected": "{count} models",
|
|
613
|
+
"models.filterTitle": "Filter models shown in the chart",
|
|
614
|
+
"models.filterLabel": "Filter chart models",
|
|
615
|
+
"window.year": "Last year",
|
|
616
|
+
"window.days": "Last {days} days",
|
|
617
|
+
"window.groupLabel": "Date range",
|
|
618
|
+
"window.viewTitle": "View usage and rankings for {window}",
|
|
619
|
+
"window.yearButton": "1 year",
|
|
620
|
+
"window.daysButton": "{days} days",
|
|
621
|
+
"metric.tokens": "tokens",
|
|
622
|
+
"metric.usage": "usage",
|
|
623
|
+
"metric.cost": "cost",
|
|
624
|
+
"metric.calls": "calls",
|
|
625
|
+
"metric.groupLabel": "Chart metric",
|
|
626
|
+
"metric.compareTitle": "Compare by {metric}",
|
|
627
|
+
"chart.dailyTitle": "{window} \xB7 daily {metric}",
|
|
628
|
+
"chart.hourlyTitle": "{window} \xB7 {metric} by hour (00\u201323)",
|
|
629
|
+
"chart.peak": "Peak {value}",
|
|
630
|
+
"chart.dimensionLabel": "Chart interval",
|
|
631
|
+
"chart.daily": "Daily",
|
|
632
|
+
"chart.hourly": "Hourly",
|
|
633
|
+
"chart.noWindowData": "No usage in {window}. Try a longer range.",
|
|
634
|
+
"chart.heatmapTitle": "Last year \xB7 daily usage heatmap",
|
|
635
|
+
"chart.empty": "No usage yet. Run a conversation in DSH to see daily and hourly totals with cost estimates.",
|
|
636
|
+
"chart.hourPoint": "{hour}:00",
|
|
637
|
+
"chart.tooltip": "{head} \xB7 {tokens} tokens \xB7 \xA5{cost} \xB7 {calls}",
|
|
638
|
+
"chart.less": "Less",
|
|
639
|
+
"chart.more": "More",
|
|
640
|
+
"chart.month1": "Jan",
|
|
641
|
+
"chart.month2": "Feb",
|
|
642
|
+
"chart.month3": "Mar",
|
|
643
|
+
"chart.month4": "Apr",
|
|
644
|
+
"chart.month5": "May",
|
|
645
|
+
"chart.month6": "Jun",
|
|
646
|
+
"chart.month7": "Jul",
|
|
647
|
+
"chart.month8": "Aug",
|
|
648
|
+
"chart.month9": "Sep",
|
|
649
|
+
"chart.month10": "Oct",
|
|
650
|
+
"chart.month11": "Nov",
|
|
651
|
+
"chart.month12": "Dec",
|
|
652
|
+
"number.tenThousand": "{value}0K",
|
|
653
|
+
"number.hundredMillion": "{value}00M",
|
|
654
|
+
"fresh.none": "No successful sync yet",
|
|
655
|
+
"fresh.justNow": "Updated just now",
|
|
656
|
+
"fresh.minutes": "Updated {count}m ago",
|
|
657
|
+
"fresh.hours": "Updated {count}h ago",
|
|
658
|
+
"fresh.days": "Updated {count}d ago",
|
|
659
|
+
"sync.first": "Syncing for the first time",
|
|
660
|
+
"sync.syncing": "Syncing \xB7 current data {age}",
|
|
661
|
+
"sync.fresh": "Synced \xB7 {age}",
|
|
662
|
+
"sync.cached": "Cached data \xB7 {age}",
|
|
663
|
+
"sync.fallback": "Using cache \xB7 {age}",
|
|
664
|
+
"sync.error": "Sync failed \xB7 no usage to display",
|
|
665
|
+
"status.neverSynced": "Usage has not synced successfully yet",
|
|
666
|
+
"status.usageTime": "Usage data timestamp: {time}",
|
|
667
|
+
"status.forceRefresh": "Force refresh (bypass cache)",
|
|
668
|
+
"status.refreshing": "Refreshing",
|
|
669
|
+
"status.refresh": "Refresh",
|
|
670
|
+
"error.balanceLoad": "Balance failed to load",
|
|
671
|
+
"error.usageLoad": "Usage failed to load",
|
|
672
|
+
"error.refreshCached": "Refresh failed; showing cached data ({error})",
|
|
673
|
+
"error.balanceRefreshCached": "Balance refresh failed; showing cached balance ({error})",
|
|
674
|
+
"error.usageRefreshCached": "Usage refresh failed; showing cached usage ({error})",
|
|
675
|
+
"error.query": "Request failed",
|
|
676
|
+
"error.credentialsUnavailable": "Credential service is unavailable",
|
|
677
|
+
"error.credentialRead": "Could not read the API key: {detail}",
|
|
678
|
+
"error.noApiKey": "DEEPSEEK_API_KEY is not configured (add it under Settings \u2192 Models)",
|
|
679
|
+
"error.balanceRequest": "Balance request failed: {detail}",
|
|
680
|
+
"error.balanceHttp": "Balance endpoint returned HTTP {status}",
|
|
681
|
+
"error.balanceParse": "Could not parse the balance response",
|
|
682
|
+
"error.persistenceUnavailable": "Session persistence is unavailable",
|
|
683
|
+
"error.sessionList": "Could not read the session list: {detail}",
|
|
684
|
+
"error.sessionLogRead": "Could not read the session log: {detail}",
|
|
685
|
+
"error.missingSessionId": "Missing session id",
|
|
686
|
+
"alert.lowTitle": "Balance {balance} {currency} is below the {threshold} warning threshold",
|
|
687
|
+
"alert.runway": "; about {days} left at recent usage",
|
|
688
|
+
"alert.topUp": "Top up",
|
|
689
|
+
"balance.title": "Account balance",
|
|
690
|
+
"balance.remaining": "Remaining balance",
|
|
691
|
+
"balance.breakdownTitle": "View topped-up and granted balance details",
|
|
692
|
+
"balance.toppedUp": "Topped up {amount}",
|
|
693
|
+
"balance.granted": "Granted {amount}",
|
|
694
|
+
"balance.runway": "Estimated runway",
|
|
695
|
+
"balance.runwayBreakdownTitle": "View estimated runway details",
|
|
696
|
+
"balance.days": "{count} days",
|
|
697
|
+
"balance.overYear": "> 365 days",
|
|
698
|
+
"balance.noRunway": "No usage in the last {days} days, so runway cannot be estimated",
|
|
699
|
+
"balance.runwayTitle": "Based on \xA5{cost} average daily spend over the last {days} days (including idle days; today is incomplete)",
|
|
700
|
+
"balance.empty": "Balance is unavailable. Add DEEPSEEK_API_KEY under Settings \u2192 Models, then refresh.",
|
|
701
|
+
"sessionUsage.title": "Current session spend (estimated)",
|
|
702
|
+
"sessionUsage.cost": "This session",
|
|
703
|
+
"sessionUsage.tokens": "Session tokens",
|
|
704
|
+
"sessionUsage.calls": "Session calls",
|
|
705
|
+
"sessionUsage.range": "Active {range}",
|
|
706
|
+
"sessionUsage.empty": "No usage in this session yet. Send a message and this session\u2019s own cost and model breakdown will show up here.",
|
|
707
|
+
"overview.title": "Spend overview (estimated)",
|
|
708
|
+
"overview.today": "Today",
|
|
709
|
+
"overview.month": "This month",
|
|
710
|
+
"overview.total": "All time",
|
|
711
|
+
"overview.vsYesterday": "vs yesterday",
|
|
712
|
+
"overview.vsLastMonth": "vs same period last month",
|
|
713
|
+
"overview.empty": "No spend to summarize yet.",
|
|
714
|
+
"usage.trendTitle": "Usage trend (tokens)",
|
|
715
|
+
"usage.windowInput": "{window} input",
|
|
716
|
+
"usage.modelCalls": "Model calls",
|
|
717
|
+
"heatmap.title": "Usage heatmap",
|
|
718
|
+
"peak.title": "Peak / off-peak distribution",
|
|
719
|
+
"cache.title": "Cache hits and savings",
|
|
720
|
+
"models.title": "Model cost ranking \xB7 {window}",
|
|
721
|
+
"sessions.title": "Session cost ranking \xB7 {window}",
|
|
722
|
+
"links.title": "Quick Links",
|
|
723
|
+
"links.usage": "View balance / usage",
|
|
724
|
+
"links.apiKey": "Create API key",
|
|
725
|
+
"links.status": "Service status",
|
|
726
|
+
"settings.title": "Settings",
|
|
727
|
+
"settings.widget": "Show the floating balance widget on other views",
|
|
728
|
+
"settings.widgetHint": "The widget hides while this full dashboard is open, then returns on Chat or Trajectory.",
|
|
729
|
+
"settings.lowBalance": "Balance warning",
|
|
730
|
+
"settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
|
|
731
|
+
"settings.lowBalanceOff": "Balance warnings are off. Enter an amount above 0 to enable them.",
|
|
732
|
+
"settings.monthlyBudget": "Monthly budget",
|
|
733
|
+
"settings.monthlyBudgetOn": "Track a {amount} CNY budget by Beijing calendar month and forecast month-end spend. Enter 0 to disable.",
|
|
734
|
+
"settings.monthlyBudgetOff": "Enter an amount above 0 to show progress and a month-end forecast.",
|
|
735
|
+
"widget.updatedNow": "Updated just now",
|
|
736
|
+
"widget.stale": "Data from {minutes}m ago; press \u21BB to update",
|
|
737
|
+
"widget.loading": "Loading\u2026",
|
|
738
|
+
"widget.lowTitle": "Balance below warning threshold {amount}",
|
|
739
|
+
"widget.todayCostTitle": "Today\u2019s spend (estimated) \xB7 {age}",
|
|
740
|
+
"widget.todayCost": "Today\u2019s spend",
|
|
741
|
+
"widget.noBalance": "No balance data",
|
|
742
|
+
"widget.dragCard": "Drag to any corner",
|
|
743
|
+
"widget.dragGrip": "Drag to move",
|
|
744
|
+
"widget.refresh": "Refresh balance and today\u2019s usage",
|
|
745
|
+
"widget.expand": "Expand",
|
|
746
|
+
"widget.collapse": "Collapse",
|
|
747
|
+
"widget.expandAria": "Expand balance widget",
|
|
748
|
+
"widget.collapseAria": "Collapse balance widget",
|
|
749
|
+
"boundary.title": "The usage dashboard crashed",
|
|
750
|
+
"boundary.body": "Rendering failed, usually because this browser still has cached data from an older version. Clear this plugin\u2019s local data and reload to recover.",
|
|
751
|
+
"boundary.reset": "Clear local data and reload"
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
// src/client/i18n.tsx
|
|
755
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
756
|
+
var fallbackT = (key, params) => {
|
|
757
|
+
const template = zh[key];
|
|
758
|
+
if (params === void 0) return template;
|
|
759
|
+
return template.replace(/\{(\w+)\}/g, (match, name2) => name2 in params ? String(params[name2]) : match);
|
|
760
|
+
};
|
|
761
|
+
var TranslationContext = (0, import_react2.createContext)(fallbackT);
|
|
762
|
+
function LocaleProvider(props) {
|
|
763
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TranslationContext.Provider, { value: props.t, children: props.children });
|
|
764
|
+
}
|
|
765
|
+
function useI18n() {
|
|
766
|
+
const t = (0, import_react2.useContext)(TranslationContext);
|
|
767
|
+
return { t, locale: t("meta.locale") === "en" ? "en" : "zh" };
|
|
768
|
+
}
|
|
769
|
+
function localizeApiError(message, t, fallback) {
|
|
770
|
+
if (message === void 0 || message === "") return t(fallback);
|
|
771
|
+
if (message === "\u51ED\u8BC1\u670D\u52A1\u4E0D\u53EF\u7528") return t("error.credentialsUnavailable");
|
|
772
|
+
if (message === "\u67E5\u8BE2\u5931\u8D25") return t("error.query");
|
|
773
|
+
if (message === "\u4F59\u989D\u52A0\u8F7D\u5931\u8D25") return t("error.balanceLoad");
|
|
774
|
+
if (message === "\u7528\u91CF\u52A0\u8F7D\u5931\u8D25") return t("error.usageLoad");
|
|
775
|
+
if (message === "\u672A\u914D\u7F6E DEEPSEEK_API_KEY\uFF08\u53EF\u5728\u300C\u8BBE\u7F6E \u2192 \u6A21\u578B\u300D\u4E2D\u586B\u5199\uFF09") return t("error.noApiKey");
|
|
776
|
+
if (message === "\u89E3\u6790\u4F59\u989D\u54CD\u5E94\u5931\u8D25") return t("error.balanceParse");
|
|
777
|
+
if (message === "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528") return t("error.persistenceUnavailable");
|
|
778
|
+
if (message === "\u7F3A\u5C11\u4F1A\u8BDD id") return t("error.missingSessionId");
|
|
779
|
+
const mappings = [
|
|
780
|
+
[/^读取 API Key 失败:(.*)$/, "error.credentialRead", "detail"],
|
|
781
|
+
[/^请求余额接口失败:(.*)$/, "error.balanceRequest", "detail"],
|
|
782
|
+
[/^余额接口返回错误(HTTP (\d+))$/, "error.balanceHttp", "status"],
|
|
783
|
+
[/^读取会话列表失败:(.*)$/, "error.sessionList", "detail"],
|
|
784
|
+
[/^读取会话日志失败:(.*)$/, "error.sessionLogRead", "detail"]
|
|
785
|
+
];
|
|
786
|
+
for (const [pattern, key, field] of mappings) {
|
|
787
|
+
const match = message.match(pattern);
|
|
788
|
+
if (match !== null) return t(key, { [field]: match[1] });
|
|
789
|
+
}
|
|
790
|
+
return message;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/client/charts.tsx
|
|
794
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
115
795
|
function useChartTip() {
|
|
116
|
-
const wrapRef = (0,
|
|
117
|
-
const [tip, setTip] = (0,
|
|
118
|
-
const
|
|
796
|
+
const wrapRef = (0, import_react3.useRef)(null);
|
|
797
|
+
const [tip, setTip] = (0, import_react3.useState)(null);
|
|
798
|
+
const [pinned, setPinned] = (0, import_react3.useState)(null);
|
|
799
|
+
const pinnedRef = (0, import_react3.useRef)(null);
|
|
800
|
+
const touchStart = (0, import_react3.useRef)(null);
|
|
801
|
+
const setPin = (next) => {
|
|
802
|
+
pinnedRef.current = next;
|
|
803
|
+
setPinned(next);
|
|
804
|
+
};
|
|
805
|
+
const showAt = (target, text, clientX, clientY) => {
|
|
119
806
|
const wrap = wrapRef.current;
|
|
120
807
|
if (text === void 0 || text === "" || wrap === null) return;
|
|
121
808
|
const rect = wrap.getBoundingClientRect();
|
|
122
|
-
const
|
|
123
|
-
|
|
809
|
+
const targetRect = target.getBoundingClientRect();
|
|
810
|
+
const x = Math.min(Math.max((clientX ?? targetRect.left + targetRect.width / 2) - rect.left, 70), Math.max(rect.width - 70, 70));
|
|
811
|
+
const viewportY = clientY ?? targetRect.top;
|
|
812
|
+
const y = viewportY - rect.top;
|
|
813
|
+
setTip({ x, y, lines: text.split(" \xB7 "), above: viewportY > window.innerHeight - 160 });
|
|
814
|
+
};
|
|
815
|
+
const show = (e, text) => {
|
|
816
|
+
if (e.pointerType === "touch") return;
|
|
817
|
+
showAt(e.currentTarget, text, e.clientX, e.clientY);
|
|
818
|
+
};
|
|
819
|
+
const showFocused = (e, text) => {
|
|
820
|
+
showAt(e.currentTarget, text);
|
|
821
|
+
};
|
|
822
|
+
const hide = () => {
|
|
823
|
+
if (pinnedRef.current !== null) return;
|
|
824
|
+
setTip(null);
|
|
825
|
+
};
|
|
826
|
+
const onPointStart = (e) => {
|
|
827
|
+
if (e.pointerType === "touch") touchStart.current = { x: e.clientX, y: e.clientY, t: Date.now() };
|
|
124
828
|
};
|
|
125
|
-
|
|
829
|
+
const onPointEnd = (e, index, text) => {
|
|
830
|
+
if (e.pointerType !== "touch") return;
|
|
831
|
+
const start = touchStart.current;
|
|
832
|
+
touchStart.current = null;
|
|
833
|
+
if (start === null || !isTapGesture(e.clientX - start.x, e.clientY - start.y, Date.now() - start.t)) return;
|
|
834
|
+
const next = nextPinnedIndex(pinnedRef.current, index);
|
|
835
|
+
setPin(next);
|
|
836
|
+
if (next === null) setTip(null);
|
|
837
|
+
else showAt(e.currentTarget, text, e.clientX, e.clientY);
|
|
838
|
+
};
|
|
839
|
+
(0, import_react3.useEffect)(() => {
|
|
840
|
+
if (pinned === null) return void 0;
|
|
841
|
+
const onDocPointerDown = (e) => {
|
|
842
|
+
const wrap = wrapRef.current;
|
|
843
|
+
if (wrap !== null && e.target instanceof Node && wrap.contains(e.target)) return;
|
|
844
|
+
setPin(null);
|
|
845
|
+
setTip(null);
|
|
846
|
+
};
|
|
847
|
+
document.addEventListener("pointerdown", onDocPointerDown);
|
|
848
|
+
return () => document.removeEventListener("pointerdown", onDocPointerDown);
|
|
849
|
+
}, [pinned]);
|
|
850
|
+
return { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd };
|
|
851
|
+
}
|
|
852
|
+
function useRovingChartPoints(count, initial, horizontalStep = 1) {
|
|
853
|
+
const refs = (0, import_react3.useRef)([]);
|
|
854
|
+
const [active, setActive] = (0, import_react3.useState)(initial);
|
|
855
|
+
(0, import_react3.useEffect)(() => setActive(Math.min(Math.max(0, initial), Math.max(0, count - 1))), [count, initial]);
|
|
856
|
+
const onKeyDown = (event, index) => {
|
|
857
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
858
|
+
event.preventDefault();
|
|
859
|
+
const next = nextChartFocus(index, count, event.key, horizontalStep);
|
|
860
|
+
setActive(next);
|
|
861
|
+
const target = refs.current[next];
|
|
862
|
+
target?.focus({ preventScroll: true });
|
|
863
|
+
target?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
864
|
+
};
|
|
865
|
+
return { active, refs, onKeyDown };
|
|
126
866
|
}
|
|
127
867
|
function ChartTip(props) {
|
|
128
868
|
const { tip } = props;
|
|
129
869
|
if (tip === null) return null;
|
|
130
870
|
const [head, ...rows] = tip.lines;
|
|
131
|
-
return /* @__PURE__ */ (0,
|
|
132
|
-
/* @__PURE__ */ (0,
|
|
133
|
-
rows.map((row, i) => /* @__PURE__ */ (0,
|
|
871
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `dq-tip${tip.above ? " dq-tip--above" : ""}`, style: { left: `${tip.x}px`, top: `${tip.y}px` }, role: "tooltip", children: [
|
|
872
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-tip-head", children: head }),
|
|
873
|
+
rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-tip-row", children: row }, i))
|
|
134
874
|
] });
|
|
135
875
|
}
|
|
136
876
|
var MODEL_COLORS = ["#4176e6", "#2da44e", "#e16f24", "#8250df", "#bf8700", "#cf222e", "#1f883d", "#0969da"];
|
|
137
877
|
function Bars(props) {
|
|
138
|
-
const { data, height = 120, labelEvery = 1 } = props;
|
|
139
|
-
const { wrapRef, tip, show, hide } = useChartTip();
|
|
878
|
+
const { data, height = 120, labelEvery = 1, minWidth } = props;
|
|
879
|
+
const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
|
|
880
|
+
const scrollRef = (0, import_react3.useRef)(null);
|
|
881
|
+
const initialFocus = lastPopulatedIndex(data.map((datum) => datum.value));
|
|
882
|
+
const roving = useRovingChartPoints(data.length, initialFocus);
|
|
883
|
+
(0, import_react3.useEffect)(() => {
|
|
884
|
+
const scroll = scrollRef.current;
|
|
885
|
+
if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
|
|
886
|
+
}, [data.length, minWidth]);
|
|
140
887
|
let max = 0;
|
|
141
888
|
for (const datum of data) if (datum.value > max) max = datum.value;
|
|
142
889
|
const scale = max > 0 ? max : 1;
|
|
143
|
-
return /* @__PURE__ */ (0,
|
|
144
|
-
/* @__PURE__ */ (0,
|
|
890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-chart-wrap", ref: wrapRef, onPointerLeave: hide, children: [
|
|
891
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: data.map((datum, i) => {
|
|
145
892
|
const h = Math.max(1, Math.round(datum.value / scale * height));
|
|
146
893
|
const text = datum.title ?? `${datum.label}: ${datum.value.toLocaleString()}`;
|
|
147
|
-
return /* @__PURE__ */ (0,
|
|
894
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
148
895
|
"div",
|
|
149
896
|
{
|
|
150
|
-
className: "dq-bar-col"
|
|
897
|
+
className: `dq-bar-col${pinned === i ? " dq-bar-col--pinned" : ""}`,
|
|
898
|
+
role: "img",
|
|
151
899
|
"aria-label": text,
|
|
900
|
+
"aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
|
|
901
|
+
tabIndex: i === roving.active ? 0 : -1,
|
|
902
|
+
ref: (element) => {
|
|
903
|
+
roving.refs.current[i] = element;
|
|
904
|
+
},
|
|
152
905
|
onPointerMove: (e) => show(e, text),
|
|
906
|
+
onPointerDown: onPointStart,
|
|
907
|
+
onPointerUp: (e) => onPointEnd(e, i, text),
|
|
908
|
+
onFocus: (e) => showFocused(e, text),
|
|
909
|
+
onBlur: hide,
|
|
910
|
+
onKeyDown: (e) => roving.onKeyDown(e, i),
|
|
153
911
|
children: [
|
|
154
|
-
/* @__PURE__ */ (0,
|
|
155
|
-
/* @__PURE__ */ (0,
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar", style: { height: `${h}px` } }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: datum.label })
|
|
156
914
|
]
|
|
157
915
|
},
|
|
158
916
|
i
|
|
159
917
|
);
|
|
160
|
-
}) }),
|
|
161
|
-
/* @__PURE__ */ (0,
|
|
918
|
+
}) }) }),
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartTip, { tip })
|
|
162
920
|
] });
|
|
163
921
|
}
|
|
164
922
|
function GroupedBars(props) {
|
|
165
|
-
const { series, height = 120, labelEvery = 1 } = props;
|
|
166
|
-
const { wrapRef, tip, show, hide } = useChartTip();
|
|
923
|
+
const { series, height = 120, labelEvery = 1, minWidth } = props;
|
|
924
|
+
const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
|
|
925
|
+
const scrollRef = (0, import_react3.useRef)(null);
|
|
167
926
|
const count = series.length > 0 ? series[0].bars.length : 0;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
927
|
+
const pointValues = Array.from({ length: count }, (_, i) => series.map((s) => s.bars[i]?.value ?? 0)).flat();
|
|
928
|
+
const roving = useRovingChartPoints(pointValues.length, lastPopulatedIndex(pointValues));
|
|
929
|
+
(0, import_react3.useEffect)(() => {
|
|
930
|
+
const scroll = scrollRef.current;
|
|
931
|
+
if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
|
|
932
|
+
}, [count, minWidth]);
|
|
933
|
+
const scale = maxOrOne(stackedTotals(series, count));
|
|
934
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-chart-wrap", ref: wrapRef, onPointerLeave: hide, children: [
|
|
935
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
936
|
+
"div",
|
|
937
|
+
{
|
|
938
|
+
className: "dq-bar-col",
|
|
939
|
+
children: [
|
|
940
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-group", children: series.map((s, seriesIndex) => {
|
|
941
|
+
const datum = s.bars[i];
|
|
942
|
+
const h = Math.max(1, Math.round(datum.value / scale * height));
|
|
943
|
+
const text = datum.title ?? `${s.key}: ${datum.value.toLocaleString()}`;
|
|
944
|
+
const pointIndex = i * series.length + seriesIndex;
|
|
945
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
946
|
+
"div",
|
|
947
|
+
{
|
|
948
|
+
className: `dq-bar${pinned === pointIndex ? " dq-bar--pinned" : ""}`,
|
|
949
|
+
style: { height: `${h}px`, background: s.color },
|
|
950
|
+
role: "img",
|
|
951
|
+
"aria-label": text,
|
|
952
|
+
"aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
|
|
953
|
+
tabIndex: pointIndex === roving.active ? 0 : -1,
|
|
954
|
+
ref: (element) => {
|
|
955
|
+
roving.refs.current[pointIndex] = element;
|
|
956
|
+
},
|
|
957
|
+
onPointerMove: (e) => show(e, text),
|
|
958
|
+
onPointerDown: onPointStart,
|
|
959
|
+
onPointerUp: (e) => onPointEnd(e, pointIndex, text),
|
|
960
|
+
onFocus: (e) => showFocused(e, text),
|
|
961
|
+
onBlur: hide,
|
|
962
|
+
onKeyDown: (e) => roving.onKeyDown(e, pointIndex)
|
|
963
|
+
},
|
|
964
|
+
s.key
|
|
965
|
+
);
|
|
966
|
+
}) }),
|
|
967
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: series[0].bars[i].label })
|
|
968
|
+
]
|
|
969
|
+
},
|
|
970
|
+
i
|
|
971
|
+
)) }) }),
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartTip, { tip })
|
|
191
973
|
] });
|
|
192
974
|
}
|
|
193
975
|
function heatColor(level) {
|
|
@@ -197,10 +979,11 @@ function heatColor(level) {
|
|
|
197
979
|
if (level === 3) return "rgba(65,118,230,0.75)";
|
|
198
980
|
return "rgba(65,118,230,1)";
|
|
199
981
|
}
|
|
200
|
-
var DOW_LABELS = ["", "\u4E00", "", "\u4E09", "", "\u4E94", ""];
|
|
201
982
|
function Heatmap(props) {
|
|
202
983
|
const { data } = props;
|
|
203
|
-
const {
|
|
984
|
+
const { t, locale } = useI18n();
|
|
985
|
+
const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
|
|
986
|
+
const roving = useRovingChartPoints(data.length, lastPopulatedIndex(data.map((datum) => datum.total)), 7);
|
|
204
987
|
let max = 0;
|
|
205
988
|
for (const datum of data) if (datum.total > max) max = datum.total;
|
|
206
989
|
let firstDow = 0;
|
|
@@ -221,39 +1004,53 @@ function Heatmap(props) {
|
|
|
221
1004
|
const month = first.date.slice(0, 7);
|
|
222
1005
|
if (month === lastMonth) return "";
|
|
223
1006
|
lastMonth = month;
|
|
224
|
-
return
|
|
1007
|
+
return t(`chart.month${Number(first.date.slice(5, 7))}`);
|
|
225
1008
|
});
|
|
226
1009
|
const levelOf = (total) => {
|
|
227
1010
|
if (total <= 0) return 0;
|
|
228
1011
|
const r = max > 0 ? total / max : 0;
|
|
229
1012
|
return r < 0.25 ? 1 : r < 0.5 ? 2 : r < 0.75 ? 3 : 4;
|
|
230
1013
|
};
|
|
231
|
-
return /* @__PURE__ */ (0,
|
|
232
|
-
/* @__PURE__ */ (0,
|
|
233
|
-
/* @__PURE__ */ (0,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
1014
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-heat dq-chart-wrap", ref: wrapRef, onPointerLeave: hide, children: [
|
|
1015
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-heat-scroll", children: [
|
|
1016
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heatmap", children: weeks.map((week, w) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heat-week", children: week.map((datum, d) => {
|
|
1017
|
+
if (datum === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heat-cell dq-heat-cell--pad" }, d);
|
|
1018
|
+
const text = t("chart.tooltip", {
|
|
1019
|
+
head: datum.date,
|
|
1020
|
+
tokens: datum.total.toLocaleString(locale === "en" ? "en-US" : "zh-CN"),
|
|
1021
|
+
cost: datum.cost.toFixed(2),
|
|
1022
|
+
calls: t("common.calls", { count: datum.calls })
|
|
1023
|
+
});
|
|
1024
|
+
const index = w * 7 + d - firstDow;
|
|
1025
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1026
|
+
"div",
|
|
1027
|
+
{
|
|
1028
|
+
className: `dq-heat-cell${pinned === index ? " dq-heat-cell--pinned" : ""}`,
|
|
1029
|
+
style: { background: heatColor(levelOf(datum.total)) },
|
|
1030
|
+
role: "img",
|
|
1031
|
+
"aria-label": text,
|
|
1032
|
+
"aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
|
|
1033
|
+
tabIndex: index === roving.active ? 0 : -1,
|
|
1034
|
+
ref: (element) => {
|
|
1035
|
+
roving.refs.current[index] = element;
|
|
246
1036
|
},
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
1037
|
+
onPointerMove: (e) => show(e, text),
|
|
1038
|
+
onPointerDown: onPointStart,
|
|
1039
|
+
onPointerUp: (e) => onPointEnd(e, index, text),
|
|
1040
|
+
onFocus: (e) => showFocused(e, text),
|
|
1041
|
+
onBlur: hide,
|
|
1042
|
+
onKeyDown: (e) => roving.onKeyDown(e, index)
|
|
1043
|
+
},
|
|
1044
|
+
d
|
|
1045
|
+
);
|
|
1046
|
+
}) }, w)) }),
|
|
1047
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heat-months", "aria-hidden": "true", children: monthLabels.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heat-month", children: label }, i)) })
|
|
251
1048
|
] }),
|
|
252
|
-
/* @__PURE__ */ (0,
|
|
253
|
-
/* @__PURE__ */ (0,
|
|
254
|
-
/* @__PURE__ */ (0,
|
|
255
|
-
[0, 1, 2, 3, 4].map((level) => /* @__PURE__ */ (0,
|
|
256
|
-
/* @__PURE__ */ (0,
|
|
1049
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartTip, { tip }),
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-heat-scale", children: [
|
|
1051
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t("chart.less") }),
|
|
1052
|
+
[0, 1, 2, 3, 4].map((level) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dq-heat-key", style: { background: heatColor(level) } }, level)),
|
|
1053
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t("chart.more") })
|
|
257
1054
|
] })
|
|
258
1055
|
] });
|
|
259
1056
|
}
|
|
@@ -263,17 +1060,145 @@ var fmt = (v) => {
|
|
|
263
1060
|
return n.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
264
1061
|
};
|
|
265
1062
|
var fmtInt = (v) => Math.round(Number(v) || 0).toLocaleString();
|
|
266
|
-
var fmtCompact = (v) => {
|
|
1063
|
+
var fmtCompact = (v, locale = "zh") => {
|
|
267
1064
|
const n = Number(v) || 0;
|
|
1065
|
+
if (locale === "en") {
|
|
1066
|
+
return new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(n);
|
|
1067
|
+
}
|
|
268
1068
|
if (n >= 1e8) return `${(n / 1e8).toFixed(1)}\u4EBF`;
|
|
269
1069
|
if (n >= 1e4) return `${(n / 1e4).toFixed(1)}\u4E07`;
|
|
270
1070
|
return Math.round(n).toLocaleString();
|
|
271
1071
|
};
|
|
272
1072
|
|
|
1073
|
+
// src/client/dom.ts
|
|
1074
|
+
function getMainColumn(frame) {
|
|
1075
|
+
const anchor = frame.querySelector('[data-slot="conversation"]');
|
|
1076
|
+
const column = anchor?.parentElement ?? null;
|
|
1077
|
+
return column === frame ? null : column;
|
|
1078
|
+
}
|
|
1079
|
+
function slotElement(frame, slot, side) {
|
|
1080
|
+
const anchor = frame.querySelector(`[data-slot="${slot}"]`);
|
|
1081
|
+
if (anchor === null) return null;
|
|
1082
|
+
if (side === "child") {
|
|
1083
|
+
const el2 = anchor.firstElementChild;
|
|
1084
|
+
return el2 !== null && el2.getBoundingClientRect().height > 0 ? el2 : null;
|
|
1085
|
+
}
|
|
1086
|
+
let el = anchor.parentElement;
|
|
1087
|
+
for (let depth = 0; depth < 4 && el !== null && el !== frame; depth++) {
|
|
1088
|
+
if (el.getBoundingClientRect().height > 0) return el;
|
|
1089
|
+
el = el.parentElement;
|
|
1090
|
+
}
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
function slotBox(frame, slot, side) {
|
|
1094
|
+
return slotElement(frame, slot, side)?.getBoundingClientRect() ?? null;
|
|
1095
|
+
}
|
|
1096
|
+
function getShellFrame(node) {
|
|
1097
|
+
const overlay = node !== null && typeof node.closest === "function" ? node.closest("[data-shell-overlay]") : null;
|
|
1098
|
+
return overlay?.parentElement ?? null;
|
|
1099
|
+
}
|
|
1100
|
+
function getComposerElement(frame) {
|
|
1101
|
+
if (frame === null) return null;
|
|
1102
|
+
return slotElement(frame, "conversation.composer.dock", "ancestor");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/client/export.ts
|
|
1106
|
+
var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
|
|
1107
|
+
function exportDateStamp(nowMs = Date.now()) {
|
|
1108
|
+
const date = new Date(nowMs + BEIJING_OFFSET_MS2);
|
|
1109
|
+
return [
|
|
1110
|
+
date.getUTCFullYear(),
|
|
1111
|
+
String(date.getUTCMonth() + 1).padStart(2, "0"),
|
|
1112
|
+
String(date.getUTCDate()).padStart(2, "0")
|
|
1113
|
+
].join("");
|
|
1114
|
+
}
|
|
1115
|
+
function csvCell(value) {
|
|
1116
|
+
let text = String(value);
|
|
1117
|
+
if (typeof value === "string" && /^[=+\-@]/.test(text)) text = `'${text}`;
|
|
1118
|
+
return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
1119
|
+
}
|
|
1120
|
+
var csv = (rows) => `\uFEFF${rows.map((row) => row.map(csvCell).join(",")).join("\r\n")}\r
|
|
1121
|
+
`;
|
|
1122
|
+
function dailyUsageCsv(usage) {
|
|
1123
|
+
return csv([
|
|
1124
|
+
["date", "input_tokens", "output_tokens", "cache_hit_tokens", "total_tokens", "calls", "estimated_cost_cny"],
|
|
1125
|
+
...usage.daily.map((day) => [day.date, day.input, day.output, day.cache, day.total, day.calls, day.cost])
|
|
1126
|
+
]);
|
|
1127
|
+
}
|
|
1128
|
+
function modelUsageCsv(usage) {
|
|
1129
|
+
return csv([
|
|
1130
|
+
["provider", "model", "input_tokens", "output_tokens", "cache_hit_tokens", "total_tokens", "calls", "estimated_cost_cny"],
|
|
1131
|
+
...usage.models.map((model) => [
|
|
1132
|
+
model.provider,
|
|
1133
|
+
model.model,
|
|
1134
|
+
model.input,
|
|
1135
|
+
model.output,
|
|
1136
|
+
model.cache,
|
|
1137
|
+
model.total,
|
|
1138
|
+
model.calls,
|
|
1139
|
+
model.cost
|
|
1140
|
+
])
|
|
1141
|
+
]);
|
|
1142
|
+
}
|
|
1143
|
+
function fullUsageJson(usage, nowMs = Date.now()) {
|
|
1144
|
+
return `${JSON.stringify({
|
|
1145
|
+
schemaVersion: 1,
|
|
1146
|
+
exportedAt: new Date(nowMs).toISOString(),
|
|
1147
|
+
scope: "local-dsh-session-logs",
|
|
1148
|
+
data: usage
|
|
1149
|
+
}, null, 2)}
|
|
1150
|
+
`;
|
|
1151
|
+
}
|
|
1152
|
+
function downloadText(filename, text, mediaType) {
|
|
1153
|
+
const url = URL.createObjectURL(new Blob([text], { type: mediaType }));
|
|
1154
|
+
const anchor = document.createElement("a");
|
|
1155
|
+
anchor.href = url;
|
|
1156
|
+
anchor.download = filename;
|
|
1157
|
+
anchor.hidden = true;
|
|
1158
|
+
document.body.appendChild(anchor);
|
|
1159
|
+
anchor.click();
|
|
1160
|
+
anchor.remove();
|
|
1161
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// src/client/freshness.ts
|
|
1165
|
+
function updatedText(updatedAt, nowMs = Date.now(), t = fallbackT) {
|
|
1166
|
+
if (updatedAt === null || !Number.isFinite(updatedAt)) return t("fresh.none");
|
|
1167
|
+
const elapsed = Math.max(0, nowMs - updatedAt);
|
|
1168
|
+
if (elapsed < 6e4) return t("fresh.justNow");
|
|
1169
|
+
const minutes = Math.floor(elapsed / 6e4);
|
|
1170
|
+
if (minutes < 60) return t("fresh.minutes", { count: minutes });
|
|
1171
|
+
const hours = Math.floor(minutes / 60);
|
|
1172
|
+
if (hours < 24) return t("fresh.hours", { count: hours });
|
|
1173
|
+
return t("fresh.days", { count: Math.floor(hours / 24) });
|
|
1174
|
+
}
|
|
1175
|
+
function syncStatusText(state, updatedAt, nowMs = Date.now(), t = fallbackT) {
|
|
1176
|
+
const age = updatedText(updatedAt, nowMs, t);
|
|
1177
|
+
if (state === "syncing") return updatedAt === null ? t("sync.first") : t("sync.syncing", { age });
|
|
1178
|
+
if (state === "fresh") return t("sync.fresh", { age });
|
|
1179
|
+
if (state === "cached") return t("sync.cached", { age });
|
|
1180
|
+
if (state === "fallback") return t("sync.fallback", { age });
|
|
1181
|
+
return t("sync.error");
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/client/metric.ts
|
|
1185
|
+
var CHART_METRICS = ["tokens", "cost", "calls"];
|
|
1186
|
+
var isChartMetric = (value) => typeof value === "string" && CHART_METRICS.some((metric) => metric === value);
|
|
1187
|
+
function chartMetricValue(metric, point) {
|
|
1188
|
+
if (metric === "cost") return point.cost;
|
|
1189
|
+
if (metric === "calls") return point.calls;
|
|
1190
|
+
return point.total;
|
|
1191
|
+
}
|
|
1192
|
+
function chartMetricName(metric, t = fallbackT) {
|
|
1193
|
+
if (metric === "cost") return t("metric.cost");
|
|
1194
|
+
if (metric === "calls") return t("metric.calls");
|
|
1195
|
+
return t("metric.usage");
|
|
1196
|
+
}
|
|
1197
|
+
|
|
273
1198
|
// src/client/prefs.ts
|
|
274
|
-
var
|
|
1199
|
+
var PREFIX2 = "dsh-usage-dashboard:pref:";
|
|
275
1200
|
var VERSION = 1;
|
|
276
|
-
var keyOf = (key) => `${
|
|
1201
|
+
var keyOf = (key) => `${PREFIX2}${key}:v${VERSION}`;
|
|
277
1202
|
function loadPref(key, isValid, fallback) {
|
|
278
1203
|
if (typeof localStorage === "undefined") return fallback;
|
|
279
1204
|
try {
|
|
@@ -316,362 +1241,673 @@ function createStore(key, isValid, fallback) {
|
|
|
316
1241
|
var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
317
1242
|
var widgetVisibleStore = createStore("widget.visible", isBoolean, true);
|
|
318
1243
|
var lowBalanceStore = createStore("alert.lowBalance", isThreshold, 10);
|
|
1244
|
+
var monthlyBudgetStore = createStore("budget.monthly", isThreshold, 0);
|
|
1245
|
+
var isUsageWindowDays = (value) => typeof value === "number" && USAGE_WINDOW_DAYS.some((days) => days === value);
|
|
1246
|
+
var usageWindowStore = createStore("usage.windowDays", isUsageWindowDays, 30);
|
|
1247
|
+
var chartMetricStore = createStore("usage.chartMetric", isChartMetric, "tokens");
|
|
1248
|
+
var quotaViewActive = false;
|
|
1249
|
+
var quotaViewListeners = /* @__PURE__ */ new Set();
|
|
1250
|
+
var quotaViewActiveStore = {
|
|
1251
|
+
get: () => quotaViewActive,
|
|
1252
|
+
set: (value) => {
|
|
1253
|
+
if (value === quotaViewActive) return;
|
|
1254
|
+
quotaViewActive = value;
|
|
1255
|
+
for (const listener of [...quotaViewListeners]) listener();
|
|
1256
|
+
},
|
|
1257
|
+
subscribe: (fn) => {
|
|
1258
|
+
quotaViewListeners.add(fn);
|
|
1259
|
+
return () => {
|
|
1260
|
+
quotaViewListeners.delete(fn);
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
319
1264
|
|
|
320
1265
|
// src/client/dashboard.tsx
|
|
321
|
-
var
|
|
1266
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1267
|
+
var COMPOSER_FALLBACK_PX = 126;
|
|
322
1268
|
function Skel(props) {
|
|
323
|
-
return /* @__PURE__ */ (0,
|
|
1269
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel", style: { width: `${props.w}px`, height: `${props.h}px` } });
|
|
324
1270
|
}
|
|
325
1271
|
function BalanceSkeleton() {
|
|
326
|
-
return /* @__PURE__ */ (0,
|
|
327
|
-
/* @__PURE__ */ (0,
|
|
328
|
-
/* @__PURE__ */ (0,
|
|
329
|
-
/* @__PURE__ */ (0,
|
|
1272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance-grid", children: [
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1274
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 56, h: 11 }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 150, h: 22 })
|
|
1276
|
+
] }),
|
|
1277
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1278
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 28, h: 11 }),
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 48, h: 22 })
|
|
1280
|
+
] })
|
|
1281
|
+
] });
|
|
1282
|
+
}
|
|
1283
|
+
function SessionUsageSkeleton() {
|
|
1284
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance-grid", children: [
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1286
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 64, h: 11 }),
|
|
1287
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 96, h: 18 })
|
|
1288
|
+
] }),
|
|
1289
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1290
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 70, h: 11 }),
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 80, h: 18 })
|
|
330
1292
|
] }),
|
|
331
|
-
/* @__PURE__ */ (0,
|
|
332
|
-
/* @__PURE__ */ (0,
|
|
333
|
-
/* @__PURE__ */ (0,
|
|
1293
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1294
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 64, h: 11 }),
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 40, h: 18 })
|
|
334
1296
|
] })
|
|
335
1297
|
] });
|
|
336
1298
|
}
|
|
337
1299
|
function UsageSkeleton() {
|
|
338
|
-
return /* @__PURE__ */ (0,
|
|
339
|
-
/* @__PURE__ */ (0,
|
|
340
|
-
/* @__PURE__ */ (0,
|
|
341
|
-
/* @__PURE__ */ (0,
|
|
1300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1301
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-usage-totals", children: [44, 44, 62, 62].map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
|
|
1303
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: w > 70 ? 84 : 56, h: 20 })
|
|
342
1304
|
] }, i)) }),
|
|
343
|
-
/* @__PURE__ */ (0,
|
|
344
|
-
/* @__PURE__ */ (0,
|
|
1305
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 140, h: 12 }),
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel-bars", children: Array.from({ length: 30 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel dq-skel-bar", style: { height: `${18 + i * 37 % 82}px` } }, i)) })
|
|
1307
|
+
] });
|
|
1308
|
+
}
|
|
1309
|
+
function HeatmapSkeleton() {
|
|
1310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 140, h: 12 }),
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel-bars", style: { height: "88px" }, children: Array.from({ length: 52 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel dq-skel-bar", style: { height: `${14 + i * 29 % 60}px` } }, i)) })
|
|
345
1313
|
] });
|
|
346
1314
|
}
|
|
347
1315
|
function Delta(props) {
|
|
1316
|
+
const { t } = useI18n();
|
|
348
1317
|
const { current, previous, label } = props;
|
|
349
1318
|
if (previous <= 0) {
|
|
350
1319
|
if (current <= 0) return null;
|
|
351
|
-
return /* @__PURE__ */ (0,
|
|
1320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--new", title: t("delta.noBaselineTitle", { label }), children: t("delta.noBaseline") });
|
|
352
1321
|
}
|
|
353
1322
|
const percent = Math.round((current - previous) / previous * 100);
|
|
354
1323
|
const title = `${label} \xA5${fmt(previous)}`;
|
|
355
|
-
if (percent === 0) return /* @__PURE__ */ (0,
|
|
1324
|
+
if (percent === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--flat", title, children: t("delta.flat") });
|
|
356
1325
|
const up = percent > 0;
|
|
357
|
-
return /* @__PURE__ */ (0,
|
|
1326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: `dq-delta ${up ? "dq-delta--up" : "dq-delta--down"}`, title, children: [
|
|
358
1327
|
up ? "\u2191" : "\u2193",
|
|
359
1328
|
Math.abs(percent),
|
|
360
1329
|
"% ",
|
|
361
|
-
/* @__PURE__ */ (0,
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta-label", children: label })
|
|
362
1331
|
] });
|
|
363
1332
|
}
|
|
364
1333
|
function Period(props) {
|
|
1334
|
+
const { t, locale } = useI18n();
|
|
365
1335
|
const { period, previous, compare } = props;
|
|
366
|
-
return /* @__PURE__ */ (0,
|
|
367
|
-
/* @__PURE__ */ (0,
|
|
368
|
-
/* @__PURE__ */ (0,
|
|
369
|
-
previous !== void 0 && compare !== void 0 && /* @__PURE__ */ (0,
|
|
1336
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-period", children: [
|
|
1337
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-period-head", children: [
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-period-label", children: props.label }),
|
|
1339
|
+
previous !== void 0 && compare !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Delta, { current: period.cost, previous: previous.cost, label: compare })
|
|
370
1340
|
] }),
|
|
371
|
-
/* @__PURE__ */ (0,
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-period-cost", children: [
|
|
372
1342
|
"\xA5 ",
|
|
373
1343
|
fmt(period.cost)
|
|
374
1344
|
] }),
|
|
375
|
-
/* @__PURE__ */ (0,
|
|
376
|
-
fmtCompact(period.total),
|
|
377
|
-
"
|
|
378
|
-
|
|
379
|
-
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-period-sub", children: t("period.volume", {
|
|
1346
|
+
tokens: fmtCompact(period.total, locale),
|
|
1347
|
+
calls: t("common.calls", { count: fmtInt(period.calls) })
|
|
1348
|
+
}) })
|
|
1349
|
+
] });
|
|
1350
|
+
}
|
|
1351
|
+
function BudgetMeter(props) {
|
|
1352
|
+
const { t } = useI18n();
|
|
1353
|
+
if (props.budget <= 0) {
|
|
1354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-budget dq-budget--unset", children: [
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("budget.unset") }),
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "dq-budget-action", onClick: props.onConfigure, children: t("budget.configure") })
|
|
1357
|
+
] });
|
|
1358
|
+
}
|
|
1359
|
+
const snapshot = budgetSnapshot(props.spent, props.budget);
|
|
1360
|
+
const usedPercent = snapshot.ratio * 100;
|
|
1361
|
+
const barPercent = Math.min(100, usedPercent);
|
|
1362
|
+
const forecastText = snapshot.status === "over" ? t("budget.over", { amount: fmt(Math.abs(snapshot.remaining)) }) : snapshot.forecastOver > 0 ? t("budget.forecastOver", { amount: fmt(snapshot.forecastOver) }) : t("budget.forecast", { amount: fmt(snapshot.forecast) });
|
|
1363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `dq-budget dq-budget--${snapshot.status}`, children: [
|
|
1364
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-budget-head", children: [
|
|
1365
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-budget-title", children: t("budget.title") }),
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-budget-amount", children: [
|
|
1367
|
+
"\xA5 ",
|
|
1368
|
+
fmt(snapshot.spent),
|
|
1369
|
+
" / \xA5 ",
|
|
1370
|
+
fmt(snapshot.budget)
|
|
1371
|
+
] })
|
|
1372
|
+
] }),
|
|
1373
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1374
|
+
"div",
|
|
1375
|
+
{
|
|
1376
|
+
className: "dq-budget-track",
|
|
1377
|
+
role: "progressbar",
|
|
1378
|
+
"aria-label": t("budget.progressLabel"),
|
|
1379
|
+
"aria-valuemin": 0,
|
|
1380
|
+
"aria-valuemax": snapshot.budget,
|
|
1381
|
+
"aria-valuenow": Math.min(snapshot.spent, snapshot.budget),
|
|
1382
|
+
"aria-valuetext": t("budget.used", { percent: usedPercent.toFixed(1), forecast: forecastText }),
|
|
1383
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-budget-fill", style: { transform: `scaleX(${barPercent / 100})` } })
|
|
1384
|
+
}
|
|
1385
|
+
),
|
|
1386
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-budget-meta", children: [
|
|
1387
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: snapshot.remaining >= 0 ? t("budget.remaining", { amount: fmt(snapshot.remaining) }) : t("budget.exceeded", { amount: fmt(Math.abs(snapshot.remaining)) }) }),
|
|
1388
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: forecastText })
|
|
380
1389
|
] })
|
|
381
1390
|
] });
|
|
382
1391
|
}
|
|
383
1392
|
var rate = (value) => `\xA5${value.toLocaleString(void 0, { maximumFractionDigits: 3 })}`;
|
|
384
1393
|
function PricingNote(props) {
|
|
1394
|
+
const { t } = useI18n();
|
|
385
1395
|
const { pricing } = props;
|
|
386
1396
|
const split = pricing.splitActive;
|
|
387
|
-
return /* @__PURE__ */ (0,
|
|
388
|
-
/* @__PURE__ */ (0,
|
|
389
|
-
"
|
|
390
|
-
split && /* @__PURE__ */ (0,
|
|
391
|
-
"\u5F53\u524D ",
|
|
392
|
-
pricing.inPeakNow ? "\u9AD8\u5CF0\u65F6\u6BB5" : "\u95F2\u65F6"
|
|
393
|
-
] })
|
|
1397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "dq-pricing", children: [
|
|
1398
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "dq-pricing-summary", children: [
|
|
1399
|
+
t("pricing.title"),
|
|
1400
|
+
split && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-pricing-now${pricing.inPeakNow ? " dq-pricing-now--peak" : ""}`, children: pricing.inPeakNow ? t("pricing.currentPeak") : t("pricing.currentOffPeak") })
|
|
394
1401
|
] }),
|
|
395
|
-
/* @__PURE__ */ (0,
|
|
396
|
-
/* @__PURE__ */ (0,
|
|
397
|
-
/* @__PURE__ */ (0,
|
|
398
|
-
/* @__PURE__ */ (0,
|
|
399
|
-
/* @__PURE__ */ (0,
|
|
400
|
-
/* @__PURE__ */ (0,
|
|
401
|
-
/* @__PURE__ */ (0,
|
|
402
|
-
/* @__PURE__ */ (0,
|
|
1402
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-pricing-body", children: [
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("table", { className: "dq-pricing-table", children: [
|
|
1404
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1405
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.model") }),
|
|
1406
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: split ? t("pricing.period") : t("pricing.unitPrice") }),
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheHit") }),
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheMiss") }),
|
|
1409
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.output") })
|
|
403
1410
|
] }) }),
|
|
404
|
-
/* @__PURE__ */ (0,
|
|
405
|
-
/* @__PURE__ */ (0,
|
|
406
|
-
/* @__PURE__ */ (0,
|
|
407
|
-
/* @__PURE__ */ (0,
|
|
408
|
-
/* @__PURE__ */ (0,
|
|
409
|
-
/* @__PURE__ */ (0,
|
|
410
|
-
/* @__PURE__ */ (0,
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react4.Fragment, { children: [
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan: tier.offPeak !== null ? 2 : 1, children: tier.model }),
|
|
1414
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: split ? t("pricing.peak") : t("pricing.fixed") }),
|
|
1415
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.cacheHit) }),
|
|
1416
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.input) }),
|
|
1417
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.output) })
|
|
411
1418
|
] }),
|
|
412
|
-
tier.offPeak !== null && /* @__PURE__ */ (0,
|
|
413
|
-
/* @__PURE__ */ (0,
|
|
414
|
-
/* @__PURE__ */ (0,
|
|
415
|
-
/* @__PURE__ */ (0,
|
|
416
|
-
/* @__PURE__ */ (0,
|
|
1419
|
+
tier.offPeak !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1420
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.offPeak") }),
|
|
1421
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.offPeak.cacheHit) }),
|
|
1422
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.offPeak.input) }),
|
|
1423
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.offPeak.output) })
|
|
417
1424
|
] })
|
|
418
1425
|
] }, tier.model)) })
|
|
419
1426
|
] }),
|
|
420
|
-
/* @__PURE__ */ (0,
|
|
421
|
-
"
|
|
422
|
-
pricing.
|
|
423
|
-
"
|
|
424
|
-
split ? `\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 ${pricing.peakWindows.join("\u3001")}\uFF0C\u5176\u4F59\u4E3A\u95F2\u65F6\uFF08\u4EF7\u683C\u51CF\u534A\uFF09\uFF1B${pricing.switchDate} \u4E4B\u524D\u7684\u7528\u91CF\u4ECD\u6309\u65E7\u4EF7\u4F30\u7B97\u3002` : `${pricing.switchDate} 00:00 \u8D77\u6539\u4E3A\u5CF0\u8C37\u5B9A\u4EF7\uFF08\u9AD8\u5CF0 ${pricing.peakWindows.join("\u3001")}\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\uFF09\uFF0C\u5C4A\u65F6\u672C\u9875\u4F1A\u6309\u6BCF\u6761\u8BB0\u5F55\u7684\u65F6\u95F4\u81EA\u52A8\u5206\u6BB5\u8BA1\u4EF7\u3002`,
|
|
425
|
-
"\u672A\u77E5\u6A21\u578B\u6309 deepseek-v4-pro \u8BA1\u4EF7\u3002"
|
|
1427
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "dq-pricing-foot", children: [
|
|
1428
|
+
t("pricing.unit", { currency: pricing.currency }),
|
|
1429
|
+
split ? t("pricing.splitNote", { windows: pricing.peakWindows.join(t("common.listSeparator")), date: pricing.switchDate }) : t("pricing.switchNote", { windows: pricing.peakWindows.join(t("common.listSeparator")), date: pricing.switchDate }),
|
|
1430
|
+
t("pricing.unknown")
|
|
426
1431
|
] })
|
|
427
1432
|
] })
|
|
428
1433
|
] });
|
|
429
1434
|
}
|
|
1435
|
+
function CoverageDiagnostics(props) {
|
|
1436
|
+
const { t, locale } = useI18n();
|
|
1437
|
+
const { coverage } = props;
|
|
1438
|
+
const hasGaps = coverage.failedSessions > 0 || coverage.skippedRecords > 0 || coverage.scannedSessions < coverage.listedSessions;
|
|
1439
|
+
const coverageTime = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "zh-CN", {
|
|
1440
|
+
timeZone: "Asia/Shanghai",
|
|
1441
|
+
year: "numeric",
|
|
1442
|
+
month: "numeric",
|
|
1443
|
+
day: "numeric",
|
|
1444
|
+
hour: "2-digit",
|
|
1445
|
+
minute: "2-digit",
|
|
1446
|
+
hour12: false
|
|
1447
|
+
});
|
|
1448
|
+
const range = coverage.earliestAt === null || coverage.latestAt === null ? t("coverage.noRange") : `${coverageTime.format(coverage.earliestAt)} \u2013 ${coverageTime.format(coverage.latestAt)}`;
|
|
1449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: `dq-coverage${hasGaps ? " dq-coverage--warn" : ""}`, children: [
|
|
1450
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "dq-coverage-summary", children: [
|
|
1451
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-coverage-title", children: t("coverage.title") }),
|
|
1452
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-coverage-status", children: hasGaps ? t("coverage.gaps") : t("coverage.complete") }),
|
|
1453
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-coverage-brief", children: t("coverage.brief", {
|
|
1454
|
+
scanned: fmtInt(coverage.scannedSessions),
|
|
1455
|
+
listed: fmtInt(coverage.listedSessions),
|
|
1456
|
+
records: fmtInt(coverage.usageRecords)
|
|
1457
|
+
}) })
|
|
1458
|
+
] }),
|
|
1459
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-coverage-body", children: [
|
|
1460
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { className: "dq-coverage-metrics", children: [
|
|
1461
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1462
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: t("coverage.scanned") }),
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: t("common.sessions", { count: fmtInt(coverage.scannedSessions) }) })
|
|
1464
|
+
] }),
|
|
1465
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: t("coverage.failed") }),
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: t("common.sessions", { count: fmtInt(coverage.failedSessions) }) })
|
|
1468
|
+
] }),
|
|
1469
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: t("coverage.skipped") }),
|
|
1471
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: t("common.records", { count: fmtInt(coverage.skippedRecords) }) })
|
|
1472
|
+
] })
|
|
1473
|
+
] }),
|
|
1474
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-coverage-range", children: t("coverage.time", { range }) }),
|
|
1475
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-coverage-note", children: t("coverage.scope") }),
|
|
1476
|
+
hasGaps && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-coverage-warning", children: t("coverage.warning") })
|
|
1477
|
+
] })
|
|
1478
|
+
] });
|
|
1479
|
+
}
|
|
430
1480
|
function Stat(props) {
|
|
431
|
-
return /* @__PURE__ */ (0,
|
|
432
|
-
/* @__PURE__ */ (0,
|
|
1481
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: props.label }),
|
|
433
1483
|
props.children
|
|
434
1484
|
] });
|
|
435
1485
|
}
|
|
436
1486
|
function Link(props) {
|
|
437
|
-
return /* @__PURE__ */ (0,
|
|
1487
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: "dq-link", href: props.href, target: "_blank", rel: "noreferrer", children: props.children });
|
|
1488
|
+
}
|
|
1489
|
+
function ExportMenu(props) {
|
|
1490
|
+
const { t } = useI18n();
|
|
1491
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
1492
|
+
const [done, setDone] = (0, import_react4.useState)(false);
|
|
1493
|
+
const rootRef = (0, import_react4.useRef)(null);
|
|
1494
|
+
const triggerRef = (0, import_react4.useRef)(null);
|
|
1495
|
+
const menuRef = (0, import_react4.useRef)(null);
|
|
1496
|
+
const doneTimerRef = (0, import_react4.useRef)(null);
|
|
1497
|
+
(0, import_react4.useEffect)(() => {
|
|
1498
|
+
if (!open) return;
|
|
1499
|
+
const onDown = (event) => {
|
|
1500
|
+
if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
|
|
1501
|
+
};
|
|
1502
|
+
const onKey = (event) => {
|
|
1503
|
+
if (event.key !== "Escape") return;
|
|
1504
|
+
setOpen(false);
|
|
1505
|
+
triggerRef.current?.focus();
|
|
1506
|
+
};
|
|
1507
|
+
document.addEventListener("mousedown", onDown);
|
|
1508
|
+
document.addEventListener("keydown", onKey);
|
|
1509
|
+
window.requestAnimationFrame(() => menuRef.current?.querySelector("button")?.focus());
|
|
1510
|
+
return () => {
|
|
1511
|
+
document.removeEventListener("mousedown", onDown);
|
|
1512
|
+
document.removeEventListener("keydown", onKey);
|
|
1513
|
+
};
|
|
1514
|
+
}, [open]);
|
|
1515
|
+
(0, import_react4.useEffect)(() => () => {
|
|
1516
|
+
if (doneTimerRef.current !== null) window.clearTimeout(doneTimerRef.current);
|
|
1517
|
+
}, []);
|
|
1518
|
+
const finish = () => {
|
|
1519
|
+
setOpen(false);
|
|
1520
|
+
setDone(true);
|
|
1521
|
+
if (doneTimerRef.current !== null) window.clearTimeout(doneTimerRef.current);
|
|
1522
|
+
doneTimerRef.current = window.setTimeout(() => setDone(false), 1400);
|
|
1523
|
+
};
|
|
1524
|
+
const exportDaily = () => {
|
|
1525
|
+
const stamp = exportDateStamp();
|
|
1526
|
+
downloadText(`dsh-usage-${stamp}.csv`, dailyUsageCsv(props.usage), "text/csv;charset=utf-8");
|
|
1527
|
+
finish();
|
|
1528
|
+
};
|
|
1529
|
+
const exportModels = () => {
|
|
1530
|
+
const stamp = exportDateStamp();
|
|
1531
|
+
downloadText(`dsh-usage-models-${stamp}.csv`, modelUsageCsv(props.usage), "text/csv;charset=utf-8");
|
|
1532
|
+
finish();
|
|
1533
|
+
};
|
|
1534
|
+
const exportJson = () => {
|
|
1535
|
+
const stamp = exportDateStamp();
|
|
1536
|
+
downloadText(`dsh-usage-${stamp}.json`, fullUsageJson(props.usage), "application/json;charset=utf-8");
|
|
1537
|
+
finish();
|
|
1538
|
+
};
|
|
1539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-export", ref: rootRef, children: [
|
|
1540
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1541
|
+
"button",
|
|
1542
|
+
{
|
|
1543
|
+
ref: triggerRef,
|
|
1544
|
+
type: "button",
|
|
1545
|
+
className: `dq-export-btn${done ? " dq-export-btn--done" : ""}`,
|
|
1546
|
+
"aria-haspopup": "true",
|
|
1547
|
+
"aria-expanded": open,
|
|
1548
|
+
title: t("export.title"),
|
|
1549
|
+
onClick: () => setOpen((value) => !value),
|
|
1550
|
+
children: done ? t("export.done") : t("export.action")
|
|
1551
|
+
}
|
|
1552
|
+
),
|
|
1553
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref: menuRef, className: "dq-export-menu", "aria-label": t("export.menuLabel"), children: [
|
|
1554
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", onClick: exportDaily, children: [
|
|
1555
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("export.dailyCsv") }),
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: t("common.rows", { count: props.usage.daily.length }) })
|
|
1557
|
+
] }),
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", onClick: exportModels, children: [
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("export.modelCsv") }),
|
|
1560
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: t("common.rows", { count: props.usage.models.length }) })
|
|
1561
|
+
] }),
|
|
1562
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", onClick: exportJson, children: [
|
|
1563
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("export.fullJson") }),
|
|
1564
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: t("export.jsonHint") })
|
|
1565
|
+
] })
|
|
1566
|
+
] })
|
|
1567
|
+
] });
|
|
438
1568
|
}
|
|
439
1569
|
var modelKeyOf = (m) => m.provider !== "" ? `${m.provider}/${m.model}` : m.model;
|
|
440
|
-
var modelNameOf = (m) => m.model !== "" ? m.model : "
|
|
1570
|
+
var modelNameOf = (m, t = fallbackT) => m.model !== "" ? m.model : t("common.unknownModel");
|
|
1571
|
+
function SessionUsageCard(props) {
|
|
1572
|
+
const { t, locale } = useI18n();
|
|
1573
|
+
const { data } = props;
|
|
1574
|
+
if (data.calls === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessionUsage.empty") });
|
|
1575
|
+
const rangeTime = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "zh-CN", {
|
|
1576
|
+
timeZone: "Asia/Shanghai",
|
|
1577
|
+
year: "numeric",
|
|
1578
|
+
month: "numeric",
|
|
1579
|
+
day: "numeric",
|
|
1580
|
+
hour: "2-digit",
|
|
1581
|
+
minute: "2-digit",
|
|
1582
|
+
hour12: false
|
|
1583
|
+
});
|
|
1584
|
+
const range = data.firstActive !== null && data.lastActive !== null ? data.firstActive === data.lastActive ? rangeTime.format(data.firstActive) : `${rangeTime.format(data.firstActive)} \u2013 ${rangeTime.format(data.lastActive)}` : null;
|
|
1585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1586
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance-grid", children: [
|
|
1587
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("sessionUsage.cost"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
1588
|
+
"\xA5 ",
|
|
1589
|
+
fmt(data.cost)
|
|
1590
|
+
] }) }),
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("sessionUsage.tokens"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtCompact(data.total, locale) }) }),
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("sessionUsage.calls"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtInt(data.calls) }) })
|
|
1593
|
+
] }),
|
|
1594
|
+
range !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-session-sub", children: t("sessionUsage.range", { range }) }),
|
|
1595
|
+
data.models.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank", children: data.models.map((m, index) => {
|
|
1596
|
+
const key = m.provider !== "" ? `${m.provider}/${m.model}` : m.model !== "" ? m.model : `model-${index}`;
|
|
1597
|
+
const name2 = m.model !== "" ? m.model : t("common.unknownModel");
|
|
1598
|
+
const color = MODEL_COLORS[index % MODEL_COLORS.length];
|
|
1599
|
+
const share = data.cost > 0 ? m.cost / data.cost : 0;
|
|
1600
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-row", children: [
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-head", children: [
|
|
1602
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-name", title: key, children: [
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: color } }),
|
|
1604
|
+
name2
|
|
1605
|
+
] }),
|
|
1606
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-cost", children: [
|
|
1607
|
+
"\xA5 ",
|
|
1608
|
+
fmt(m.cost),
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-share", children: [
|
|
1610
|
+
(share * 100).toFixed(1),
|
|
1611
|
+
"%"
|
|
1612
|
+
] })
|
|
1613
|
+
] })
|
|
1614
|
+
] }),
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1616
|
+
"div",
|
|
1617
|
+
{
|
|
1618
|
+
className: "dq-rank-track",
|
|
1619
|
+
role: "img",
|
|
1620
|
+
"aria-label": t("models.shareAria", { name: name2, percent: (share * 100).toFixed(1) }),
|
|
1621
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank-fill", style: { width: `${Math.max(share * 100, 1.5)}%`, background: color } })
|
|
1622
|
+
}
|
|
1623
|
+
),
|
|
1624
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank-sub", children: t("models.detail", {
|
|
1625
|
+
tokens: fmtCompact(m.total, locale),
|
|
1626
|
+
calls: t("common.calls", { count: fmtInt(m.calls) }),
|
|
1627
|
+
input: fmtCompact(m.input, locale),
|
|
1628
|
+
output: fmtCompact(m.output, locale),
|
|
1629
|
+
cache: fmtCompact(m.cache, locale)
|
|
1630
|
+
}) })
|
|
1631
|
+
] }, key);
|
|
1632
|
+
}) })
|
|
1633
|
+
] });
|
|
1634
|
+
}
|
|
441
1635
|
function CacheCard(props) {
|
|
1636
|
+
const { t, locale } = useI18n();
|
|
442
1637
|
const { totals } = props;
|
|
443
1638
|
const prompt = totals.input + totals.cache;
|
|
444
|
-
if (prompt === 0) return /* @__PURE__ */ (0,
|
|
1639
|
+
if (prompt === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") });
|
|
445
1640
|
const rate2 = totals.cache / prompt;
|
|
446
1641
|
const wouldHaveCost = totals.cost + totals.cacheSavings;
|
|
447
1642
|
const low = rate2 < 0.6;
|
|
448
|
-
return /* @__PURE__ */ (0,
|
|
449
|
-
/* @__PURE__ */ (0,
|
|
450
|
-
/* @__PURE__ */ (0,
|
|
451
|
-
/* @__PURE__ */ (0,
|
|
452
|
-
/* @__PURE__ */ (0,
|
|
1643
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-cache", children: [
|
|
1644
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-cache-figures", children: [
|
|
1645
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1646
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("cache.rate") }),
|
|
1647
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `dq-stat-value${low ? " dq-stat-value--warn" : " dq-stat-value--ok"}`, children: [
|
|
453
1648
|
(rate2 * 100).toFixed(1),
|
|
454
1649
|
"%"
|
|
455
1650
|
] })
|
|
456
1651
|
] }),
|
|
457
|
-
/* @__PURE__ */ (0,
|
|
458
|
-
/* @__PURE__ */ (0,
|
|
459
|
-
/* @__PURE__ */ (0,
|
|
1652
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1653
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("cache.saved") }),
|
|
1654
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
460
1655
|
"\xA5 ",
|
|
461
1656
|
fmt(totals.cacheSavings)
|
|
462
1657
|
] })
|
|
463
1658
|
] }),
|
|
464
|
-
/* @__PURE__ */ (0,
|
|
465
|
-
/* @__PURE__ */ (0,
|
|
466
|
-
/* @__PURE__ */ (0,
|
|
1659
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1660
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("cache.allMiss") }),
|
|
1661
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value dq-muted-value", children: [
|
|
467
1662
|
"\xA5 ",
|
|
468
1663
|
fmt(wouldHaveCost)
|
|
469
1664
|
] })
|
|
470
1665
|
] })
|
|
471
1666
|
] }),
|
|
472
|
-
/* @__PURE__ */ (0,
|
|
473
|
-
/* @__PURE__ */ (0,
|
|
474
|
-
/* @__PURE__ */ (0,
|
|
475
|
-
/* @__PURE__ */ (0,
|
|
476
|
-
"
|
|
477
|
-
fmtCompact(totals.cache)
|
|
1667
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-cache-track", role: "img", "aria-label": t("cache.aria", { hit: (rate2 * 100).toFixed(1), miss: ((1 - rate2) * 100).toFixed(1) }), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-cache-fill", style: { width: `${rate2 * 100}%` } }) }),
|
|
1668
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-cache-legend", children: [
|
|
1669
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
1670
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--hit" }),
|
|
1671
|
+
t("cache.hitLegend", { tokens: fmtCompact(totals.cache, locale) })
|
|
478
1672
|
] }),
|
|
479
|
-
/* @__PURE__ */ (0,
|
|
480
|
-
/* @__PURE__ */ (0,
|
|
481
|
-
"
|
|
482
|
-
fmtCompact(totals.input),
|
|
483
|
-
"\uFF08\u6309\u672A\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF09"
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--miss" }),
|
|
1675
|
+
t("cache.missLegend", { tokens: fmtCompact(totals.input, locale) })
|
|
484
1676
|
] })
|
|
485
1677
|
] }),
|
|
486
|
-
/* @__PURE__ */ (0,
|
|
1678
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: low ? t("cache.lowHint") : t("cache.goodHint") })
|
|
487
1679
|
] });
|
|
488
1680
|
}
|
|
489
1681
|
function PeakCard(props) {
|
|
1682
|
+
const { t, locale } = useI18n();
|
|
490
1683
|
const { split, pricing, currentCost } = props;
|
|
491
1684
|
const total = split.peak.total + split.offPeak.total;
|
|
492
|
-
if (total === 0) return /* @__PURE__ */ (0,
|
|
1685
|
+
if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") });
|
|
493
1686
|
const peakShare = split.peak.total / total;
|
|
494
1687
|
const shiftSaving = split.peakEraCost - split.offPeakEraCost;
|
|
495
1688
|
const increase = split.peakEraCost - currentCost;
|
|
496
1689
|
const increasePercent = currentCost > 0 ? increase / currentCost * 100 : 0;
|
|
497
|
-
return /* @__PURE__ */ (0,
|
|
498
|
-
/* @__PURE__ */ (0,
|
|
1690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak", children: [
|
|
1691
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
499
1692
|
"div",
|
|
500
1693
|
{
|
|
501
1694
|
className: "dq-peak-track",
|
|
502
1695
|
role: "img",
|
|
503
|
-
"aria-label":
|
|
504
|
-
children: /* @__PURE__ */ (0,
|
|
1696
|
+
"aria-label": t("peak.aria", { peak: (peakShare * 100).toFixed(1), offPeak: ((1 - peakShare) * 100).toFixed(1) }),
|
|
1697
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-fill", style: { width: `${peakShare * 100}%` } })
|
|
505
1698
|
}
|
|
506
1699
|
),
|
|
507
|
-
/* @__PURE__ */ (0,
|
|
508
|
-
/* @__PURE__ */ (0,
|
|
509
|
-
/* @__PURE__ */ (0,
|
|
510
|
-
"
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
fmtInt(split.peak.calls),
|
|
516
|
-
" \u6B21"
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-legend", children: [
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
1702
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--peak" }),
|
|
1703
|
+
t("peak.peakLegend", {
|
|
1704
|
+
share: (peakShare * 100).toFixed(1),
|
|
1705
|
+
tokens: fmtCompact(split.peak.total, locale),
|
|
1706
|
+
calls: t("common.callsShort", { count: fmtInt(split.peak.calls) })
|
|
1707
|
+
})
|
|
517
1708
|
] }),
|
|
518
|
-
/* @__PURE__ */ (0,
|
|
519
|
-
/* @__PURE__ */ (0,
|
|
520
|
-
"
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
fmtInt(split.offPeak.calls),
|
|
526
|
-
" \u6B21"
|
|
1709
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
1710
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--offpeak" }),
|
|
1711
|
+
t("peak.offPeakLegend", {
|
|
1712
|
+
share: ((1 - peakShare) * 100).toFixed(1),
|
|
1713
|
+
tokens: fmtCompact(split.offPeak.total, locale),
|
|
1714
|
+
calls: t("common.callsShort", { count: fmtInt(split.offPeak.calls) })
|
|
1715
|
+
})
|
|
527
1716
|
] })
|
|
528
1717
|
] }),
|
|
529
|
-
/* @__PURE__ */ (0,
|
|
530
|
-
!pricing.splitActive && /* @__PURE__ */ (0,
|
|
531
|
-
/* @__PURE__ */ (0,
|
|
532
|
-
/* @__PURE__ */ (0,
|
|
1718
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
|
|
1719
|
+
!pricing.splitActive && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1720
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("peak.newPrice") }),
|
|
1721
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
533
1722
|
"\xA5 ",
|
|
534
1723
|
fmt(split.peakEraCost),
|
|
535
|
-
/* @__PURE__ */ (0,
|
|
536
|
-
"\u8F83\u73B0\u4EF7 +",
|
|
537
|
-
increasePercent.toFixed(0),
|
|
538
|
-
"%"
|
|
539
|
-
] })
|
|
1724
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta", children: t("peak.increase", { percent: increasePercent.toFixed(0) }) })
|
|
540
1725
|
] })
|
|
541
1726
|
] }),
|
|
542
|
-
/* @__PURE__ */ (0,
|
|
543
|
-
/* @__PURE__ */ (0,
|
|
544
|
-
/* @__PURE__ */ (0,
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("peak.shift") }),
|
|
1729
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
545
1730
|
"\xA5 ",
|
|
546
1731
|
fmt(split.offPeakEraCost),
|
|
547
|
-
/* @__PURE__ */ (0,
|
|
548
|
-
"\u53EF\u7701 \xA5",
|
|
549
|
-
fmt(shiftSaving)
|
|
550
|
-
] })
|
|
1732
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
|
|
551
1733
|
] })
|
|
552
1734
|
] })
|
|
553
1735
|
] }),
|
|
554
|
-
/* @__PURE__ */ (0,
|
|
555
|
-
"
|
|
556
|
-
pricing.
|
|
557
|
-
"\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\u3002",
|
|
558
|
-
pricing.splitActive ? "\u628A\u6279\u91CF\u3001\u53EF\u5EF6\u540E\u7684\u4EFB\u52A1\u653E\u5230\u95F2\u65F6\u8DD1\uFF0C\u540C\u6837\u7684 token \u53EA\u8981\u4E00\u534A\u7684\u94B1\u3002" : `\u65B0\u4EF7 ${pricing.switchDate} 00:00 \u751F\u6548\uFF1B\u4E0A\u9762\u4E24\u4E2A\u6570\u5B57\u662F\u6309\u4F60\u5DF2\u6709\u7684\u5168\u90E8\u7528\u91CF\u91CD\u7B97\u7684\u3002`
|
|
1736
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "dq-peak-foot", children: [
|
|
1737
|
+
t("peak.schedule", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
|
|
1738
|
+
pricing.splitActive ? t("peak.activeHint") : t("peak.futureHint", { date: pricing.switchDate })
|
|
559
1739
|
] })
|
|
560
1740
|
] });
|
|
561
1741
|
}
|
|
562
|
-
function agoText(ms) {
|
|
1742
|
+
function agoText(ms, t) {
|
|
563
1743
|
const minutes = Math.floor((Date.now() - ms) / 6e4);
|
|
564
|
-
if (minutes < 1) return "
|
|
565
|
-
if (minutes < 60) return
|
|
1744
|
+
if (minutes < 1) return t("common.justNow");
|
|
1745
|
+
if (minutes < 60) return t("common.minutesAgo", { count: minutes });
|
|
566
1746
|
const hours = Math.floor(minutes / 60);
|
|
567
|
-
if (hours < 24) return
|
|
568
|
-
return
|
|
1747
|
+
if (hours < 24) return t("common.hoursAgo", { count: hours });
|
|
1748
|
+
return t("common.daysAgo", { count: Math.floor(hours / 24) });
|
|
1749
|
+
}
|
|
1750
|
+
function SessionDetail(props) {
|
|
1751
|
+
const { t } = useI18n();
|
|
1752
|
+
const { state } = props;
|
|
1753
|
+
if (state === void 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageSkeleton, {});
|
|
1754
|
+
if (state.data !== null) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageCard, { data: state.data });
|
|
1755
|
+
if (state.error !== null) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty dq-empty--error", children: localizeApiError(state.error, t, "error.query") });
|
|
1756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageSkeleton, {});
|
|
569
1757
|
}
|
|
570
1758
|
function SessionRanking(props) {
|
|
1759
|
+
const { t, locale } = useI18n();
|
|
571
1760
|
const { sessions, count, totalCost } = props;
|
|
572
|
-
|
|
1761
|
+
const [details, setDetails] = (0, import_react4.useState)({});
|
|
1762
|
+
const startedRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
|
|
1763
|
+
const loadDetail = (id) => {
|
|
1764
|
+
if (startedRef.current.has(id)) return;
|
|
1765
|
+
startedRef.current.add(id);
|
|
1766
|
+
fetchSessionUsage(id).then((res) => {
|
|
1767
|
+
setDetails((prev) => ({
|
|
1768
|
+
...prev,
|
|
1769
|
+
[id]: res.ok && res.data !== void 0 ? { error: null, data: res.data } : { error: res.error ?? null, data: null }
|
|
1770
|
+
}));
|
|
1771
|
+
}, (err) => {
|
|
1772
|
+
const message = err?.message ?? String(err);
|
|
1773
|
+
setDetails((prev) => ({ ...prev, [id]: { error: message, data: null } }));
|
|
1774
|
+
});
|
|
1775
|
+
};
|
|
1776
|
+
if (sessions.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessions.empty") });
|
|
573
1777
|
const top = sessions[0].cost;
|
|
574
|
-
return /* @__PURE__ */ (0,
|
|
575
|
-
/* @__PURE__ */ (0,
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1778
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1779
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { className: "dq-sessions", children: sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "dq-session", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1780
|
+
"details",
|
|
1781
|
+
{
|
|
1782
|
+
className: "dq-session-details",
|
|
1783
|
+
onToggle: (e) => {
|
|
1784
|
+
if (e.currentTarget.open) loadDetail(s.id);
|
|
1785
|
+
},
|
|
1786
|
+
children: [
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "dq-session-summary", children: [
|
|
1788
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-session-chevron", "aria-hidden": "true", children: "\u25B8" }),
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-session-summary-body", children: [
|
|
1790
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-session-head", children: [
|
|
1791
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-session-title", title: `${s.title}
|
|
1792
|
+
${s.id}`, children: s.title || t("sessions.fallbackTitle", { id: s.id }) }),
|
|
1793
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-session-cost", children: [
|
|
1794
|
+
"\xA5 ",
|
|
1795
|
+
fmt(s.cost)
|
|
1796
|
+
] })
|
|
1797
|
+
] }),
|
|
1798
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-session-track", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-session-fill", style: { width: `${Math.max(s.cost / (top || 1) * 100, 1.5)}%` } }) }),
|
|
1799
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-session-sub", children: [
|
|
1800
|
+
t("sessions.detail", {
|
|
1801
|
+
tokens: fmtCompact(s.total, locale),
|
|
1802
|
+
calls: t("common.calls", { count: fmtInt(s.calls) }),
|
|
1803
|
+
ago: agoText(s.lastActive, t)
|
|
1804
|
+
}),
|
|
1805
|
+
totalCost > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1806
|
+
" \xB7 ",
|
|
1807
|
+
t("sessions.share", { percent: (s.cost / totalCost * 100).toFixed(1) })
|
|
1808
|
+
] })
|
|
1809
|
+
] })
|
|
1810
|
+
] })
|
|
1811
|
+
] }),
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-session-expand", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionDetail, { state: details[s.id] }) })
|
|
1813
|
+
]
|
|
1814
|
+
}
|
|
1815
|
+
) }, s.id)) }),
|
|
1816
|
+
count > sessions.length && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-session-foot", children: t("sessions.more", { count: fmtInt(count), shown: sessions.length }) })
|
|
605
1817
|
] });
|
|
606
1818
|
}
|
|
607
1819
|
function ModelRanking(props) {
|
|
1820
|
+
const { t, locale } = useI18n();
|
|
608
1821
|
const { models, totalCost } = props;
|
|
609
|
-
if (models.length === 0) return /* @__PURE__ */ (0,
|
|
610
|
-
return /* @__PURE__ */ (0,
|
|
1822
|
+
if (models.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("models.empty") });
|
|
1823
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank", children: models.map((m) => {
|
|
611
1824
|
const key = modelKeyOf(m);
|
|
612
1825
|
const share = totalCost > 0 ? m.cost / totalCost : 0;
|
|
613
|
-
return /* @__PURE__ */ (0,
|
|
614
|
-
/* @__PURE__ */ (0,
|
|
615
|
-
/* @__PURE__ */ (0,
|
|
616
|
-
/* @__PURE__ */ (0,
|
|
617
|
-
modelNameOf(m)
|
|
1826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-row", children: [
|
|
1827
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-head", children: [
|
|
1828
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-name", title: key, children: [
|
|
1829
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: props.colorOf(key) } }),
|
|
1830
|
+
modelNameOf(m, t)
|
|
618
1831
|
] }),
|
|
619
|
-
/* @__PURE__ */ (0,
|
|
1832
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-cost", children: [
|
|
620
1833
|
"\xA5 ",
|
|
621
1834
|
fmt(m.cost),
|
|
622
|
-
/* @__PURE__ */ (0,
|
|
1835
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-rank-share", children: [
|
|
623
1836
|
(share * 100).toFixed(1),
|
|
624
1837
|
"%"
|
|
625
1838
|
] })
|
|
626
1839
|
] })
|
|
627
1840
|
] }),
|
|
628
|
-
/* @__PURE__ */ (0,
|
|
1841
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
629
1842
|
"div",
|
|
630
1843
|
{
|
|
631
1844
|
className: "dq-rank-track",
|
|
632
1845
|
role: "img",
|
|
633
|
-
"aria-label":
|
|
634
|
-
children: /* @__PURE__ */ (0,
|
|
1846
|
+
"aria-label": t("models.shareAria", { name: modelNameOf(m, t), percent: (share * 100).toFixed(1) }),
|
|
1847
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank-fill", style: { width: `${Math.max(share * 100, 1.5)}%`, background: props.colorOf(key) } })
|
|
635
1848
|
}
|
|
636
1849
|
),
|
|
637
|
-
/* @__PURE__ */ (0,
|
|
638
|
-
fmtCompact(m.total),
|
|
639
|
-
"
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
fmtCompact(m.
|
|
643
|
-
|
|
644
|
-
fmtCompact(m.output),
|
|
645
|
-
" / \u7F13\u5B58 ",
|
|
646
|
-
fmtCompact(m.cache)
|
|
647
|
-
] })
|
|
1850
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank-sub", children: t("models.detail", {
|
|
1851
|
+
tokens: fmtCompact(m.total, locale),
|
|
1852
|
+
calls: t("common.calls", { count: fmtInt(m.calls) }),
|
|
1853
|
+
input: fmtCompact(m.input, locale),
|
|
1854
|
+
output: fmtCompact(m.output, locale),
|
|
1855
|
+
cache: fmtCompact(m.cache, locale)
|
|
1856
|
+
}) })
|
|
648
1857
|
] }, key);
|
|
649
1858
|
}) });
|
|
650
1859
|
}
|
|
651
1860
|
function ModelPicker(props) {
|
|
1861
|
+
const { t, locale } = useI18n();
|
|
652
1862
|
const { models } = props;
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
const
|
|
656
|
-
(0,
|
|
1863
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
1864
|
+
const rootRef = (0, import_react4.useRef)(null);
|
|
1865
|
+
const triggerRef = (0, import_react4.useRef)(null);
|
|
1866
|
+
const menuRef = (0, import_react4.useRef)(null);
|
|
1867
|
+
(0, import_react4.useEffect)(() => {
|
|
657
1868
|
if (!open) return;
|
|
658
1869
|
const onDown = (e) => {
|
|
659
1870
|
if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
|
|
660
1871
|
};
|
|
1872
|
+
const onKey = (e) => {
|
|
1873
|
+
if (e.key !== "Escape") return;
|
|
1874
|
+
e.preventDefault();
|
|
1875
|
+
setOpen(false);
|
|
1876
|
+
triggerRef.current?.focus();
|
|
1877
|
+
};
|
|
661
1878
|
document.addEventListener("mousedown", onDown);
|
|
662
|
-
|
|
1879
|
+
document.addEventListener("keydown", onKey);
|
|
1880
|
+
window.requestAnimationFrame(() => menuRef.current?.querySelector("input")?.focus());
|
|
1881
|
+
return () => {
|
|
1882
|
+
document.removeEventListener("mousedown", onDown);
|
|
1883
|
+
document.removeEventListener("keydown", onKey);
|
|
1884
|
+
};
|
|
663
1885
|
}, [open]);
|
|
1886
|
+
if (models.length === 0) return null;
|
|
664
1887
|
const toggle = (key) => {
|
|
665
1888
|
props.onChange(
|
|
666
1889
|
props.selected.includes(key) ? props.selected.filter((k) => k !== key) : [...props.selected, key]
|
|
667
1890
|
);
|
|
668
1891
|
};
|
|
669
|
-
const label = props.selected.length === 0 ? "
|
|
670
|
-
return /* @__PURE__ */ (0,
|
|
671
|
-
/* @__PURE__ */ (0,
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1892
|
+
const label = props.selected.length === 0 ? t("models.all") : t("models.selected", { count: props.selected.length });
|
|
1893
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-model-picker", ref: rootRef, children: [
|
|
1894
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1895
|
+
"button",
|
|
1896
|
+
{
|
|
1897
|
+
ref: triggerRef,
|
|
1898
|
+
type: "button",
|
|
1899
|
+
className: "dq-model-btn",
|
|
1900
|
+
"aria-haspopup": "true",
|
|
1901
|
+
"aria-expanded": open,
|
|
1902
|
+
"aria-controls": "dq-model-menu",
|
|
1903
|
+
title: t("models.filterTitle"),
|
|
1904
|
+
onClick: () => setOpen((value) => !value),
|
|
1905
|
+
children: label
|
|
1906
|
+
}
|
|
1907
|
+
),
|
|
1908
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { id: "dq-model-menu", ref: menuRef, className: "dq-model-menu", role: "group", "aria-label": t("models.filterLabel"), children: [
|
|
1909
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-model-item dq-model-item--all", children: [
|
|
1910
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
675
1911
|
"input",
|
|
676
1912
|
{
|
|
677
1913
|
type: "checkbox",
|
|
@@ -679,40 +1915,120 @@ function ModelPicker(props) {
|
|
|
679
1915
|
onChange: () => props.onChange([])
|
|
680
1916
|
}
|
|
681
1917
|
),
|
|
682
|
-
/* @__PURE__ */ (0,
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("models.all") })
|
|
683
1919
|
] }),
|
|
684
1920
|
models.map((m) => {
|
|
685
1921
|
const key = modelKeyOf(m);
|
|
686
|
-
return /* @__PURE__ */ (0,
|
|
687
|
-
/* @__PURE__ */ (0,
|
|
688
|
-
/* @__PURE__ */ (0,
|
|
689
|
-
/* @__PURE__ */ (0,
|
|
690
|
-
/* @__PURE__ */ (0,
|
|
1922
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-model-item", children: [
|
|
1923
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: props.selected.includes(key), onChange: () => toggle(key) }),
|
|
1924
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: props.colorOf(key) } }),
|
|
1925
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: modelNameOf(m, t) }),
|
|
1926
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-model-tag", children: fmtCompact(m.total, locale) })
|
|
691
1927
|
] }, key);
|
|
692
1928
|
})
|
|
693
1929
|
] })
|
|
694
1930
|
] });
|
|
695
1931
|
}
|
|
696
|
-
|
|
1932
|
+
var windowName = (days, t = fallbackT) => days === 365 ? t("window.year") : t("window.days", { days });
|
|
1933
|
+
function UsageWindowPicker(props) {
|
|
1934
|
+
const { t } = useI18n();
|
|
1935
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-window-switch", role: "group", "aria-label": t("window.groupLabel"), children: USAGE_WINDOW_DAYS.map((days) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1936
|
+
"button",
|
|
1937
|
+
{
|
|
1938
|
+
type: "button",
|
|
1939
|
+
className: `dq-window-btn${props.value === days ? " dq-window-btn--on" : ""}`,
|
|
1940
|
+
"aria-pressed": props.value === days,
|
|
1941
|
+
title: t("window.viewTitle", { window: windowName(days, t) }),
|
|
1942
|
+
onClick: () => props.onChange(days),
|
|
1943
|
+
children: days === 365 ? t("window.yearButton") : t("window.daysButton", { days })
|
|
1944
|
+
},
|
|
1945
|
+
days
|
|
1946
|
+
)) });
|
|
1947
|
+
}
|
|
1948
|
+
var chartMetricLabel = (metric, t = fallbackT) => metric === "tokens" ? t("metric.tokens") : chartMetricName(metric, t);
|
|
1949
|
+
function ChartMetricPicker(props) {
|
|
1950
|
+
const { t } = useI18n();
|
|
1951
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-chart-switch", role: "group", "aria-label": t("metric.groupLabel"), children: CHART_METRICS.map((metric) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1952
|
+
"button",
|
|
1953
|
+
{
|
|
1954
|
+
type: "button",
|
|
1955
|
+
className: `dq-chart-switch-btn${props.value === metric ? " dq-chart-switch-btn--on" : ""}`,
|
|
1956
|
+
"aria-pressed": props.value === metric,
|
|
1957
|
+
title: t("metric.compareTitle", { metric: chartMetricLabel(metric, t) }),
|
|
1958
|
+
onClick: () => props.onChange(metric),
|
|
1959
|
+
children: chartMetricLabel(metric, t)
|
|
1960
|
+
},
|
|
1961
|
+
metric
|
|
1962
|
+
)) });
|
|
1963
|
+
}
|
|
1964
|
+
function BalanceDashboard(props) {
|
|
1965
|
+
const { t, locale } = useI18n();
|
|
697
1966
|
const cachedBalance = getCachedBalance();
|
|
698
1967
|
const cachedUsage = getCachedUsage();
|
|
699
|
-
const
|
|
700
|
-
const [
|
|
701
|
-
const [
|
|
702
|
-
const [
|
|
703
|
-
const [
|
|
704
|
-
const [
|
|
705
|
-
const [
|
|
706
|
-
const
|
|
707
|
-
const [
|
|
708
|
-
const [
|
|
709
|
-
const [
|
|
710
|
-
const
|
|
1968
|
+
const cachedUsageAt = getCachedUsageAt();
|
|
1969
|
+
const [balance, setBalance] = (0, import_react4.useState)(cachedBalance?.data ?? null);
|
|
1970
|
+
const [usage, setUsage] = (0, import_react4.useState)(cachedUsage?.data ?? null);
|
|
1971
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
1972
|
+
const [notice, setNotice] = (0, import_react4.useState)(null);
|
|
1973
|
+
const [loadingBalance, setLoadingBalance] = (0, import_react4.useState)(cachedBalance === null);
|
|
1974
|
+
const [loadingUsage, setLoadingUsage] = (0, import_react4.useState)(cachedUsage === null);
|
|
1975
|
+
const [refreshing, setRefreshing] = (0, import_react4.useState)(false);
|
|
1976
|
+
const [usageUpdatedAt, setUsageUpdatedAt] = (0, import_react4.useState)(cachedUsageAt);
|
|
1977
|
+
const [syncState, setSyncState] = (0, import_react4.useState)(cachedUsageAt === null ? "syncing" : "cached");
|
|
1978
|
+
const [freshnessNow, setFreshnessNow] = (0, import_react4.useState)(Date.now());
|
|
1979
|
+
const hadDataRef = (0, import_react4.useRef)(cachedBalance !== null || cachedUsage !== null);
|
|
1980
|
+
const [widgetOn, setWidgetOn] = (0, import_react4.useState)(widgetVisibleStore.get());
|
|
1981
|
+
const [lowBalance, setLowBalance] = (0, import_react4.useState)(lowBalanceStore.get());
|
|
1982
|
+
const [monthlyBudget, setMonthlyBudget] = (0, import_react4.useState)(monthlyBudgetStore.get());
|
|
1983
|
+
const [barMode, setBarMode] = (0, import_react4.useState)("daily");
|
|
1984
|
+
const [selectedModels, setSelectedModels] = (0, import_react4.useState)([]);
|
|
1985
|
+
const [windowDays, setWindowDays] = (0, import_react4.useState)(usageWindowStore.get());
|
|
1986
|
+
const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
|
|
1987
|
+
const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
|
|
1988
|
+
const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
|
|
1989
|
+
const budgetInputRef = (0, import_react4.useRef)(null);
|
|
1990
|
+
const rootRef = (0, import_react4.useRef)(null);
|
|
1991
|
+
const [composerHeight, setComposerHeight] = (0, import_react4.useState)(null);
|
|
1992
|
+
const [sessionUsage, setSessionUsage] = (0, import_react4.useState)(null);
|
|
1993
|
+
const [loadingSessionUsage, setLoadingSessionUsage] = (0, import_react4.useState)(props.sessionId !== void 0 && props.sessionId !== "");
|
|
1994
|
+
const [sessionUsageError, setSessionUsageError] = (0, import_react4.useState)(null);
|
|
1995
|
+
const sessionUsageIdRef = (0, import_react4.useRef)(null);
|
|
1996
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
1997
|
+
const node = rootRef.current;
|
|
1998
|
+
if (node === null) return;
|
|
1999
|
+
const measure = () => {
|
|
2000
|
+
const frame2 = getShellFrame(node);
|
|
2001
|
+
const composer2 = getComposerElement(frame2);
|
|
2002
|
+
const rect = composer2?.getBoundingClientRect() ?? null;
|
|
2003
|
+
const next = rect !== null && rect.height > 0 ? Math.round(rect.height) : null;
|
|
2004
|
+
setComposerHeight((prev) => prev === next ? prev : next);
|
|
2005
|
+
};
|
|
2006
|
+
measure();
|
|
2007
|
+
const frame = getShellFrame(node);
|
|
2008
|
+
const composer = getComposerElement(frame);
|
|
2009
|
+
let observer = null;
|
|
2010
|
+
if (typeof ResizeObserver === "function") {
|
|
2011
|
+
observer = new ResizeObserver(measure);
|
|
2012
|
+
if (composer !== null) observer.observe(composer);
|
|
2013
|
+
if (frame !== null) observer.observe(frame);
|
|
2014
|
+
}
|
|
2015
|
+
window.addEventListener("resize", measure);
|
|
2016
|
+
return () => {
|
|
2017
|
+
observer?.disconnect();
|
|
2018
|
+
window.removeEventListener("resize", measure);
|
|
2019
|
+
};
|
|
2020
|
+
}, []);
|
|
2021
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
2022
|
+
quotaViewActiveStore.set(true);
|
|
2023
|
+
return () => quotaViewActiveStore.set(false);
|
|
2024
|
+
}, []);
|
|
711
2025
|
const errText = (err) => err?.message ?? String(err);
|
|
712
2026
|
const load = async (force) => {
|
|
2027
|
+
const usageAtBefore = getCachedUsageAt();
|
|
713
2028
|
setError(null);
|
|
714
2029
|
setNotice(null);
|
|
715
2030
|
setRefreshing(true);
|
|
2031
|
+
setSyncState("syncing");
|
|
716
2032
|
const balanceTask = fetchBalance(force).then((res) => {
|
|
717
2033
|
if (res.ok && res.data !== void 0) {
|
|
718
2034
|
setBalance(res.data);
|
|
@@ -727,6 +2043,10 @@ function BalanceDashboard() {
|
|
|
727
2043
|
const usageTask = fetchUsage(force).then((res) => {
|
|
728
2044
|
if (res.ok && res.data !== void 0) {
|
|
729
2045
|
setUsage(res.data);
|
|
2046
|
+
const updatedAt = getCachedUsageAt();
|
|
2047
|
+
setUsageUpdatedAt(updatedAt);
|
|
2048
|
+
setFreshnessNow(Date.now());
|
|
2049
|
+
setSyncState(updatedAt !== null && updatedAt !== usageAtBefore ? "fresh" : "cached");
|
|
730
2050
|
hadDataRef.current = true;
|
|
731
2051
|
return null;
|
|
732
2052
|
}
|
|
@@ -737,18 +2057,62 @@ function BalanceDashboard() {
|
|
|
737
2057
|
});
|
|
738
2058
|
const [balanceError, usageError] = await Promise.all([balanceTask, usageTask]);
|
|
739
2059
|
setRefreshing(false);
|
|
2060
|
+
if (usageError !== null) {
|
|
2061
|
+
const fallbackAt = getCachedUsageAt();
|
|
2062
|
+
setUsageUpdatedAt(fallbackAt);
|
|
2063
|
+
setFreshnessNow(Date.now());
|
|
2064
|
+
setSyncState(fallbackAt === null ? "error" : "fallback");
|
|
2065
|
+
}
|
|
740
2066
|
if (balanceError !== null && usageError !== null) {
|
|
741
|
-
if (hadDataRef.current) setNotice(
|
|
2067
|
+
if (hadDataRef.current) setNotice({ kind: "all", error: balanceError });
|
|
742
2068
|
else setError(balanceError);
|
|
743
2069
|
} else if (balanceError !== null) {
|
|
744
|
-
setNotice(
|
|
2070
|
+
setNotice({ kind: "balance", error: balanceError });
|
|
745
2071
|
} else if (usageError !== null) {
|
|
746
|
-
setNotice(
|
|
2072
|
+
setNotice({ kind: "usage", error: usageError });
|
|
2073
|
+
}
|
|
2074
|
+
if (force && props.sessionId !== void 0 && props.sessionId !== "") {
|
|
2075
|
+
void loadSessionUsage(props.sessionId, true);
|
|
747
2076
|
}
|
|
748
2077
|
};
|
|
749
|
-
(
|
|
2078
|
+
const loadSessionUsage = async (id, force) => {
|
|
2079
|
+
if (!force) setLoadingSessionUsage(true);
|
|
2080
|
+
try {
|
|
2081
|
+
const res = await fetchSessionUsage(id, force);
|
|
2082
|
+
if (sessionUsageIdRef.current !== id) return;
|
|
2083
|
+
if (res.ok && res.data !== void 0) {
|
|
2084
|
+
setSessionUsage(res.data);
|
|
2085
|
+
setSessionUsageError(null);
|
|
2086
|
+
} else {
|
|
2087
|
+
setSessionUsageError(res.error ?? null);
|
|
2088
|
+
}
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
if (sessionUsageIdRef.current === id) setSessionUsageError(errText(err));
|
|
2091
|
+
} finally {
|
|
2092
|
+
if (sessionUsageIdRef.current === id) setLoadingSessionUsage(false);
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
(0, import_react4.useEffect)(() => {
|
|
750
2096
|
void load(false);
|
|
751
2097
|
}, []);
|
|
2098
|
+
(0, import_react4.useEffect)(() => {
|
|
2099
|
+
const id = props.sessionId;
|
|
2100
|
+
sessionUsageIdRef.current = id ?? null;
|
|
2101
|
+
if (id === void 0 || id === "") {
|
|
2102
|
+
setSessionUsage(null);
|
|
2103
|
+
setSessionUsageError(null);
|
|
2104
|
+
setLoadingSessionUsage(false);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
setSessionUsage(null);
|
|
2108
|
+
setSessionUsageError(null);
|
|
2109
|
+
setLoadingSessionUsage(true);
|
|
2110
|
+
void loadSessionUsage(id, false);
|
|
2111
|
+
}, [props.sessionId]);
|
|
2112
|
+
(0, import_react4.useEffect)(() => {
|
|
2113
|
+
const timer = window.setInterval(() => setFreshnessNow(Date.now()), 3e4);
|
|
2114
|
+
return () => window.clearInterval(timer);
|
|
2115
|
+
}, []);
|
|
752
2116
|
const toggle = () => {
|
|
753
2117
|
const next = !widgetOn;
|
|
754
2118
|
setWidgetOn(next);
|
|
@@ -758,36 +2122,62 @@ function BalanceDashboard() {
|
|
|
758
2122
|
setLowBalance(value);
|
|
759
2123
|
lowBalanceStore.set(value);
|
|
760
2124
|
};
|
|
2125
|
+
const changeMonthlyBudget = (value) => {
|
|
2126
|
+
setMonthlyBudget(value);
|
|
2127
|
+
monthlyBudgetStore.set(value);
|
|
2128
|
+
};
|
|
2129
|
+
const changeWindowDays = (value) => {
|
|
2130
|
+
setWindowDays(value);
|
|
2131
|
+
usageWindowStore.set(value);
|
|
2132
|
+
};
|
|
2133
|
+
const changeChartMetric = (value) => {
|
|
2134
|
+
setChartMetric(value);
|
|
2135
|
+
chartMetricStore.set(value);
|
|
2136
|
+
};
|
|
761
2137
|
const primary = balance !== null && balance.balances.length > 0 ? balance.balances[0] : null;
|
|
762
2138
|
const recentDays = (usage?.daily ?? []).slice(-7);
|
|
763
2139
|
const avgDailyCost = recentDays.length > 0 ? recentDays.reduce((sum, d) => sum + d.cost, 0) / recentDays.length : 0;
|
|
764
2140
|
const balanceValue = primary !== null ? Number(primary.total) : Number.NaN;
|
|
765
2141
|
const balanceLow = lowBalance > 0 && Number.isFinite(balanceValue) && balanceValue < lowBalance;
|
|
766
2142
|
const daysLeft = avgDailyCost > 0 && Number.isFinite(balanceValue) ? balanceValue / avgDailyCost : null;
|
|
767
|
-
const daysLeftText = daysLeft === null ? "\u2014" : daysLeft >= 365 ? "
|
|
768
|
-
const runwayTitle = daysLeft === null ?
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
2143
|
+
const daysLeftText = daysLeft === null ? "\u2014" : daysLeft >= 365 ? t("balance.overYear") : t("balance.days", { count: daysLeft < 10 ? daysLeft.toFixed(1) : Math.round(daysLeft) });
|
|
2144
|
+
const runwayTitle = daysLeft === null ? t("balance.noRunway", { days: recentDays.length || 7 }) : t("balance.runwayTitle", { days: recentDays.length, cost: fmt(avgDailyCost) });
|
|
2145
|
+
const activeWindow = usage?.windows.find((window2) => window2.days === windowDays) ?? null;
|
|
2146
|
+
const activeWindowName = windowName(windowDays, t);
|
|
2147
|
+
const dailyBars = (activeWindow?.daily ?? []).map((d) => ({
|
|
2148
|
+
label: windowDays === 365 ? d.date.slice(5).replace("-", "/") : d.date.slice(8, 10),
|
|
2149
|
+
value: chartMetricValue(chartMetric, d),
|
|
2150
|
+
title: t("chart.tooltip", {
|
|
2151
|
+
head: d.date,
|
|
2152
|
+
tokens: fmtInt(d.total),
|
|
2153
|
+
cost: fmt(d.cost),
|
|
2154
|
+
calls: t("common.calls", { count: d.calls })
|
|
2155
|
+
})
|
|
773
2156
|
}));
|
|
774
|
-
const hourlyBars = (
|
|
2157
|
+
const hourlyBars = (activeWindow?.hourly ?? []).map((h) => ({
|
|
775
2158
|
label: String(h.hour),
|
|
776
|
-
value: h
|
|
777
|
-
title:
|
|
2159
|
+
value: chartMetricValue(chartMetric, h),
|
|
2160
|
+
title: t("chart.tooltip", {
|
|
2161
|
+
head: t("chart.hourPoint", { hour: h.hour }),
|
|
2162
|
+
tokens: fmtInt(h.total),
|
|
2163
|
+
cost: fmt(h.cost),
|
|
2164
|
+
calls: t("common.calls", { count: h.calls })
|
|
2165
|
+
})
|
|
778
2166
|
}));
|
|
779
2167
|
const chartPeak = (barMode === "daily" ? dailyBars : hourlyBars).reduce((peak, bar) => bar.value > peak ? bar.value : peak, 0);
|
|
780
|
-
const
|
|
2168
|
+
const chartPeakText = chartMetric === "cost" ? `\xA5${fmt(chartPeak)}` : chartMetric === "calls" ? t("common.callsShort", { count: fmtInt(chartPeak) }) : `${fmtCompact(chartPeak, locale)} tokens`;
|
|
2169
|
+
const modelList = [...activeWindow?.models ?? []].sort((a, b) => b.cost - a.cost);
|
|
2170
|
+
const canonicalModels = [...usage?.models ?? []].sort((a, b) => b.cost - a.cost);
|
|
781
2171
|
const availableModelKeys = new Set(modelList.map(modelKeyOf));
|
|
782
2172
|
const effectiveSelection = selectedModels.filter((k) => availableModelKeys.has(k));
|
|
783
2173
|
const colorOf = (key) => {
|
|
784
|
-
const index =
|
|
2174
|
+
const index = canonicalModels.findIndex((m) => modelKeyOf(m) === key);
|
|
785
2175
|
return MODEL_COLORS[(index < 0 ? 0 : index) % MODEL_COLORS.length];
|
|
786
2176
|
};
|
|
787
2177
|
const modelCostTotal = modelList.reduce((sum, m) => sum + m.cost, 0);
|
|
788
2178
|
const grouped = effectiveSelection.map((key) => {
|
|
789
2179
|
const m = modelList.find((x) => modelKeyOf(x) === key);
|
|
790
|
-
const name2 = m === void 0 ? key : modelNameOf(m);
|
|
2180
|
+
const name2 = m === void 0 ? key : modelNameOf(m, t);
|
|
791
2181
|
const color = colorOf(key);
|
|
792
2182
|
if (barMode === "daily") {
|
|
793
2183
|
return {
|
|
@@ -795,9 +2185,14 @@ function BalanceDashboard() {
|
|
|
795
2185
|
name: name2,
|
|
796
2186
|
color,
|
|
797
2187
|
bars: (m?.daily ?? []).map((p, i) => ({
|
|
798
|
-
label: (
|
|
799
|
-
value: p
|
|
800
|
-
title:
|
|
2188
|
+
label: windowDays === 365 ? (activeWindow?.daily ?? [])[i]?.date.slice(5).replace("-", "/") ?? "" : (activeWindow?.daily ?? [])[i]?.date.slice(8, 10) ?? "",
|
|
2189
|
+
value: chartMetricValue(chartMetric, p),
|
|
2190
|
+
title: t("chart.tooltip", {
|
|
2191
|
+
head: `${name2} \xB7 ${(activeWindow?.daily ?? [])[i]?.date ?? ""}`,
|
|
2192
|
+
tokens: fmtInt(p.total),
|
|
2193
|
+
cost: fmt(p.cost),
|
|
2194
|
+
calls: t("common.calls", { count: p.calls })
|
|
2195
|
+
})
|
|
801
2196
|
}))
|
|
802
2197
|
};
|
|
803
2198
|
}
|
|
@@ -807,134 +2202,174 @@ function BalanceDashboard() {
|
|
|
807
2202
|
color,
|
|
808
2203
|
bars: (m?.hourly ?? []).map((p, i) => ({
|
|
809
2204
|
label: String(i),
|
|
810
|
-
value: p
|
|
811
|
-
title:
|
|
2205
|
+
value: chartMetricValue(chartMetric, p),
|
|
2206
|
+
title: t("chart.tooltip", {
|
|
2207
|
+
head: `${name2} \xB7 ${t("chart.hourPoint", { hour: i })}`,
|
|
2208
|
+
tokens: fmtInt(p.total),
|
|
2209
|
+
cost: fmt(p.cost),
|
|
2210
|
+
calls: t("common.calls", { count: p.calls })
|
|
2211
|
+
})
|
|
812
2212
|
}))
|
|
813
2213
|
};
|
|
814
2214
|
});
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
2215
|
+
const dailyLabelEvery = windowDays === 7 ? 1 : windowDays === 30 ? 5 : windowDays === 90 ? 15 : 30;
|
|
2216
|
+
const dailyMinWidth = windowDays === 365 ? 1825 : void 0;
|
|
2217
|
+
const noticeText = notice === null ? null : t(
|
|
2218
|
+
notice.kind === "all" ? "error.refreshCached" : notice.kind === "balance" ? "error.balanceRefreshCached" : "error.usageRefreshCached",
|
|
2219
|
+
{ error: localizeApiError(notice.error, t, "error.query") }
|
|
2220
|
+
);
|
|
2221
|
+
const errorText = error === null ? null : localizeApiError(error, t, "error.query");
|
|
2222
|
+
const composerSafeAreaStyle = {
|
|
2223
|
+
"--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px`
|
|
2224
|
+
};
|
|
2225
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance", ref: rootRef, style: composerSafeAreaStyle, children: [
|
|
2226
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-row", children: [
|
|
2227
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-messages", children: [
|
|
2228
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2229
|
+
"span",
|
|
2230
|
+
{
|
|
2231
|
+
className: `dq-sync dq-sync--${syncState}`,
|
|
2232
|
+
role: "status",
|
|
2233
|
+
"aria-live": "polite",
|
|
2234
|
+
title: usageUpdatedAt === null ? t("status.neverSynced") : t("status.usageTime", { time: new Date(usageUpdatedAt).toLocaleString(locale === "en" ? "en-US" : "zh-CN", { timeZone: "Asia/Shanghai" }) }),
|
|
2235
|
+
children: [
|
|
2236
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sync-dot", "aria-hidden": "true" }),
|
|
2237
|
+
syncStatusText(syncState, usageUpdatedAt, freshnessNow, t)
|
|
2238
|
+
]
|
|
2239
|
+
}
|
|
2240
|
+
),
|
|
2241
|
+
noticeText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-warn", children: noticeText }),
|
|
2242
|
+
errorText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-error", children: errorText })
|
|
822
2243
|
] }),
|
|
823
|
-
/* @__PURE__ */ (0,
|
|
2244
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
824
2245
|
"button",
|
|
825
2246
|
{
|
|
826
2247
|
type: "button",
|
|
827
2248
|
className: "dq-refresh-btn",
|
|
828
|
-
title: "
|
|
2249
|
+
title: t("status.forceRefresh"),
|
|
829
2250
|
disabled: refreshing,
|
|
830
2251
|
"aria-busy": refreshing,
|
|
831
2252
|
onClick: () => {
|
|
832
2253
|
void load(true);
|
|
833
2254
|
},
|
|
834
2255
|
children: [
|
|
835
|
-
/* @__PURE__ */ (0,
|
|
836
|
-
refreshing ? "
|
|
2256
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-refresh-icon${refreshing ? " dq-refresh-icon--spin" : ""}`, children: "\u21BB" }),
|
|
2257
|
+
refreshing ? t("status.refreshing") : t("status.refresh")
|
|
837
2258
|
]
|
|
838
2259
|
}
|
|
839
2260
|
)
|
|
840
2261
|
] }),
|
|
841
|
-
balanceLow && /* @__PURE__ */ (0,
|
|
842
|
-
/* @__PURE__ */ (0,
|
|
843
|
-
/* @__PURE__ */ (0,
|
|
844
|
-
/* @__PURE__ */ (0,
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
primary?.currency,
|
|
849
|
-
" \u5DF2\u4F4E\u4E8E\u9884\u8B66\u7EBF ",
|
|
850
|
-
fmt(lowBalance)
|
|
851
|
-
] }),
|
|
852
|
-
daysLeft !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
853
|
-
"\uFF0C\u6309\u8FD1\u671F\u7528\u91CF\u4F30\u8BA1\u8FD8\u80FD\u6491 ",
|
|
854
|
-
daysLeftText
|
|
855
|
-
] }),
|
|
856
|
-
"\u3002",
|
|
857
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "dq-alert-link", href: "https://platform.deepseek.com/top_up", target: "_blank", rel: "noreferrer", children: "\u53BB\u5145\u503C" })
|
|
2262
|
+
balanceLow && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-alert", role: "status", children: [
|
|
2263
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-alert-icon", "aria-hidden": "true", children: "!" }),
|
|
2264
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
2265
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: t("alert.lowTitle", { balance: fmt(balanceValue), currency: primary?.currency, threshold: fmt(lowBalance) }) }),
|
|
2266
|
+
daysLeft !== null && t("alert.runway", { days: daysLeftText }),
|
|
2267
|
+
t("common.period"),
|
|
2268
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: "dq-alert-link", href: "https://platform.deepseek.com/top_up", target: "_blank", rel: "noreferrer", children: t("alert.topUp") })
|
|
858
2269
|
] })
|
|
859
2270
|
] }),
|
|
860
|
-
/* @__PURE__ */ (0,
|
|
861
|
-
/* @__PURE__ */ (0,
|
|
862
|
-
primary !== null ? /* @__PURE__ */ (0,
|
|
863
|
-
/* @__PURE__ */ (0,
|
|
864
|
-
/* @__PURE__ */ (0,
|
|
865
|
-
/* @__PURE__ */ (0,
|
|
866
|
-
/* @__PURE__ */ (0,
|
|
2271
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
|
|
2272
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("balance.title") }),
|
|
2273
|
+
primary !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance-grid", children: [
|
|
2274
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
2275
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("balance.remaining") }),
|
|
2276
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "dq-stat-value dq-remaining", children: [
|
|
2277
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { title: t("balance.breakdownTitle"), children: [
|
|
867
2278
|
fmt(primary.total),
|
|
868
2279
|
" ",
|
|
869
2280
|
primary.currency
|
|
870
2281
|
] }),
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
872
|
-
/* @__PURE__ */ (0,
|
|
873
|
-
|
|
874
|
-
fmt(primary.toppedUp)
|
|
875
|
-
] }),
|
|
876
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dq-remaining-granted", children: [
|
|
877
|
-
"\u8D60\u9001\u989D\u5EA6 ",
|
|
878
|
-
fmt(primary.granted)
|
|
879
|
-
] })
|
|
2282
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-remaining-breakdown", children: [
|
|
2283
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("balance.toppedUp", { amount: fmt(primary.toppedUp) }) }),
|
|
2284
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-remaining-granted", children: t("balance.granted", { amount: fmt(primary.granted) }) })
|
|
880
2285
|
] })
|
|
881
2286
|
] })
|
|
882
2287
|
] }),
|
|
883
|
-
/* @__PURE__ */ (0,
|
|
884
|
-
/* @__PURE__ */ (0,
|
|
885
|
-
/* @__PURE__ */ (0,
|
|
886
|
-
"
|
|
2288
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
2289
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("balance.runway") }),
|
|
2290
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2291
|
+
"details",
|
|
887
2292
|
{
|
|
888
2293
|
className: `dq-stat-value dq-runway${daysLeft !== null && daysLeft < 3 ? " dq-stat-value--bad" : ""}`,
|
|
889
|
-
|
|
890
|
-
|
|
2294
|
+
open: runwayHovering || runwayManuallyOpened,
|
|
2295
|
+
onPointerEnter: () => setRunwayHovering(true),
|
|
2296
|
+
onPointerLeave: () => setRunwayHovering(false),
|
|
2297
|
+
children: [
|
|
2298
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2299
|
+
"summary",
|
|
2300
|
+
{
|
|
2301
|
+
title: t("balance.runwayBreakdownTitle"),
|
|
2302
|
+
onClick: (e) => {
|
|
2303
|
+
e.preventDefault();
|
|
2304
|
+
setRunwayManuallyOpened((v) => !v);
|
|
2305
|
+
},
|
|
2306
|
+
children: daysLeftText
|
|
2307
|
+
}
|
|
2308
|
+
),
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-runway-detail", children: runwayTitle })
|
|
2310
|
+
]
|
|
891
2311
|
}
|
|
892
2312
|
)
|
|
893
2313
|
] }),
|
|
894
|
-
/* @__PURE__ */ (0,
|
|
895
|
-
] }) : loadingBalance ? /* @__PURE__ */ (0,
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("common.status"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `dq-stat-value${balance?.isAvailable === false ? " dq-stat-value--bad" : " dq-stat-value--ok"}`, children: balance?.isAvailable === false ? t("common.unavailable") : t("common.available") }) })
|
|
2315
|
+
] }) : loadingBalance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BalanceSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("balance.empty") })
|
|
2316
|
+
] }),
|
|
2317
|
+
props.sessionId !== void 0 && props.sessionId !== "" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
|
|
2318
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("sessionUsage.title") }),
|
|
2319
|
+
sessionUsage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageCard, { data: sessionUsage }) : loadingSessionUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageSkeleton, {}) : sessionUsageError !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty dq-empty--error", children: localizeApiError(sessionUsageError, t, "error.query") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessionUsage.empty") })
|
|
896
2320
|
] }),
|
|
897
|
-
/* @__PURE__ */ (0,
|
|
898
|
-
/* @__PURE__ */ (0,
|
|
899
|
-
usage !== null ? /* @__PURE__ */ (0,
|
|
900
|
-
/* @__PURE__ */ (0,
|
|
901
|
-
/* @__PURE__ */ (0,
|
|
902
|
-
/* @__PURE__ */ (0,
|
|
2321
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
|
|
2322
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("overview.title") }),
|
|
2323
|
+
usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-period-grid", children: [
|
|
2324
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Period, { label: t("overview.today"), period: usage.summary.today, previous: usage.summary.yesterday, compare: t("overview.vsYesterday") }),
|
|
2325
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Period, { label: t("overview.month"), period: usage.summary.month, previous: usage.summary.lastMonthToDate, compare: t("overview.vsLastMonth") }),
|
|
2326
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
903
2327
|
Period,
|
|
904
2328
|
{
|
|
905
|
-
label: "
|
|
2329
|
+
label: t("overview.total"),
|
|
906
2330
|
period: { total: usage.totals.total, cost: usage.totals.cost, calls: usage.totals.calls }
|
|
907
2331
|
}
|
|
908
2332
|
)
|
|
909
|
-
] }) : loadingUsage ? /* @__PURE__ */ (0,
|
|
910
|
-
/* @__PURE__ */ (0,
|
|
911
|
-
/* @__PURE__ */ (0,
|
|
912
|
-
/* @__PURE__ */ (0,
|
|
913
|
-
] }, i)) }) : /* @__PURE__ */ (0,
|
|
914
|
-
usage !== null && /* @__PURE__ */ (0,
|
|
2333
|
+
] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-period-grid", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-period", children: [
|
|
2334
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 40, h: 11 }),
|
|
2335
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 104, h: 24 }),
|
|
2336
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 132, h: 11 })
|
|
2337
|
+
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("overview.empty") }),
|
|
2338
|
+
usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2339
|
+
BudgetMeter,
|
|
2340
|
+
{
|
|
2341
|
+
spent: usage.summary.month.cost,
|
|
2342
|
+
budget: monthlyBudget,
|
|
2343
|
+
onConfigure: () => budgetInputRef.current?.focus()
|
|
2344
|
+
}
|
|
2345
|
+
),
|
|
2346
|
+
usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
|
|
915
2347
|
] }),
|
|
916
|
-
/* @__PURE__ */ (0,
|
|
917
|
-
/* @__PURE__ */ (0,
|
|
918
|
-
|
|
919
|
-
/* @__PURE__ */ (0,
|
|
920
|
-
/* @__PURE__ */ (0,
|
|
921
|
-
/* @__PURE__ */ (0,
|
|
922
|
-
|
|
923
|
-
|
|
2348
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
|
|
2349
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-head", children: [
|
|
2350
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("usage.trendTitle") }),
|
|
2351
|
+
usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-actions", children: [
|
|
2352
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageWindowPicker, { value: windowDays, onChange: changeWindowDays }),
|
|
2353
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExportMenu, { usage })
|
|
2354
|
+
] })
|
|
2355
|
+
] }),
|
|
2356
|
+
usage !== null && activeWindow !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2357
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-usage-totals", children: [
|
|
2358
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("usage.windowInput", { window: activeWindowName }), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtCompact(activeWindow.totals.input, locale) }) }),
|
|
2359
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("common.output"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtCompact(activeWindow.totals.output, locale) }) }),
|
|
2360
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("common.cacheHit"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtCompact(activeWindow.totals.cache, locale) }) }),
|
|
2361
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("usage.modelCalls"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtInt(activeWindow.totals.calls) }) })
|
|
924
2362
|
] }),
|
|
925
|
-
/* @__PURE__ */ (0,
|
|
926
|
-
/* @__PURE__ */ (0,
|
|
927
|
-
barMode === "daily" ? "
|
|
928
|
-
chartPeak > 0 && /* @__PURE__ */ (0,
|
|
929
|
-
"\u5CF0\u503C ",
|
|
930
|
-
fmtCompact(chartPeak),
|
|
931
|
-
" tokens"
|
|
932
|
-
] })
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-block-head", children: [
|
|
2364
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-title", children: [
|
|
2365
|
+
barMode === "daily" ? t("chart.dailyTitle", { window: activeWindowName, metric: chartMetricName(chartMetric, t) }) : t("chart.hourlyTitle", { window: activeWindowName, metric: chartMetricName(chartMetric, t) }),
|
|
2366
|
+
chartPeak > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-chart-peak", children: t("chart.peak", { value: chartPeakText }) })
|
|
933
2367
|
] }),
|
|
934
|
-
/* @__PURE__ */ (0,
|
|
935
|
-
/* @__PURE__ */ (0,
|
|
936
|
-
/* @__PURE__ */ (0,
|
|
937
|
-
|
|
2368
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-controls", children: [
|
|
2369
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModelPicker, { models: modelList, selected: effectiveSelection, colorOf, onChange: setSelectedModels }),
|
|
2370
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartMetricPicker, { value: chartMetric, onChange: changeChartMetric }),
|
|
2371
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-switch", role: "tablist", "aria-label": t("chart.dimensionLabel"), children: [
|
|
2372
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
938
2373
|
"button",
|
|
939
2374
|
{
|
|
940
2375
|
type: "button",
|
|
@@ -942,10 +2377,10 @@ function BalanceDashboard() {
|
|
|
942
2377
|
"aria-selected": barMode === "daily",
|
|
943
2378
|
className: `dq-chart-switch-btn${barMode === "daily" ? " dq-chart-switch-btn--on" : ""}`,
|
|
944
2379
|
onClick: () => setBarMode("daily"),
|
|
945
|
-
children: "
|
|
2380
|
+
children: t("chart.daily")
|
|
946
2381
|
}
|
|
947
2382
|
),
|
|
948
|
-
/* @__PURE__ */ (0,
|
|
2383
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
949
2384
|
"button",
|
|
950
2385
|
{
|
|
951
2386
|
type: "button",
|
|
@@ -953,81 +2388,87 @@ function BalanceDashboard() {
|
|
|
953
2388
|
"aria-selected": barMode === "hourly",
|
|
954
2389
|
className: `dq-chart-switch-btn${barMode === "hourly" ? " dq-chart-switch-btn--on" : ""}`,
|
|
955
2390
|
onClick: () => setBarMode("hourly"),
|
|
956
|
-
children: "
|
|
2391
|
+
children: t("chart.hourly")
|
|
957
2392
|
}
|
|
958
2393
|
)
|
|
959
2394
|
] })
|
|
960
2395
|
] })
|
|
961
2396
|
] }),
|
|
962
|
-
grouped.length > 0 ? /* @__PURE__ */ (0,
|
|
963
|
-
/* @__PURE__ */ (0,
|
|
2397
|
+
activeWindow.totals.calls === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.noWindowData", { window: activeWindowName }) }) : grouped.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
964
2399
|
GroupedBars,
|
|
965
2400
|
{
|
|
966
2401
|
series: grouped,
|
|
967
2402
|
height: barMode === "daily" ? 120 : 100,
|
|
968
|
-
labelEvery: barMode === "daily" ?
|
|
2403
|
+
labelEvery: barMode === "daily" ? dailyLabelEvery : 3,
|
|
2404
|
+
minWidth: barMode === "daily" ? dailyMinWidth : void 0
|
|
969
2405
|
}
|
|
970
2406
|
),
|
|
971
|
-
/* @__PURE__ */ (0,
|
|
972
|
-
/* @__PURE__ */ (0,
|
|
2407
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-legend", children: grouped.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-legend-item", children: [
|
|
2408
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: s.color } }),
|
|
973
2409
|
s.name
|
|
974
2410
|
] }, s.key)) })
|
|
975
|
-
] }) : barMode === "daily" ? /* @__PURE__ */ (0,
|
|
976
|
-
/* @__PURE__ */ (0,
|
|
977
|
-
|
|
978
|
-
] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(UsageSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dq-empty", children: "\u8FD8\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u3002\u5728 DSH \u91CC\u8DD1\u4E00\u8F6E\u5BF9\u8BDD\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u9010\u5929 / \u9010\u5C0F\u65F6\u7EDF\u8BA1\u4E0E\u8D39\u7528\u4F30\u7B97\u3002" })
|
|
2411
|
+
] }) : barMode === "daily" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: dailyBars, height: 120, labelEvery: dailyLabelEvery, minWidth: dailyMinWidth }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: hourlyBars, height: 100, labelEvery: 3 }),
|
|
2412
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CoverageDiagnostics, { coverage: usage.coverage })
|
|
2413
|
+
] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") })
|
|
979
2414
|
] }),
|
|
980
|
-
/* @__PURE__ */ (0,
|
|
981
|
-
/* @__PURE__ */ (0,
|
|
982
|
-
usage !== null ? /* @__PURE__ */ (0,
|
|
983
|
-
/* @__PURE__ */ (0,
|
|
984
|
-
/* @__PURE__ */ (0,
|
|
985
|
-
|
|
986
|
-
|
|
2415
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2416
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("heatmap.title") }),
|
|
2417
|
+
usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2418
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-chart-title", children: t("chart.heatmapTitle") }),
|
|
2419
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Heatmap, { data: usage.heatmap })
|
|
2420
|
+
] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeatmapSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") })
|
|
2421
|
+
] }),
|
|
2422
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2423
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("peak.title") }),
|
|
2424
|
+
usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PeakCard, { split: usage.peakSplit, pricing: usage.pricing, currentCost: usage.totals.cost }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2425
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-track" }),
|
|
2426
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
|
|
2427
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 120, h: 11 }),
|
|
2428
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 96, h: 20 })
|
|
987
2429
|
] })
|
|
988
|
-
] }) : /* @__PURE__ */ (0,
|
|
2430
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") })
|
|
989
2431
|
] }),
|
|
990
|
-
/* @__PURE__ */ (0,
|
|
991
|
-
/* @__PURE__ */ (0,
|
|
992
|
-
usage !== null ? /* @__PURE__ */ (0,
|
|
993
|
-
/* @__PURE__ */ (0,
|
|
994
|
-
/* @__PURE__ */ (0,
|
|
995
|
-
] }, i)) }) : /* @__PURE__ */ (0,
|
|
2432
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2433
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("cache.title") }),
|
|
2434
|
+
usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CacheCard, { totals: usage.totals }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-cache-figures", children: [60, 96, 72].map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
2435
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
|
|
2436
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
|
|
2437
|
+
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
|
|
996
2438
|
] }),
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
999
|
-
|
|
1000
|
-
/* @__PURE__ */ (0,
|
|
1001
|
-
/* @__PURE__ */ (0,
|
|
1002
|
-
/* @__PURE__ */ (0,
|
|
1003
|
-
] }, i)) }) : /* @__PURE__ */ (0,
|
|
2439
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2440
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("models.title", { window: activeWindowName }) }),
|
|
2441
|
+
activeWindow !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModelRanking, { models: modelList, totalCost: modelCostTotal, colorOf }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank", children: [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-row", children: [
|
|
2442
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 148, h: 13 }),
|
|
2443
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank-track" }),
|
|
2444
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 230, h: 11 })
|
|
2445
|
+
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("models.empty") })
|
|
1004
2446
|
] }),
|
|
1005
|
-
/* @__PURE__ */ (0,
|
|
1006
|
-
/* @__PURE__ */ (0,
|
|
1007
|
-
|
|
1008
|
-
/* @__PURE__ */ (0,
|
|
1009
|
-
/* @__PURE__ */ (0,
|
|
1010
|
-
/* @__PURE__ */ (0,
|
|
1011
|
-
] }, i)) }) : /* @__PURE__ */ (0,
|
|
2447
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2448
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("sessions.title", { window: activeWindowName }) }),
|
|
2449
|
+
activeWindow !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionRanking, { sessions: activeWindow.sessions, count: activeWindow.sessionCount, totalCost: activeWindow.totals.cost }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-sessions", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-session", children: [
|
|
2450
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 200, h: 13 }),
|
|
2451
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-session-track" }),
|
|
2452
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 250, h: 11 })
|
|
2453
|
+
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessions.empty") })
|
|
1012
2454
|
] }),
|
|
1013
|
-
/* @__PURE__ */ (0,
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
1015
|
-
/* @__PURE__ */ (0,
|
|
1016
|
-
|
|
1017
|
-
/* @__PURE__ */ (0,
|
|
1018
|
-
/* @__PURE__ */ (0,
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: widgetOn, onChange: toggle }),
|
|
1025
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u663E\u793A\u53F3\u4E0B\u89D2\u60AC\u6D6E\u989D\u5EA6\u7A97\u53E3" })
|
|
2455
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2456
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("settings.title") }),
|
|
2457
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-links-title", children: t("links.title") }),
|
|
2458
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-links", children: [
|
|
2459
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/usage", children: t("links.usage") }),
|
|
2460
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/api_keys", children: t("links.apiKey") }),
|
|
2461
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://status.deepseek.com", children: t("links.status") })
|
|
2462
|
+
] }),
|
|
2463
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-toggle", children: [
|
|
2464
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: widgetOn, onChange: toggle }),
|
|
2465
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.widget") })
|
|
1026
2466
|
] }),
|
|
1027
|
-
/* @__PURE__ */ (0,
|
|
1028
|
-
|
|
1029
|
-
/* @__PURE__ */ (0,
|
|
1030
|
-
|
|
2467
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-toggle-hint", children: t("settings.widgetHint") }),
|
|
2468
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
|
|
2469
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
|
|
2470
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
|
|
2471
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1031
2472
|
"input",
|
|
1032
2473
|
{
|
|
1033
2474
|
id: "dq-low-balance",
|
|
@@ -1039,7 +2480,27 @@ function BalanceDashboard() {
|
|
|
1039
2480
|
onChange: (e) => changeLowBalance(Math.max(0, Number(e.target.value) || 0))
|
|
1040
2481
|
}
|
|
1041
2482
|
),
|
|
1042
|
-
/* @__PURE__ */ (0,
|
|
2483
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: lowBalance > 0 ? t("settings.lowBalanceOn", { amount: fmt(lowBalance), currency: primary?.currency ?? "CNY" }) : t("settings.lowBalanceOff") })
|
|
2484
|
+
] })
|
|
2485
|
+
] }),
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
|
|
2487
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-monthly-budget", children: t("settings.monthlyBudget") }),
|
|
2488
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
|
|
2489
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2490
|
+
"input",
|
|
2491
|
+
{
|
|
2492
|
+
ref: budgetInputRef,
|
|
2493
|
+
id: "dq-monthly-budget",
|
|
2494
|
+
className: "dq-number",
|
|
2495
|
+
type: "number",
|
|
2496
|
+
min: 0,
|
|
2497
|
+
step: 10,
|
|
2498
|
+
value: monthlyBudget,
|
|
2499
|
+
"aria-describedby": "dq-monthly-budget-hint",
|
|
2500
|
+
onChange: (e) => changeMonthlyBudget(Math.max(0, Number(e.target.value) || 0))
|
|
2501
|
+
}
|
|
2502
|
+
),
|
|
2503
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-monthly-budget-hint", className: "dq-setting-hint", children: monthlyBudget > 0 ? t("settings.monthlyBudgetOn", { amount: fmt(monthlyBudget) }) : t("settings.monthlyBudgetOff") })
|
|
1043
2504
|
] })
|
|
1044
2505
|
] })
|
|
1045
2506
|
] })
|
|
@@ -1048,7 +2509,8 @@ function BalanceDashboard() {
|
|
|
1048
2509
|
|
|
1049
2510
|
// src/client/styles.ts
|
|
1050
2511
|
var css = `
|
|
1051
|
-
.dsh-quota-root{position:absolute;right:16px;bottom:16px;z-index:2147483000;pointer-events:auto;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:13px;line-height:1.45;color:var(--dsw-alias-label-primary,#1f2328);transition:left .28s cubic-bezier(.22,1,.36,1),top .28s cubic-bezier(.22,1,.36,1)}
|
|
2512
|
+
.dsh-quota-root{position:absolute;right:16px;bottom:16px;z-index:2147483000;pointer-events:auto;opacity:1;visibility:visible;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:13px;line-height:1.45;color:var(--dsw-alias-label-primary,#1f2328);transition:left .28s cubic-bezier(.22,1,.36,1),top .28s cubic-bezier(.22,1,.36,1),opacity .16s ease-out,visibility 0s linear}
|
|
2513
|
+
.dsh-quota-root.dsh-quota-root--dashboard{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .14s ease-out,visibility 0s linear .14s}
|
|
1052
2514
|
.dsh-quota-root.dsh-quota-dragging{transition:none}
|
|
1053
2515
|
.dsh-quota-card,.dsh-quota-card *{box-sizing:border-box}
|
|
1054
2516
|
.dsh-quota-card{background:var(--dsw-alias-bg-overlay,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);padding:12px 14px;min-width:208px;max-width:270px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
|
|
@@ -1062,7 +2524,8 @@ var css = `
|
|
|
1062
2524
|
.dsh-quota-dot--warn{background:var(--dsw-alias-state-warning-primary,#bf8700)}
|
|
1063
2525
|
.dsh-quota-total--low{color:var(--dsw-alias-state-warning-primary,#9a6700)!important}
|
|
1064
2526
|
.dsh-quota-dot--idle{background:var(--dsw-alias-label-secondary,#59636e);opacity:.6}
|
|
1065
|
-
.dsh-quota-collapsed-total{
|
|
2527
|
+
.dsh-quota-collapsed-total-row{margin-top:6px}
|
|
2528
|
+
.dsh-quota-collapsed-total{font-weight:700;font-size:15px;color:var(--dsw-alias-state-business-primary,#0969da);font-variant-numeric:tabular-nums}
|
|
1066
2529
|
.dsh-quota-actions{display:flex;gap:2px;flex:none}
|
|
1067
2530
|
.dsh-quota-btn{border:none;background:transparent;cursor:pointer;color:var(--dsw-alias-label-secondary,#59636e);padding:3px 7px;border-radius:6px;font-size:13px;line-height:1}
|
|
1068
2531
|
.dsh-quota-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.06));color:var(--dsw-alias-label-primary,#1f2328)}
|
|
@@ -1077,27 +2540,114 @@ var css = `
|
|
|
1077
2540
|
.dsh-quota-label{color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1078
2541
|
.dsh-quota-value{color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
1079
2542
|
.dsh-quota-error{color:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
1080
|
-
|
|
2543
|
+
/* \u5361\u7247\u95F4\u8DDD\uFF08\u6B64\u5904 gap\uFF09\u523B\u610F\u5927\u4E8E\u5361\u5185\u5C0F\u8282\u95F4\u8DDD\uFF08margin-top:12/14/16px \u90A3\u4E00\u6863\uFF09\uFF0C\u5236\u9020"\u5361\u7247\u5916\u758F\u3001\u5361\u7247\u5185\u5BC6"\u7684
|
|
2544
|
+
\u758F\u5BC6\u5BF9\u6BD4\uFF1A24px \u76F8\u5BF9\u5361\u5185\u6700\u5927\u6863 16px \u4ECD\u6709 1.5 \u500D\u7EA7\u5DEE\uFF0C\u76F8\u5BF9\u5E38\u89C1\u6863 12px \u6709 2 \u500D\u7EA7\u5DEE\uFF0C\u4E0D\u4E0E .dq-card \u7684
|
|
2545
|
+
padding\uFF0816px/20px\uFF0C\u5361\u7247\u5185\u90E8\u547C\u5438\u611F\uFF0C\u8F6E\u6B21 36 \u5DF2\u5B9A\uFF09\u6DF7\u540C\u3002 */
|
|
2546
|
+
.dq-balance{display:flex;flex-direction:column;gap:24px;padding:20px 24px 48px;max-width:860px;margin:0 auto;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2547
|
+
@media (max-width:620px){.dq-balance{box-sizing:border-box;max-width:calc(100vw - 56px);padding:16px 16px calc(var(--dq-composer-h,126px) + 16px)}}
|
|
1081
2548
|
.dq-card{background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-radius:12px;padding:16px}
|
|
1082
2549
|
.dq-card-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:0 0 12px;letter-spacing:.02em}
|
|
2550
|
+
.dq-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}
|
|
2551
|
+
.dq-card-head .dq-card-title{margin:0}
|
|
2552
|
+
.dq-card-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;min-width:0}
|
|
2553
|
+
/* \u4E3B\u5361\u7247\uFF08\u4F59\u989D/\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017/\u6D88\u8017\u6982\u89C8/\u7528\u91CF\u8D8B\u52BF\uFF0C\u8F6E\u6B21 45 \u65B0\u589E\u300C\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017\u300D\uFF09\u89C6\u89C9\u6743\u91CD\u9AD8\u4E8E\u6B21\u7EA7\u5206\u6790
|
|
2554
|
+
\u5361\u7247\u4E0E\u5BFC\u822A\u7C7B\u5361\u7247\uFF1A\u53EA\u8C03\u6392\u7248\u5C5E\u6027\u2014\u2014\u5185\u8FB9\u8DDD\u3001\u6807\u9898\u5B57\u53F7\u5B57\u91CD\u4E0E\u53D6\u8272\u3001\u8FB9\u6846\u6DF1\u6D45\u2014\u2014\u4E0D\u5F15\u5165\u65B0\u914D\u8272\u4F53\u7CFB\u3002\u4E24\u6761\u89C4\u5219
|
|
2555
|
+
\u4E0E\u4E0A\u9762\u7684\u57FA\u7840\u89C4\u5219\u540C\u4E3A\u5355\u7C7B\u9009\u62E9\u5668\u4E14\u6E90\u7801\u987A\u5E8F\u66F4\u9760\u540E\uFF0C\u5929\u7136\u8986\u76D6\uFF0C\u4E0D\u9700\u8981 !important\u3002\u6B21\u7EA7\u5206\u6790\u5361\u7247\uFF08\u7528\u91CF
|
|
2556
|
+
\u70ED\u529B\u56FE/\u9AD8\u5CF0\u95F2\u65F6/\u7F13\u5B58\u547D\u4E2D/\u6A21\u578B\u6392\u884C/\u4F1A\u8BDD\u6392\u884C\uFF09\u4E0E\u5BFC\u822A\u7C7B\u5361\u7247\uFF08\u8BBE\u7F6E\uFF0C\u542B\u8F6E\u6B21 39 \u5E76\u5165\u7684\u5B98\u65B9\u5E73\u53F0\u94FE\u63A5\uFF09\u90FD\u4E0D\u52A0
|
|
2557
|
+
\u8FD9\u4E2A\u4FEE\u9970\u7C7B\uFF0C\u7EF4\u6301\u539F\u6709 .dq-card \u57FA\u7EBF\uFF0C\u5F62\u6210\u4E24\u6863\u6743\u91CD\u3002\u300C\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017\u300D\u5347\u7EA7\u4E3A\u4E3B\u5361\u7247\u7684\u7406\u7531\uFF1A\u5B83\u548C\u4F59\u989D\u4E00\u6837\u662F
|
|
2558
|
+
\u6253\u5F00\u8FD9\u4E2A\u300C\u989D\u5EA6\u300Dtab \u65F6\u4E0A\u4E0B\u6587\u6700\u76F4\u63A5\u76F8\u5173\u7684\u6570\u5B57\uFF08\u8FD9\u4E2A tab \u6302\u5728\u5177\u4F53\u4F1A\u8BDD\u5185\u90E8\uFF0C\u89C1 DESIGN_NOTES\u300C\u4FE1\u606F\u5C42\u7EA7\u300D\uFF09\uFF0C
|
|
2559
|
+
\u4E0D\u5C5E\u4E8E\u539F\u6709\u8D26\u6237\u5168\u5C40\u5C42\u7EA7 1-3 \u7684\u4EFB\u4F55\u4E00\u7EA7\uFF0C\u800C\u662F\u4E0E\u4E4B\u6B63\u4EA4\u7684\u53E6\u4E00\u4E2A\u7EF4\u5EA6\uFF0C\u4F46\u540C\u6837\u662F\u9996\u5C4F\u5FC5\u8BFB\uFF0C\u56E0\u6B64\u540C\u6837\u7ED9
|
|
2560
|
+
primary \u6743\u91CD\uFF0C\u4E0D\u65B0\u5F00\u7B2C\u4E09\u6863\u3002 */
|
|
2561
|
+
.dq-card--primary{padding:20px;border-color:var(--dsw-alias-border-l2,rgba(0,0,0,.12))}
|
|
2562
|
+
.dq-card--primary .dq-card-title{font-size:13px;font-weight:700;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2563
|
+
.dq-window-switch{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.08))}
|
|
2564
|
+
.dq-window-btn{border:0;border-radius:6px;padding:4px 8px;background:transparent;color:var(--dsw-alias-label-secondary,#59636e);font-size:12px;white-space:nowrap;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}
|
|
2565
|
+
.dq-window-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2566
|
+
.dq-window-btn--on{background:var(--dsw-alias-bg-overlay,#fff);color:var(--dsw-alias-label-primary,#1f2328);box-shadow:0 1px 3px rgba(0,0,0,.12)}
|
|
2567
|
+
.dq-window-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
2568
|
+
@media (max-width:620px){.dq-card-head{align-items:flex-start;flex-wrap:wrap}.dq-card-actions{width:100%;justify-content:space-between}}
|
|
2569
|
+
.dq-export{position:relative;flex:none}
|
|
2570
|
+
.dq-export-btn{min-width:58px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#59636e);border-radius:8px;padding:4px 10px;font-size:12px;cursor:pointer;transition:border-color .15s ease,color .15s ease,background .15s ease}
|
|
2571
|
+
.dq-export-btn:hover{border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
|
|
2572
|
+
.dq-export-btn--done{border-color:rgba(45,164,78,.35);color:var(--dsw-alias-state-success-primary,#1a7f37);background:rgba(45,164,78,.08)}
|
|
2573
|
+
.dq-export-menu{position:absolute;right:0;top:calc(100% + 6px);z-index:30;min-width:180px;padding:6px;background:var(--dsw-alias-bg-overlay,#fff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.16);display:flex;flex-direction:column;gap:2px}
|
|
2574
|
+
.dq-export-menu button{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;border:0;border-radius:6px;padding:7px 8px;background:transparent;color:var(--dsw-alias-label-primary,#1f2328);font-size:12px;text-align:left;cursor:pointer}
|
|
2575
|
+
.dq-export-menu button:hover,.dq-export-menu button:focus-visible{background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.06))}
|
|
2576
|
+
.dq-export-menu button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:-1px}
|
|
2577
|
+
.dq-export-menu small{font-size:10.5px;font-weight:400;color:var(--dsw-alias-label-tertiary,#59636e);white-space:nowrap}
|
|
2578
|
+
/* Small-text tier system (\u8F6E\u6B21 41 \u6536\u655B): two whole-pixel tiers replace what used to be four values
|
|
2579
|
+
(11/11.5/12/12.5px) spaced only 0.5px apart \u2014 a gap browsers subpixel-round away, so it read as one
|
|
2580
|
+
size with extra maintenance cost, not an actual visual hierarchy. 11px = "micro" chrome the user
|
|
2581
|
+
scans rather than reads (dt-style stat/period labels, delta/status/coverage pills, share %, tags,
|
|
2582
|
+
the widget grip) \u2014 kept as-is. 12px = "secondary text" (section titles, hint/footnote/legend/sub
|
|
2583
|
+
prose, table cells, tooltip copy, alert/empty-state sentences, and filter/segmented-control button
|
|
2584
|
+
labels like .dq-window-btn/.dq-chart-switch-btn, which are the same control family and should share
|
|
2585
|
+
one size) \u2014 absorbed the old 11.5px and 12.5px selectors, all by rounding toward the nearer tier
|
|
2586
|
+
(11.5\u219212 grows slightly for legibility, safe because none of the affected containers are width-
|
|
2587
|
+
capped; 12.5\u219212 shrinks by 0.5px on two full-sentence blocks, imperceptible). Weight/color still do
|
|
2588
|
+
the job of separating titles from body text within the 12px tier, same as before this merge. Do not
|
|
2589
|
+
reintroduce a fractional-px value in this range; if a selector needs to stand apart from these two,
|
|
2590
|
+
it belongs in an already-established different tier (10px icon glyphs, 13px+ hero-adjacent text). */
|
|
1083
2591
|
.dq-balance-grid{display:flex;flex-wrap:wrap;gap:12px 24px}
|
|
1084
2592
|
.dq-stat{min-width:110px}
|
|
1085
2593
|
.dq-stat-label{font-size:11px;color:var(--dsw-alias-label-tertiary,#59636e);margin-bottom:2px}
|
|
1086
2594
|
.dq-stat-value{font-size:18px;font-weight:650;color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
1087
2595
|
.dq-stat-value--ok{color:var(--dsw-alias-state-success-primary,#2da44e)}
|
|
1088
2596
|
.dq-stat-value--bad{color:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
1089
|
-
.dq-runway
|
|
1090
|
-
.dq-
|
|
2597
|
+
/* \u8F6E\u6B21 43: .dq-runway is a controlled <details> (see dashboard.tsx) \u2014 hover/toggle state
|
|
2598
|
+
drives the real open attribute now, so .dq-runway-detail only needs to react to [open];
|
|
2599
|
+
an unopened <details>'s non-summary children don't actually render in current Chromium no
|
|
2600
|
+
matter what display value author CSS gives them (:hover{display:block} alone is a dead end). */
|
|
2601
|
+
.dq-runway{position:relative}
|
|
2602
|
+
.dq-runway>summary{display:inline-block;list-style:none;border-radius:4px;cursor:pointer;text-decoration:underline dotted var(--dsw-alias-border-l2,rgba(0,0,0,.25));text-underline-offset:4px}
|
|
2603
|
+
.dq-runway>summary::-webkit-details-marker{display:none}
|
|
2604
|
+
.dq-runway>summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:3px}
|
|
2605
|
+
.dq-runway-detail{display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:20;max-width:240px;padding:8px 10px;background:var(--dsw-alias-bg-overlay,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.15);white-space:normal;font-size:12px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2606
|
+
.dq-runway[open] .dq-runway-detail{display:block}
|
|
2607
|
+
/* hero number for the balance, matching .dq-period-cost; relies on cascade order (declared after
|
|
2608
|
+
.dq-stat-value, same specificity) to win over the .dq-stat-value it's paired with in JSX \u2014 keep it below */
|
|
2609
|
+
.dq-remaining{position:relative;font-size:22px;font-weight:680;letter-spacing:-.01em;line-height:1.25}
|
|
2610
|
+
.dq-remaining>summary{display:inline-block;list-style:none;border-radius:4px;cursor:pointer;text-decoration:underline dotted var(--dsw-alias-border-l2,rgba(0,0,0,.25));text-underline-offset:4px}
|
|
2611
|
+
.dq-remaining>summary::-webkit-details-marker{display:none}
|
|
2612
|
+
.dq-remaining>summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:3px}
|
|
1091
2613
|
.dq-remaining-breakdown{display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:20;flex-direction:column;gap:4px;padding:8px 10px;background:var(--dsw-alias-bg-overlay,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.15);white-space:nowrap;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
1092
|
-
.dq-remaining:hover .dq-remaining-breakdown{display:flex}
|
|
2614
|
+
.dq-remaining:hover .dq-remaining-breakdown,.dq-remaining[open] .dq-remaining-breakdown{display:flex}
|
|
1093
2615
|
.dq-remaining-granted{color:var(--dsw-alias-label-tertiary,#59636e);font-weight:400;font-size:11px}
|
|
1094
|
-
.dq-
|
|
1095
|
-
|
|
1096
|
-
.
|
|
2616
|
+
/* narrow screens: .dq-balance-grid wraps its 3 stats onto separate lines, so the absolutely-positioned
|
|
2617
|
+
.dq-remaining-breakdown/.dq-runway-detail popovers can float down over whichever stat now sits in the
|
|
2618
|
+
next row (e.g. "\u4F59\u989D\u660E\u7EC6" open covering "\u9884\u8BA1\u53EF\u7528"'s clickable summary) \u2014 force each stat onto its own
|
|
2619
|
+
full-width row and switch both popovers to normal in-flow layout so an open one pushes its own row
|
|
2620
|
+
taller instead of overlaying a sibling; desktop keeps the original hover-overlay behavior unchanged */
|
|
2621
|
+
@media (max-width:620px){
|
|
2622
|
+
.dq-balance-grid .dq-stat{flex:1 1 100%}
|
|
2623
|
+
.dq-remaining-breakdown,.dq-runway-detail{position:static;margin-top:6px;box-shadow:none;max-width:none;white-space:normal}
|
|
2624
|
+
}
|
|
2625
|
+
.dq-period-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0}
|
|
2626
|
+
.dq-period{min-width:0;padding:4px 16px}
|
|
2627
|
+
.dq-period:first-child{padding-left:0}
|
|
2628
|
+
.dq-period:last-child{padding-right:0}
|
|
2629
|
+
.dq-period+.dq-period{border-left:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
2630
|
+
@media (max-width:620px){.dq-period-grid{grid-template-columns:1fr}.dq-period,.dq-period:first-child,.dq-period:last-child{padding:12px 0}.dq-period:first-child{padding-top:2px}.dq-period+.dq-period{border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-left:0}}
|
|
1097
2631
|
.dq-period-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}
|
|
1098
2632
|
.dq-period-label{font-size:11px;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
1099
2633
|
.dq-period-cost{font-size:22px;font-weight:680;letter-spacing:-.01em;color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums;line-height:1.25}
|
|
1100
|
-
.dq-period-sub{font-size:
|
|
2634
|
+
.dq-period-sub{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);margin-top:2px;font-variant-numeric:tabular-nums;line-height:1.45}
|
|
2635
|
+
.dq-budget{margin-top:12px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
2636
|
+
.dq-budget--unset{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
2637
|
+
.dq-budget-action{border:0;background:transparent;padding:3px 4px;border-radius:5px;color:var(--dsw-alias-state-business-primary,#0969da);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer}
|
|
2638
|
+
.dq-budget-action:hover{text-decoration:underline;text-underline-offset:3px}
|
|
2639
|
+
.dq-budget-action:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
|
|
2640
|
+
.dq-budget-head,.dq-budget-meta{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
2641
|
+
.dq-budget-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2642
|
+
.dq-budget-amount{font-size:12px;font-weight:650;color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
2643
|
+
.dq-budget-track{height:8px;margin-top:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.12));overflow:hidden}
|
|
2644
|
+
.dq-budget-fill{width:100%;height:100%;border-radius:999px;background:var(--dsw-alias-state-success-primary,#2da44e);transform-origin:left center;transition:transform .35s cubic-bezier(.22,1,.36,1)}
|
|
2645
|
+
.dq-budget--risk .dq-budget-fill{background:var(--dsw-alias-state-warning-primary,#bf8700)}
|
|
2646
|
+
.dq-budget--over .dq-budget-fill{background:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
2647
|
+
.dq-budget-meta{margin-top:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums}
|
|
2648
|
+
.dq-budget--risk .dq-budget-meta span:last-child{color:var(--dsw-alias-state-warning-primary,#9a6700);font-weight:600}
|
|
2649
|
+
.dq-budget--over .dq-budget-meta{color:var(--dsw-alias-state-error-primary,#cf222e);font-weight:600}
|
|
2650
|
+
@media (max-width:620px){.dq-budget-head,.dq-budget-meta,.dq-budget--unset{align-items:flex-start;flex-direction:column;gap:4px}}
|
|
1101
2651
|
.dq-delta{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-weight:600;padding:1px 6px;border-radius:999px;white-space:nowrap;cursor:help;font-variant-numeric:tabular-nums}
|
|
1102
2652
|
.dq-delta--up{color:var(--dsw-alias-state-warning-primary,#9a6700);background:rgba(191,135,0,.14)}
|
|
1103
2653
|
.dq-delta--down{color:var(--dsw-alias-state-success-primary,#1a7f37);background:rgba(45,164,78,.14)}
|
|
@@ -1113,31 +2663,51 @@ var css = `
|
|
|
1113
2663
|
.dq-pricing-now{font-size:11px;font-weight:600;padding:1px 7px;border-radius:999px;color:var(--dsw-alias-label-tertiary,#59636e);background:rgba(120,130,150,.14)}
|
|
1114
2664
|
.dq-pricing-now--peak{color:var(--dsw-alias-state-warning-primary,#9a6700);background:rgba(191,135,0,.14)}
|
|
1115
2665
|
.dq-pricing-body{margin-top:10px;overflow-x:auto}
|
|
1116
|
-
.dq-pricing-table{border-collapse:collapse;font-size:
|
|
2666
|
+
.dq-pricing-table{border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums;min-width:100%}
|
|
1117
2667
|
.dq-pricing-table th{text-align:left;font-weight:500;color:var(--dsw-alias-label-tertiary,#59636e);padding:4px 12px 4px 0;white-space:nowrap;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
1118
2668
|
.dq-pricing-table td{padding:4px 12px 4px 0;color:var(--dsw-alias-label-primary,#1f2328);white-space:nowrap;vertical-align:top}
|
|
1119
2669
|
.dq-pricing-table tbody tr+tr td{border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.05))}
|
|
1120
|
-
.dq-pricing-foot{margin:8px 0 0;font-size:
|
|
2670
|
+
.dq-pricing-foot{margin:8px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
2671
|
+
.dq-coverage{margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
2672
|
+
.dq-coverage-summary{display:flex;align-items:center;flex-wrap:wrap;gap:6px 9px;cursor:pointer;list-style:none;width:fit-content;max-width:100%;margin-left:-4px;padding:3px 4px;border-radius:6px;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
2673
|
+
.dq-coverage-summary::-webkit-details-marker{display:none}
|
|
2674
|
+
.dq-coverage-summary::before{content:"\u25B8";display:inline-block;font-size:10px;opacity:.7;transition:transform .15s ease}
|
|
2675
|
+
.dq-coverage[open] .dq-coverage-summary::before{transform:rotate(90deg)}
|
|
2676
|
+
.dq-coverage-summary:hover{color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2677
|
+
.dq-coverage-summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
2678
|
+
.dq-coverage-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2679
|
+
.dq-coverage-status{padding:1px 7px;border-radius:999px;background:rgba(45,164,78,.14);color:var(--dsw-alias-state-success-primary,#1a7f37);font-size:11px;font-weight:600}
|
|
2680
|
+
.dq-coverage--warn .dq-coverage-status{background:rgba(191,135,0,.14);color:var(--dsw-alias-state-warning-primary,#9a6700)}
|
|
2681
|
+
.dq-coverage-brief{font-size:12px;font-variant-numeric:tabular-nums}
|
|
2682
|
+
.dq-coverage-body{margin-top:10px;padding:11px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.06))}
|
|
2683
|
+
.dq-coverage-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px 20px;margin:0}
|
|
2684
|
+
.dq-coverage-metrics div{min-width:0}
|
|
2685
|
+
.dq-coverage-metrics dt{font-size:11px;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
2686
|
+
.dq-coverage-metrics dd{margin:3px 0 0;font-size:13px;font-weight:650;color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
2687
|
+
.dq-coverage-range,.dq-coverage-note,.dq-coverage-warning{margin:9px 0 0;font-size:12px;line-height:1.55;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
2688
|
+
.dq-coverage-range{font-variant-numeric:tabular-nums}
|
|
2689
|
+
.dq-coverage-warning{padding:8px 10px;border-radius:8px;background:rgba(191,135,0,.10);color:var(--dsw-alias-state-warning-primary,#7a5200)}
|
|
2690
|
+
@media (max-width:620px){.dq-coverage-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.dq-coverage-brief{flex-basis:100%;margin-left:19px}}
|
|
1121
2691
|
.dq-peak-track{height:8px;border-radius:999px;background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.85;overflow:hidden}
|
|
1122
2692
|
.dq-peak-fill{height:100%;border-radius:999px;background:var(--dsw-alias-state-warning-primary,#bf8700);transition:width .35s cubic-bezier(.22,1,.36,1)}
|
|
1123
|
-
.dq-peak-legend{display:flex;flex-wrap:wrap;gap:6px 20px;margin-top:8px;font-size:
|
|
2693
|
+
.dq-peak-legend{display:flex;flex-wrap:wrap;gap:6px 20px;margin-top:8px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums}
|
|
1124
2694
|
.dq-peak-legend span{display:inline-flex;align-items:center;gap:6px}
|
|
1125
2695
|
.dq-legend-swatch--peak{background:var(--dsw-alias-state-warning-primary,#bf8700)}
|
|
1126
2696
|
.dq-legend-swatch--offpeak{background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.85}
|
|
1127
2697
|
.dq-peak-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-top:14px}
|
|
1128
2698
|
.dq-peak-delta{font-size:11px;font-weight:500;margin-left:8px;color:var(--dsw-alias-state-warning-primary,#9a6700)}
|
|
1129
2699
|
.dq-peak-delta--save{color:var(--dsw-alias-state-success-primary,#1a7f37)}
|
|
1130
|
-
.dq-peak-foot{margin:10px 0 0;font-size:
|
|
2700
|
+
.dq-peak-foot{margin:10px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1131
2701
|
.dq-cache-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-bottom:12px}
|
|
1132
2702
|
.dq-muted-value{color:var(--dsw-alias-label-secondary,#59636e)!important;font-weight:550}
|
|
1133
2703
|
.dq-stat-value--warn{color:var(--dsw-alias-state-warning-primary,#9a6700)}
|
|
1134
2704
|
.dq-cache-track{height:8px;border-radius:999px;background:var(--dsw-alias-state-warning-primary,#bf8700);overflow:hidden}
|
|
1135
2705
|
.dq-cache-fill{height:100%;border-radius:999px;background:var(--dsw-alias-state-success-primary,#2da44e);transition:width .35s cubic-bezier(.22,1,.36,1)}
|
|
1136
|
-
.dq-cache-legend{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:8px;font-size:
|
|
2706
|
+
.dq-cache-legend{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:8px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums}
|
|
1137
2707
|
.dq-cache-legend span{display:inline-flex;align-items:center;gap:6px}
|
|
1138
2708
|
.dq-legend-swatch--hit{background:var(--dsw-alias-state-success-primary,#2da44e)}
|
|
1139
2709
|
.dq-legend-swatch--miss{background:var(--dsw-alias-state-warning-primary,#bf8700)}
|
|
1140
|
-
.dq-cache-foot{margin:10px 0 0;font-size:
|
|
2710
|
+
.dq-cache-foot{margin:10px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1141
2711
|
.dq-sessions{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}
|
|
1142
2712
|
.dq-session{display:flex;flex-direction:column;gap:5px;min-width:0}
|
|
1143
2713
|
.dq-session-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
|
|
@@ -1145,8 +2715,32 @@ var css = `
|
|
|
1145
2715
|
.dq-session-cost{font-size:14px;font-weight:650;color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums;flex:none}
|
|
1146
2716
|
.dq-session-track{height:6px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.12));overflow:hidden}
|
|
1147
2717
|
.dq-session-fill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary,#4176e6);transition:width .35s cubic-bezier(.22,1,.36,1)}
|
|
1148
|
-
.dq-session-sub{font-size:
|
|
1149
|
-
|
|
2718
|
+
.dq-session-sub{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums;line-height:1.5}
|
|
2719
|
+
/* The \u300C\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017\u300Dcard uses this class on a bare <p> (no flex-gap parent
|
|
2720
|
+
to lean on, unlike the .dq-session usage above), so it needs its own
|
|
2721
|
+
explicit spacing rather than falling back to the browser's implicit <p>
|
|
2722
|
+
margin \u2014 this project always declares spacing explicitly. */
|
|
2723
|
+
p.dq-session-sub{margin:8px 0}
|
|
2724
|
+
.dq-session-foot{margin:12px 0 0;font-size:12px;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
2725
|
+
/* \u8F6E\u6B21 46: each ranking row is now a <details> whose <summary> wraps the existing
|
|
2726
|
+
head/track/sub trio unchanged (in a flex-column body div) plus a leading chevron \u2014
|
|
2727
|
+
same disclosure grammar as .dq-pricing/.dq-coverage above (hidden native marker,
|
|
2728
|
+
rotating glyph, :focus-visible ring), just applied to a multi-line summary instead
|
|
2729
|
+
of a single line. .dq-session-details needs its own min-width:0 because it is now
|
|
2730
|
+
the flex item inside .dq-session that the ellipsis chain must shrink through. */
|
|
2731
|
+
.dq-session-details{min-width:0}
|
|
2732
|
+
.dq-session-details>summary{list-style:none;cursor:pointer}
|
|
2733
|
+
.dq-session-details>summary::-webkit-details-marker{display:none}
|
|
2734
|
+
.dq-session-summary{display:flex;align-items:flex-start;gap:8px;border-radius:8px;padding:4px;margin:-4px}
|
|
2735
|
+
.dq-session-summary:hover{background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.05))}
|
|
2736
|
+
.dq-session-summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
2737
|
+
.dq-session-chevron{flex:none;width:12px;margin-top:4px;font-size:10px;line-height:1;color:var(--dsw-alias-label-tertiary,#59636e);transition:transform .15s ease}
|
|
2738
|
+
.dq-session-details[open] .dq-session-chevron{transform:rotate(90deg)}
|
|
2739
|
+
.dq-session-summary-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px}
|
|
2740
|
+
/* Expanded body reuses the same shaded-inset language as .dq-coverage-body (10px
|
|
2741
|
+
gap from the summary, 11px/12px padding, 10px radius, bg-layer-2) rather than
|
|
2742
|
+
inventing a new "nested panel" look. SessionUsageCard renders inside unchanged. */
|
|
2743
|
+
.dq-session-expand{margin-top:10px;padding:11px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.06))}
|
|
1150
2744
|
.dq-rank{display:flex;flex-direction:column;gap:14px}
|
|
1151
2745
|
.dq-rank-row{display:flex;flex-direction:column;gap:5px;min-width:0}
|
|
1152
2746
|
.dq-rank-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
|
|
@@ -1155,31 +2749,45 @@ var css = `
|
|
|
1155
2749
|
.dq-rank-share{font-size:11px;font-weight:500;color:var(--dsw-alias-label-tertiary,#59636e);min-width:30px;text-align:right}
|
|
1156
2750
|
.dq-rank-track{height:6px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.12));overflow:hidden}
|
|
1157
2751
|
.dq-rank-fill{height:100%;border-radius:999px;transition:width .35s cubic-bezier(.22,1,.36,1)}
|
|
1158
|
-
.dq-rank-sub{font-size:
|
|
1159
|
-
.dq-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:
|
|
2752
|
+
.dq-rank-sub{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums;line-height:1.5}
|
|
2753
|
+
.dq-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:12px;line-height:1.6;color:var(--dsw-alias-state-warning-primary,#7a5200);background:rgba(191,135,0,.12);border:1px solid rgba(191,135,0,.35)}
|
|
1160
2754
|
.dq-alert-icon{flex:none;width:18px;height:18px;border-radius:50%;background:var(--dsw-alias-state-warning-primary,#bf8700);color:#fff;font-weight:700;font-size:12px;line-height:18px;text-align:center}
|
|
1161
2755
|
.dq-alert strong{font-weight:650}
|
|
1162
2756
|
.dq-alert-link{margin-left:8px;color:inherit;text-decoration:underline;text-underline-offset:2px;white-space:nowrap}
|
|
1163
2757
|
.dq-setting{display:flex;align-items:flex-start;gap:12px;margin-top:14px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
1164
2758
|
.dq-setting-label{font-size:13px;color:var(--dsw-alias-label-primary,#1f2328);padding-top:6px;flex:none}
|
|
1165
2759
|
.dq-setting-control{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
|
|
1166
|
-
.dq-setting-hint{font-size:
|
|
2760
|
+
.dq-setting-hint{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);line-height:1.5}
|
|
1167
2761
|
.dq-number{width:82px;padding:5px 8px;font-size:13px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.16));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
1168
2762
|
.dq-number:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;border-color:transparent}
|
|
2763
|
+
.dq-links-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,#1f2328);margin:0 0 8px}
|
|
1169
2764
|
.dq-links{display:flex;flex-wrap:wrap;gap:8px}
|
|
1170
2765
|
.dq-link{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-state-business-primary,#0969da);text-decoration:none;font-size:13px;font-weight:500;cursor:pointer}
|
|
1171
2766
|
.dq-link:hover{border-color:var(--dsw-alias-state-business-primary,#0969da);background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}
|
|
1172
|
-
.dq-toggle
|
|
2767
|
+
/* .dq-toggle is the first settings control after the links row (\u8F6E\u6B21 39 merge) \u2014 its own
|
|
2768
|
+
border-top separates the "\u5FEB\u6377\u5165\u53E3" links (originally the standalone \u5B98\u65B9\u5E73\u53F0 card, renamed
|
|
2769
|
+
in \u8F6E\u6B21 39 fix-round-1 to read as part of \u8BBE\u7F6E's own navigation area) from \u8BBE\u7F6E controls
|
|
2770
|
+
inside the merged card, same 14px/14px/border-l1 recipe as .dq-setting so the two
|
|
2771
|
+
sub-sections read at equal weight. */
|
|
2772
|
+
.dq-toggle{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;margin-top:14px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
1173
2773
|
.dq-toggle input{width:16px;height:16px;accent-color:var(--dsw-alias-state-business-primary,#0969da);cursor:pointer}
|
|
2774
|
+
.dq-toggle-hint{margin:5px 0 0 26px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1174
2775
|
.dq-toggle input:focus-visible,.dq-model-item input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
1175
|
-
.dq-link:focus-visible,.dq-model-btn:focus-visible,.dq-chart-switch-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
2776
|
+
.dq-link:focus-visible,.dq-model-btn:focus-visible,.dq-chart-switch-btn:focus-visible,.dq-export-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
1176
2777
|
.dq-usage-totals{display:flex;flex-wrap:wrap;gap:12px 24px;margin-bottom:16px}
|
|
2778
|
+
/* value itself (number+unit, e.g. "9999.9\u4E07") must never break across lines inside its own column \u2014
|
|
2779
|
+
nowrap+ellipsis is a defensive floor for all widths; the actual fix for the realistic 320px 2-col
|
|
2780
|
+
case is the font-size drop below, which keeps every measured stress value comfortably under the
|
|
2781
|
+
83px column width without ever hitting the ellipsis fallback (see round 38 PROGRESS entry) */
|
|
2782
|
+
.dq-usage-totals .dq-stat-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
2783
|
+
@media (max-width:620px){.dq-usage-totals{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 24px}.dq-usage-totals .dq-stat{min-width:0}.dq-usage-totals .dq-stat-value{font-size:13px}}
|
|
1177
2784
|
.dq-chart-peak{margin-left:10px;font-size:11px;font-weight:400;color:var(--dsw-alias-label-tertiary,#59636e);font-variant-numeric:tabular-nums}
|
|
1178
|
-
.dq-chart-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:
|
|
2785
|
+
.dq-chart-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:16px 0 8px}
|
|
1179
2786
|
.dq-chart-title:first-of-type{margin-top:0}
|
|
1180
|
-
.dq-chart-block-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:
|
|
2787
|
+
.dq-chart-block-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:16px 0 8px}
|
|
1181
2788
|
.dq-chart-block-head .dq-chart-title{margin:0}
|
|
1182
|
-
.dq-chart-controls{display:flex;align-items:center;gap:8px;flex:none}
|
|
2789
|
+
.dq-chart-controls{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex:none;flex-wrap:wrap}
|
|
2790
|
+
@media (max-width:620px){.dq-chart-block-head{align-items:flex-start;flex-direction:column}.dq-chart-controls{width:100%}}
|
|
1183
2791
|
.dq-model-picker{position:relative}
|
|
1184
2792
|
.dq-model-btn{border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#59636e);border-radius:8px;padding:4px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
|
|
1185
2793
|
.dq-model-btn:hover{border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
|
|
@@ -1191,8 +2799,11 @@ var css = `
|
|
|
1191
2799
|
.dq-model-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1192
2800
|
.dq-model-item--all{border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));padding-bottom:8px;margin-bottom:2px;border-radius:6px}
|
|
1193
2801
|
.dq-model-tag{color:var(--dsw-alias-label-tertiary,#59636e);font-size:11px;flex:none}
|
|
1194
|
-
.dq-bar-group{display:flex;
|
|
1195
|
-
.dq-bar-group .dq-bar{flex:
|
|
2802
|
+
.dq-bar-group{display:flex;flex-direction:column-reverse;width:100%}
|
|
2803
|
+
.dq-bar-group .dq-bar{flex:0 0 auto;width:100%;border-radius:0}
|
|
2804
|
+
.dq-bar-group .dq-bar:first-child{border-radius:0 0 2px 2px}
|
|
2805
|
+
.dq-bar-group .dq-bar:last-child{border-radius:2px 2px 0 0}
|
|
2806
|
+
.dq-bar-group .dq-bar:not(:last-child){box-shadow:inset 0 1px 0 rgba(0,0,0,.18)}
|
|
1196
2807
|
.dq-legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:8px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1197
2808
|
.dq-legend-item{display:inline-flex;align-items:center;gap:6px}
|
|
1198
2809
|
.dq-legend-swatch{width:10px;height:10px;border-radius:3px;flex:none}
|
|
@@ -1201,27 +2812,34 @@ var css = `
|
|
|
1201
2812
|
.dq-chart-switch-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
|
|
1202
2813
|
.dq-chart-switch-btn--on{background:var(--dsw-alias-bg-overlay,#fff);color:var(--dsw-alias-label-primary,#1f2328);box-shadow:0 1px 3px rgba(0,0,0,.14)}
|
|
1203
2814
|
.dq-chart-wrap{position:relative}
|
|
1204
|
-
.dq-
|
|
2815
|
+
.dq-bars-scroll{overflow-x:auto;overflow-y:hidden;padding-bottom:2px}
|
|
2816
|
+
.dq-tip{position:absolute;transform:translate(-50%,14px);z-index:40;pointer-events:none;background:var(--dsw-alias-bg-inverse,#1f2328);color:var(--dsw-alias-label-inverse,#fff);border-radius:8px;padding:7px 10px;font-size:12px;line-height:1.55;white-space:nowrap;box-shadow:0 6px 20px rgba(0,0,0,.28);font-variant-numeric:tabular-nums}
|
|
2817
|
+
.dq-tip--above{transform:translate(-50%,calc(-100% - 8px))}
|
|
1205
2818
|
.dq-tip-head{font-weight:650;margin-bottom:2px}
|
|
1206
2819
|
.dq-tip-row{opacity:.85}
|
|
1207
2820
|
.dq-bars{display:flex;align-items:flex-end;gap:2px;width:100%}
|
|
1208
2821
|
.dq-bar-col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch}
|
|
1209
2822
|
.dq-bar{background:var(--dsw-alias-state-business-primary,#4176e6);border-radius:2px 2px 0 0;min-height:1px;transition:filter .12s ease}
|
|
1210
2823
|
.dq-bar-col{cursor:default}
|
|
2824
|
+
.dq-bar-col,.dq-bar,.dq-heat-cell{scroll-margin-block-start:24px;scroll-margin-block-end:160px}
|
|
1211
2825
|
.dq-bar-col:hover .dq-bar{filter:brightness(1.18) saturate(1.1)}
|
|
2826
|
+
.dq-bar-col:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:-2px;border-radius:3px}
|
|
2827
|
+
.dq-bar:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;filter:brightness(1.18) saturate(1.1);position:relative;z-index:2}
|
|
2828
|
+
.dq-bar-col--pinned{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:-2px;border-radius:3px}
|
|
2829
|
+
.dq-bar-col--pinned .dq-bar{filter:brightness(1.18) saturate(1.1)}
|
|
2830
|
+
.dq-bar--pinned{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;filter:brightness(1.18) saturate(1.1);position:relative;z-index:2}
|
|
1212
2831
|
.dq-heat-cell:not(.dq-heat-cell--pad):hover{outline:1.5px solid var(--dsw-alias-label-primary,#1f2328);outline-offset:1px}
|
|
1213
|
-
.dq-
|
|
2832
|
+
.dq-heat-cell:not(.dq-heat-cell--pad):focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;position:relative;z-index:2}
|
|
2833
|
+
.dq-heat-cell--pinned{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;position:relative;z-index:2}
|
|
2834
|
+
.dq-bar-label{font-size:9px;color:var(--dsw-alias-label-tertiary,#59636e);text-align:center;margin-top:3px;overflow:visible;white-space:nowrap}
|
|
1214
2835
|
.dq-heat{display:flex;flex-direction:column;gap:8px;max-width:100%}
|
|
1215
|
-
.dq-heat-
|
|
1216
|
-
.dq-
|
|
1217
|
-
.dq-heat-
|
|
1218
|
-
.dq-heat-
|
|
1219
|
-
.dq-heat-months{display:flex;gap:3px;height:12px}
|
|
1220
|
-
.dq-heat-month{width:12px;flex:none;font-size:9px;line-height:12px;color:var(--dsw-alias-label-tertiary,#59636e);white-space:nowrap;overflow:visible}
|
|
1221
|
-
.dq-heatmap{display:flex;gap:3px;width:max-content}
|
|
1222
|
-
.dq-heat-week{display:flex;flex-direction:column;gap:3px}
|
|
1223
|
-
.dq-heat-cell{width:12px;height:12px;border-radius:2px;flex:none}
|
|
2836
|
+
.dq-heat-scroll{overflow-x:auto;padding-bottom:2px}
|
|
2837
|
+
.dq-heatmap{display:flex;gap:3px}
|
|
2838
|
+
.dq-heat-week{display:flex;flex:1 1 0;min-width:6px;flex-direction:column;gap:3px}
|
|
2839
|
+
.dq-heat-cell{width:100%;aspect-ratio:1/1;border-radius:3px;flex:none}
|
|
1224
2840
|
.dq-heat-cell--pad{background:transparent}
|
|
2841
|
+
.dq-heat-months{display:flex;gap:3px;margin-top:4px}
|
|
2842
|
+
.dq-heat-month{flex:1 1 0;min-width:6px;font-size:10px;line-height:14px;color:var(--dsw-alias-label-tertiary,#59636e);white-space:nowrap;overflow:visible}
|
|
1225
2843
|
.dq-heat-key{width:12px;height:12px;border-radius:2px;flex:none}
|
|
1226
2844
|
.dq-heat-scale{display:flex;align-items:center;gap:3px;font-size:10px;color:var(--dsw-alias-label-tertiary,#59636e);align-self:flex-end}
|
|
1227
2845
|
.dq-heat-scale>span:first-child{margin-right:2px}
|
|
@@ -1229,15 +2847,31 @@ var css = `
|
|
|
1229
2847
|
.dq-muted{color:var(--dsw-alias-label-secondary,#59636e)}
|
|
1230
2848
|
.dq-error{color:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
1231
2849
|
.dq-warn{color:var(--dsw-alias-state-warning-primary,#9a6700)}
|
|
1232
|
-
.dq-
|
|
2850
|
+
.dq-crash{margin:10px 0 14px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.10));border-radius:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary,#cf222e);white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
2851
|
+
.dq-empty{color:var(--dsw-alias-label-secondary,#59636e);font-size:12px;line-height:1.6;padding:6px 0}
|
|
2852
|
+
/* Error-state modifier for a card body that has nothing else to show (mirrors the .dq-stat-value--ok/
|
|
2853
|
+
--bad/--warn pattern: same layout as .dq-empty, declared after it so this color wins at equal
|
|
2854
|
+
specificity). Used by the current-session card when its own fetch failed and there is no prior
|
|
2855
|
+
data to fall back to \u2014 the status-row .dq-error/.dq-warn spans are for the account-wide balance/
|
|
2856
|
+
usage fetch, this is the per-card equivalent. */
|
|
2857
|
+
.dq-empty--error{color:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
1233
2858
|
.dq-skel{background:linear-gradient(90deg,rgba(125,135,155,.10) 25%,rgba(125,135,155,.20) 37%,rgba(125,135,155,.10) 63%);background-size:400% 100%;border-radius:5px;animation:dq-shimmer 1.5s ease-in-out infinite}
|
|
1234
2859
|
.dq-stat .dq-skel+.dq-skel{margin-top:6px}
|
|
1235
2860
|
.dq-skel-bars{display:flex;align-items:flex-end;gap:2px;width:100%;height:120px;margin-top:10px}
|
|
1236
2861
|
.dq-skel-bar{flex:1 1 0;min-width:0;border-radius:2px 2px 0 0}
|
|
1237
2862
|
@keyframes dq-shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
|
|
1238
|
-
@media (prefers-reduced-motion:reduce){.dq-skel{animation:none}.dq-refresh-icon--spin{animation:none}}
|
|
1239
2863
|
.dq-status-row{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:20px}
|
|
1240
|
-
.dq-status-
|
|
2864
|
+
.dq-status-messages{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0}
|
|
2865
|
+
.dq-status-row .dq-warn,.dq-status-row .dq-error{font-size:12px;line-height:1.45}
|
|
2866
|
+
.dq-sync{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums}
|
|
2867
|
+
.dq-sync-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--dsw-alias-label-tertiary,#8c959f)}
|
|
2868
|
+
.dq-sync--fresh .dq-sync-dot{background:var(--dsw-alias-state-success-primary,#2da44e)}
|
|
2869
|
+
.dq-sync--syncing .dq-sync-dot{background:var(--dsw-alias-state-business-primary,#4176e6);animation:dq-sync-pulse 1.2s ease-in-out infinite}
|
|
2870
|
+
.dq-sync--fallback{color:var(--dsw-alias-state-warning-primary,#9a6700)}
|
|
2871
|
+
.dq-sync--fallback .dq-sync-dot{background:var(--dsw-alias-state-warning-primary,#bf8700)}
|
|
2872
|
+
.dq-sync--error{color:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
2873
|
+
.dq-sync--error .dq-sync-dot{background:var(--dsw-alias-state-error-primary,#cf222e)}
|
|
2874
|
+
@keyframes dq-sync-pulse{50%{opacity:.35;transform:scale(.8)}}
|
|
1241
2875
|
.dq-refresh-btn{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#59636e);border-radius:8px;padding:5px 10px;font-size:12px;cursor:pointer;transition:border-color .15s ease,color .15s ease}
|
|
1242
2876
|
.dq-refresh-btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
|
|
1243
2877
|
.dq-refresh-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
@@ -1245,41 +2879,26 @@ var css = `
|
|
|
1245
2879
|
.dq-refresh-icon{display:inline-block;line-height:1}
|
|
1246
2880
|
.dq-refresh-icon--spin{animation:dq-spin .9s linear infinite}
|
|
1247
2881
|
@keyframes dq-spin{to{transform:rotate(360deg)}}
|
|
2882
|
+
@media (prefers-reduced-motion:reduce){
|
|
2883
|
+
.dsh-quota-root,.dsh-quota-root *,
|
|
2884
|
+
.dsh-quota-root *::before,.dsh-quota-root *::after,
|
|
2885
|
+
.dq-balance,.dq-balance *,
|
|
2886
|
+
.dq-balance *::before,.dq-balance *::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
|
|
2887
|
+
}
|
|
1248
2888
|
`;
|
|
1249
2889
|
|
|
1250
2890
|
// src/client/widget.tsx
|
|
1251
|
-
var
|
|
1252
|
-
var
|
|
2891
|
+
var import_react5 = require("react");
|
|
2892
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1253
2893
|
var MARGIN = 16;
|
|
1254
2894
|
var CORNER_KEY = "widget.corner";
|
|
1255
2895
|
var COLLAPSED_KEY = "widget.collapsed";
|
|
1256
2896
|
var isCorner = (value) => value === "tl" || value === "tr" || value === "bl" || value === "br";
|
|
1257
|
-
function getMainColumn(frame) {
|
|
1258
|
-
const anchor = frame.querySelector('[data-slot="conversation"]');
|
|
1259
|
-
const column = anchor?.parentElement ?? null;
|
|
1260
|
-
return column === frame ? null : column;
|
|
1261
|
-
}
|
|
1262
|
-
function slotBox(frame, slot, side) {
|
|
1263
|
-
const anchor = frame.querySelector(`[data-slot="${slot}"]`);
|
|
1264
|
-
if (anchor === null) return null;
|
|
1265
|
-
if (side === "child") {
|
|
1266
|
-
const rect = anchor.firstElementChild?.getBoundingClientRect() ?? null;
|
|
1267
|
-
return rect !== null && rect.height > 0 ? rect : null;
|
|
1268
|
-
}
|
|
1269
|
-
let el = anchor.parentElement;
|
|
1270
|
-
for (let depth = 0; depth < 4 && el !== null && el !== frame; depth++) {
|
|
1271
|
-
const rect = el.getBoundingClientRect();
|
|
1272
|
-
if (rect.height > 0) return rect;
|
|
1273
|
-
el = el.parentElement;
|
|
1274
|
-
}
|
|
1275
|
-
return null;
|
|
1276
|
-
}
|
|
1277
2897
|
var MIN_REGION_HEIGHT = 140;
|
|
1278
2898
|
function getBounds(node) {
|
|
1279
2899
|
const fallback = { left: 0, top: 0, right: 1e5, bottom: 1e5 };
|
|
1280
2900
|
if (node === null) return fallback;
|
|
1281
|
-
const
|
|
1282
|
-
const frame = overlay?.parentElement ?? null;
|
|
2901
|
+
const frame = getShellFrame(node);
|
|
1283
2902
|
if (frame === null) return fallback;
|
|
1284
2903
|
const frameRect = frame.getBoundingClientRect();
|
|
1285
2904
|
let left = frameRect.left;
|
|
@@ -1319,26 +2938,29 @@ function cornerPos(corner, node, bounds) {
|
|
|
1319
2938
|
return { x: xRight, y: yBottom };
|
|
1320
2939
|
}
|
|
1321
2940
|
function QuotaWidget() {
|
|
1322
|
-
const
|
|
1323
|
-
(0,
|
|
1324
|
-
|
|
1325
|
-
(0,
|
|
2941
|
+
const { t } = useI18n();
|
|
2942
|
+
const [visible, setVisible] = (0, import_react5.useState)(widgetVisibleStore.get());
|
|
2943
|
+
(0, import_react5.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
|
|
2944
|
+
const [quotaViewActive2, setQuotaViewActive] = (0, import_react5.useState)(quotaViewActiveStore.get());
|
|
2945
|
+
(0, import_react5.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
|
|
2946
|
+
const [lowBalance, setLowBalance] = (0, import_react5.useState)(lowBalanceStore.get());
|
|
2947
|
+
(0, import_react5.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
|
|
1326
2948
|
const cachedBalance = getCachedBalance();
|
|
1327
|
-
const [data, setData] = (0,
|
|
1328
|
-
const [error, setError] = (0,
|
|
1329
|
-
const [loading, setLoading] = (0,
|
|
1330
|
-
const [collapsed, setCollapsed] = (0,
|
|
1331
|
-
const [pos, setPos] = (0,
|
|
1332
|
-
const [corner, setCorner] = (0,
|
|
1333
|
-
const [dragging, setDragging] = (0,
|
|
1334
|
-
const rootRef = (0,
|
|
1335
|
-
const dragRef = (0,
|
|
2949
|
+
const [data, setData] = (0, import_react5.useState)(cachedBalance?.data ?? null);
|
|
2950
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
2951
|
+
const [loading, setLoading] = (0, import_react5.useState)(cachedBalance === null);
|
|
2952
|
+
const [collapsed, setCollapsed] = (0, import_react5.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
|
|
2953
|
+
const [pos, setPos] = (0, import_react5.useState)(null);
|
|
2954
|
+
const [corner, setCorner] = (0, import_react5.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
|
|
2955
|
+
const [dragging, setDragging] = (0, import_react5.useState)(false);
|
|
2956
|
+
const rootRef = (0, import_react5.useRef)(null);
|
|
2957
|
+
const dragRef = (0, import_react5.useRef)(null);
|
|
1336
2958
|
const readUsage = () => {
|
|
1337
2959
|
const cached = getCachedUsage();
|
|
1338
2960
|
return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
|
|
1339
2961
|
};
|
|
1340
|
-
const [usage, setUsage] = (0,
|
|
1341
|
-
(0,
|
|
2962
|
+
const [usage, setUsage] = (0, import_react5.useState)(readUsage);
|
|
2963
|
+
(0, import_react5.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
|
|
1342
2964
|
const load = async (showLoading, force = false) => {
|
|
1343
2965
|
if (showLoading) setLoading(true);
|
|
1344
2966
|
try {
|
|
@@ -1348,7 +2970,7 @@ function QuotaWidget() {
|
|
|
1348
2970
|
setError(null);
|
|
1349
2971
|
} else {
|
|
1350
2972
|
setData(null);
|
|
1351
|
-
setError(res.error ?? "
|
|
2973
|
+
setError(res.error ?? "");
|
|
1352
2974
|
}
|
|
1353
2975
|
} catch (err) {
|
|
1354
2976
|
setData(null);
|
|
@@ -1357,15 +2979,15 @@ function QuotaWidget() {
|
|
|
1357
2979
|
setLoading(false);
|
|
1358
2980
|
}
|
|
1359
2981
|
};
|
|
1360
|
-
(0,
|
|
2982
|
+
(0, import_react5.useEffect)(() => {
|
|
1361
2983
|
void load(true);
|
|
1362
2984
|
const id = setInterval(() => {
|
|
1363
2985
|
void load(false);
|
|
1364
2986
|
}, 6e4);
|
|
1365
2987
|
return () => clearInterval(id);
|
|
1366
2988
|
}, []);
|
|
1367
|
-
(0,
|
|
1368
|
-
if (!visible) return;
|
|
2989
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2990
|
+
if (!visible || quotaViewActive2) return;
|
|
1369
2991
|
const node = rootRef.current;
|
|
1370
2992
|
if (node === null) return;
|
|
1371
2993
|
const place = () => {
|
|
@@ -1374,7 +2996,7 @@ function QuotaWidget() {
|
|
|
1374
2996
|
setPos((prev) => prev !== null && prev.x === next.x && prev.y === next.y ? prev : next);
|
|
1375
2997
|
};
|
|
1376
2998
|
place();
|
|
1377
|
-
const frame = node
|
|
2999
|
+
const frame = getShellFrame(node);
|
|
1378
3000
|
let observer = null;
|
|
1379
3001
|
if (frame !== null && typeof ResizeObserver === "function") {
|
|
1380
3002
|
observer = new ResizeObserver(place);
|
|
@@ -1389,7 +3011,7 @@ function QuotaWidget() {
|
|
|
1389
3011
|
window.removeEventListener("resize", place);
|
|
1390
3012
|
clearInterval(poll);
|
|
1391
3013
|
};
|
|
1392
|
-
}, [visible, corner, collapsed]);
|
|
3014
|
+
}, [visible, quotaViewActive2, corner, collapsed]);
|
|
1393
3015
|
const snapToNearest = (session) => {
|
|
1394
3016
|
const node = rootRef.current;
|
|
1395
3017
|
if (node === null) return;
|
|
@@ -1489,118 +3111,139 @@ function QuotaWidget() {
|
|
|
1489
3111
|
const low = lowBalance > 0 && Number.isFinite(balanceValue) && balanceValue < lowBalance;
|
|
1490
3112
|
const dotClass = primary === null ? "dsh-quota-dot dsh-quota-dot--idle" : data?.isAvailable === false ? "dsh-quota-dot dsh-quota-dot--error" : low ? "dsh-quota-dot dsh-quota-dot--warn" : "dsh-quota-dot";
|
|
1491
3113
|
const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
|
|
1492
|
-
const usageAgeText = ageMinutes === null || ageMinutes < 1 ? "
|
|
3114
|
+
const usageAgeText = ageMinutes === null || ageMinutes < 1 ? t("widget.updatedNow") : t("widget.stale", { minutes: ageMinutes });
|
|
1493
3115
|
let body = null;
|
|
1494
3116
|
if (!collapsed) {
|
|
1495
3117
|
if (loading && data === null && error === null) {
|
|
1496
|
-
body = /* @__PURE__ */ (0,
|
|
3118
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body", children: t("widget.loading") });
|
|
1497
3119
|
} else if (error !== null && data === null) {
|
|
1498
|
-
body = /* @__PURE__ */ (0,
|
|
3120
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body dsh-quota-error", children: localizeApiError(error, t, "error.query") });
|
|
1499
3121
|
} else if (primary !== null) {
|
|
1500
|
-
body = /* @__PURE__ */ (0,
|
|
1501
|
-
/* @__PURE__ */ (0,
|
|
1502
|
-
/* @__PURE__ */ (0,
|
|
1503
|
-
/* @__PURE__ */ (0,
|
|
1504
|
-
/* @__PURE__ */ (0,
|
|
3122
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-body", children: [
|
|
3123
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
|
|
3124
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
|
|
3125
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
|
|
3126
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
|
|
1505
3127
|
] }),
|
|
1506
|
-
usage.data !== null && /* @__PURE__ */ (0,
|
|
1507
|
-
/* @__PURE__ */ (0,
|
|
1508
|
-
/* @__PURE__ */ (0,
|
|
3128
|
+
usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
|
|
3129
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
|
|
3130
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "dsh-quota-value", children: [
|
|
1509
3131
|
"\xA5 ",
|
|
1510
3132
|
fmt(usage.data.summary.today.cost)
|
|
1511
3133
|
] })
|
|
1512
3134
|
] }),
|
|
1513
|
-
data?.isAvailable === false && /* @__PURE__ */ (0,
|
|
1514
|
-
/* @__PURE__ */ (0,
|
|
1515
|
-
/* @__PURE__ */ (0,
|
|
3135
|
+
data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-row", children: [
|
|
3136
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
|
|
3137
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
|
|
1516
3138
|
] })
|
|
1517
3139
|
] });
|
|
1518
3140
|
} else {
|
|
1519
|
-
body = /* @__PURE__ */ (0,
|
|
3141
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body dsh-quota-error", children: t("widget.noBalance") });
|
|
1520
3142
|
}
|
|
1521
3143
|
}
|
|
1522
3144
|
const style = pos !== null ? { left: `${pos.x}px`, top: `${pos.y}px`, right: "auto", bottom: "auto" } : void 0;
|
|
1523
|
-
return /* @__PURE__ */ (0,
|
|
3145
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1524
3146
|
"div",
|
|
1525
3147
|
{
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
3148
|
+
className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${quotaViewActive2 ? " dsh-quota-root--dashboard" : ""}`,
|
|
3149
|
+
style,
|
|
3150
|
+
"aria-hidden": quotaViewActive2,
|
|
3151
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3152
|
+
"div",
|
|
3153
|
+
{
|
|
3154
|
+
ref: rootRef,
|
|
3155
|
+
className: "dsh-quota-card",
|
|
3156
|
+
title: t("widget.dragCard"),
|
|
3157
|
+
onPointerDown,
|
|
3158
|
+
onPointerMove,
|
|
3159
|
+
onPointerUp,
|
|
3160
|
+
onPointerCancel: () => endDrag(false),
|
|
3161
|
+
onLostPointerCapture: () => endDrag(false),
|
|
3162
|
+
children: [
|
|
3163
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-header", children: [
|
|
3164
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-title", children: [
|
|
3165
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
|
|
3166
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: dotClass }),
|
|
3167
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
|
|
3168
|
+
] }),
|
|
3169
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-actions", children: [
|
|
3170
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3171
|
+
"button",
|
|
3172
|
+
{
|
|
3173
|
+
className: "dsh-quota-btn",
|
|
3174
|
+
type: "button",
|
|
3175
|
+
title: t("widget.refresh"),
|
|
3176
|
+
"aria-label": t("widget.refresh"),
|
|
3177
|
+
disabled: loading,
|
|
3178
|
+
onClick: () => {
|
|
3179
|
+
void load(true, true);
|
|
3180
|
+
void fetchUsage(true);
|
|
3181
|
+
},
|
|
3182
|
+
children: "\u21BB"
|
|
3183
|
+
}
|
|
3184
|
+
),
|
|
3185
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3186
|
+
"button",
|
|
3187
|
+
{
|
|
3188
|
+
className: "dsh-quota-btn",
|
|
3189
|
+
type: "button",
|
|
3190
|
+
title: collapsed ? t("widget.expand") : t("widget.collapse"),
|
|
3191
|
+
"aria-label": collapsed ? t("widget.expandAria") : t("widget.collapseAria"),
|
|
3192
|
+
"aria-expanded": !collapsed,
|
|
3193
|
+
onClick: () => {
|
|
3194
|
+
const next = !collapsed;
|
|
3195
|
+
setCollapsed(next);
|
|
3196
|
+
savePref(COLLAPSED_KEY, next);
|
|
3197
|
+
},
|
|
3198
|
+
children: collapsed ? "+" : "\u2212"
|
|
3199
|
+
}
|
|
3200
|
+
)
|
|
3201
|
+
] })
|
|
3202
|
+
] }),
|
|
3203
|
+
collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
|
|
1541
3204
|
fmt(primary.total),
|
|
1542
3205
|
" ",
|
|
1543
3206
|
primary.currency
|
|
1544
|
-
] })
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
{
|
|
1550
|
-
className: "dsh-quota-btn",
|
|
1551
|
-
type: "button",
|
|
1552
|
-
title: "\u5237\u65B0\u4F59\u989D\u4E0E\u4ECA\u65E5\u7528\u91CF",
|
|
1553
|
-
"aria-label": "\u5237\u65B0\u4F59\u989D\u4E0E\u4ECA\u65E5\u7528\u91CF",
|
|
1554
|
-
disabled: loading,
|
|
1555
|
-
onClick: () => {
|
|
1556
|
-
void load(true, true);
|
|
1557
|
-
void fetchUsage(true);
|
|
1558
|
-
},
|
|
1559
|
-
children: "\u21BB"
|
|
1560
|
-
}
|
|
1561
|
-
),
|
|
1562
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1563
|
-
"button",
|
|
1564
|
-
{
|
|
1565
|
-
className: "dsh-quota-btn",
|
|
1566
|
-
type: "button",
|
|
1567
|
-
title: collapsed ? "\u5C55\u5F00" : "\u6536\u8D77",
|
|
1568
|
-
"aria-label": collapsed ? "\u5C55\u5F00\u989D\u5EA6\u7A97\u53E3" : "\u6536\u8D77\u989D\u5EA6\u7A97\u53E3",
|
|
1569
|
-
"aria-expanded": !collapsed,
|
|
1570
|
-
onClick: () => {
|
|
1571
|
-
const next = !collapsed;
|
|
1572
|
-
setCollapsed(next);
|
|
1573
|
-
savePref(COLLAPSED_KEY, next);
|
|
1574
|
-
},
|
|
1575
|
-
children: collapsed ? "+" : "\u2212"
|
|
1576
|
-
}
|
|
1577
|
-
)
|
|
1578
|
-
] })
|
|
1579
|
-
] }),
|
|
1580
|
-
body
|
|
1581
|
-
]
|
|
3207
|
+
] }) }),
|
|
3208
|
+
body
|
|
3209
|
+
]
|
|
3210
|
+
}
|
|
3211
|
+
)
|
|
1582
3212
|
}
|
|
1583
|
-
)
|
|
3213
|
+
);
|
|
1584
3214
|
}
|
|
1585
3215
|
|
|
1586
3216
|
// src/client/index.tsx
|
|
1587
|
-
var
|
|
3217
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1588
3218
|
var name = "dsh-usage-dashboard";
|
|
1589
|
-
var inject = ["slots"];
|
|
3219
|
+
var inject = ["slots", "locale"];
|
|
1590
3220
|
function apply(ctx) {
|
|
3221
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-usage-dashboard: browser dictionaries");
|
|
3222
|
+
const t = ctx.locale.bind(NS);
|
|
1591
3223
|
if (typeof document !== "undefined") {
|
|
1592
3224
|
const tag = document.createElement("style");
|
|
1593
3225
|
tag.dataset.plugin = "dsh-usage-dashboard";
|
|
1594
3226
|
tag.textContent = css;
|
|
1595
3227
|
document.head.appendChild(tag);
|
|
1596
3228
|
}
|
|
3229
|
+
void fetchBalance().catch(() => {
|
|
3230
|
+
});
|
|
3231
|
+
void fetchUsage().catch(() => {
|
|
3232
|
+
});
|
|
1597
3233
|
ctx.slots.inject("shell.overlay", () => ctx.slots.register(
|
|
1598
|
-
{ name: "shell.overlay", id: "deepseek-quota", order: 1e3, label:
|
|
1599
|
-
() => /* @__PURE__ */ (0,
|
|
3234
|
+
{ name: "shell.overlay", id: "deepseek-quota", order: 1e3, locale: NS, label: () => t("widget.title") },
|
|
3235
|
+
({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QuotaWidget, {}) }) })
|
|
1600
3236
|
));
|
|
1601
3237
|
ctx.slots.inject("conversation.view", () => ctx.slots.register(
|
|
1602
|
-
{ name: "conversation.view", id: "balance", order: 20, label: "
|
|
1603
|
-
|
|
3238
|
+
{ name: "conversation.view", id: "balance", order: 20, locale: NS, label: () => t("nav.quota") },
|
|
3239
|
+
// `conversation.view` is a session-scoped slot, so the host injects its
|
|
3240
|
+
// standard session props (including `sessionId`) alongside `t` — this
|
|
3241
|
+
// plugin only destructures the two fields it needs, per the existing
|
|
3242
|
+
// "hand-write the minimal structural type" convention (see context.ts).
|
|
3243
|
+
// `sessionId` is typed optional defensively even though the host is
|
|
3244
|
+
// expected to always supply it: BalanceDashboard renders the current-
|
|
3245
|
+
// session card only when it actually has one, never throws either way.
|
|
3246
|
+
({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BalanceDashboard, { sessionId }) }) })
|
|
1604
3247
|
));
|
|
1605
3248
|
}
|
|
1606
3249
|
return module.exports; } });
|