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