@cassius0924/dsh-usage-dashboard 0.8.0 → 0.8.3
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 +55 -3
- package/lib/client.js +1085 -236
- package/lib/client.js.map +4 -4
- package/lib/index.js +506 -61
- package/lib/index.js.map +4 -4
- package/lib/types/breaker-gate.d.ts +94 -0
- package/lib/types/breaker-settings.d.ts +28 -0
- package/lib/types/breaker.d.ts +49 -0
- package/lib/types/client/api.d.ts +8 -1
- package/lib/types/client/breaker-pill.d.ts +21 -0
- package/lib/types/client/breaker-state.d.ts +8 -0
- package/lib/types/client/breaker.d.ts +60 -0
- package/lib/types/client/charts.d.ts +8 -0
- package/lib/types/client/i18n.d.ts +21 -0
- package/lib/types/client/locales.d.ts +33 -0
- package/lib/types/client/session-cost-pill.d.ts +15 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/client/top-up.d.ts +13 -0
- package/lib/types/context.d.ts +36 -0
- package/lib/types/contract.d.ts +65 -0
- package/lib/types/index.d.ts +19 -16
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -26,7 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
name: () => name
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
-
var
|
|
29
|
+
var import_react10 = require("react");
|
|
30
30
|
|
|
31
31
|
// src/client/boundary.tsx
|
|
32
32
|
var import_react = require("react");
|
|
@@ -89,9 +89,9 @@ function loadPersisted(storageKey, isUsable) {
|
|
|
89
89
|
function pruneOldVersions(storageKey) {
|
|
90
90
|
if (typeof localStorage === "undefined") return;
|
|
91
91
|
try {
|
|
92
|
-
const
|
|
92
|
+
const current2 = `${storageKey}:v${LS_VERSION}`;
|
|
93
93
|
for (const key of Object.keys(localStorage)) {
|
|
94
|
-
if (key.startsWith(`${storageKey}:v`) && key !==
|
|
94
|
+
if (key.startsWith(`${storageKey}:v`) && key !== current2) localStorage.removeItem(key);
|
|
95
95
|
}
|
|
96
96
|
} catch {
|
|
97
97
|
}
|
|
@@ -118,6 +118,17 @@ function createCache(ttlMs, storageKey, isUsable) {
|
|
|
118
118
|
|
|
119
119
|
// src/contract.ts
|
|
120
120
|
var USAGE_WINDOW_DAYS = [7, 30, 90, 365];
|
|
121
|
+
var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
122
|
+
var isValidSessionId = (id) => SESSION_ID_PATTERN.test(id);
|
|
123
|
+
var BREAKER_API = {
|
|
124
|
+
state: "/api/dsh-usage-dashboard/breaker",
|
|
125
|
+
release: "/api/dsh-usage-dashboard/breaker/release"
|
|
126
|
+
};
|
|
127
|
+
var isBreakerSettings = (value) => {
|
|
128
|
+
if (typeof value !== "object" || value === null) return false;
|
|
129
|
+
const candidate = value;
|
|
130
|
+
return typeof candidate.enabled === "boolean" && Array.isArray(candidate.sessions) && candidate.sessions.every((session) => typeof session === "string" && isValidSessionId(session));
|
|
131
|
+
};
|
|
121
132
|
|
|
122
133
|
// src/client/api.ts
|
|
123
134
|
var BALANCE_TTL_MS = 6e4;
|
|
@@ -195,15 +206,15 @@ var sessionUsageCache = /* @__PURE__ */ new Map();
|
|
|
195
206
|
var sessionUsageListeners = /* @__PURE__ */ new Map();
|
|
196
207
|
var getCachedSessionUsage = (sessionId) => sessionUsageCache.get(sessionId) ?? null;
|
|
197
208
|
var subscribeSessionUsage = (sessionId, fn) => {
|
|
198
|
-
let
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
sessionUsageListeners.set(sessionId,
|
|
209
|
+
let listeners2 = sessionUsageListeners.get(sessionId);
|
|
210
|
+
if (listeners2 === void 0) {
|
|
211
|
+
listeners2 = /* @__PURE__ */ new Set();
|
|
212
|
+
sessionUsageListeners.set(sessionId, listeners2);
|
|
202
213
|
}
|
|
203
|
-
|
|
214
|
+
listeners2.add(fn);
|
|
204
215
|
return () => {
|
|
205
|
-
|
|
206
|
-
if (
|
|
216
|
+
listeners2?.delete(fn);
|
|
217
|
+
if (listeners2?.size === 0) sessionUsageListeners.delete(sessionId);
|
|
207
218
|
};
|
|
208
219
|
};
|
|
209
220
|
var publishSessionUsage = (sessionId, response) => {
|
|
@@ -230,20 +241,118 @@ async function fetchSessionUsage(sessionId, force = false) {
|
|
|
230
241
|
sessionUsageInflight.set(sessionId, tracked);
|
|
231
242
|
return tracked;
|
|
232
243
|
}
|
|
244
|
+
async function breakerRequest(url, init) {
|
|
245
|
+
try {
|
|
246
|
+
const response = await fetch(url, {
|
|
247
|
+
method: init.method,
|
|
248
|
+
cache: "no-store",
|
|
249
|
+
headers: init.body === void 0 ? { accept: "application/json" } : { accept: "application/json", "content-type": "application/json" },
|
|
250
|
+
...init.body === void 0 ? {} : { body: init.body }
|
|
251
|
+
});
|
|
252
|
+
if (!response.ok) return { ok: false, error: `HTTP ${response.status}` };
|
|
253
|
+
return await response.json();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function fetchBreaker() {
|
|
259
|
+
return breakerRequest(BREAKER_API.state, { method: "GET" });
|
|
260
|
+
}
|
|
261
|
+
async function updateBreaker(request) {
|
|
262
|
+
return breakerRequest(BREAKER_API.state, { method: "PUT", body: JSON.stringify(request) });
|
|
263
|
+
}
|
|
264
|
+
async function releaseBreaker(request) {
|
|
265
|
+
return breakerRequest(
|
|
266
|
+
BREAKER_API.release,
|
|
267
|
+
request === void 0 ? { method: "POST" } : { method: "POST", body: JSON.stringify(request) }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
233
270
|
|
|
234
271
|
// src/client/dashboard.tsx
|
|
235
|
-
var
|
|
272
|
+
var import_react5 = require("react");
|
|
236
273
|
|
|
237
|
-
// src/client/
|
|
274
|
+
// src/client/breaker.ts
|
|
238
275
|
var BEIJING_OFFSET_MS = 8 * 60 * 60 * 1e3;
|
|
276
|
+
var SHORT_SESSION_ID = 8;
|
|
277
|
+
function breakerIsUsable(res) {
|
|
278
|
+
if (res === null || res === void 0 || res.ok !== true) return false;
|
|
279
|
+
const data = res.data;
|
|
280
|
+
if (data === void 0 || data === null) return false;
|
|
281
|
+
return isBreakerSettings(data) && Array.isArray(data.holding) && data.holding.every((id) => typeof id === "string") && typeof data.now === "string" && typeof data.peak === "boolean" && (data.resumeAt === null || typeof data.resumeAt === "string") && Array.isArray(data.peakWindows) && data.peakWindows.every((window2) => typeof window2 === "string");
|
|
282
|
+
}
|
|
283
|
+
function beijingClock(iso) {
|
|
284
|
+
if (typeof iso !== "string" || iso === "") return null;
|
|
285
|
+
const ms = Date.parse(iso);
|
|
286
|
+
if (!Number.isFinite(ms)) return null;
|
|
287
|
+
const beijing = new Date(ms + BEIJING_OFFSET_MS);
|
|
288
|
+
const hours = String(beijing.getUTCHours()).padStart(2, "0");
|
|
289
|
+
const minutes = String(beijing.getUTCMinutes()).padStart(2, "0");
|
|
290
|
+
return `${hours}:${minutes}`;
|
|
291
|
+
}
|
|
292
|
+
function isSessionOptedIn(data, sessionId) {
|
|
293
|
+
if (data === null || sessionId === null || sessionId === void 0 || sessionId === "") return false;
|
|
294
|
+
return Array.isArray(data.sessions) && data.sessions.includes(sessionId);
|
|
295
|
+
}
|
|
296
|
+
function isSessionEnabled(data, sessionId) {
|
|
297
|
+
if (data === null || data.enabled !== true) return false;
|
|
298
|
+
return isSessionOptedIn(data, sessionId);
|
|
299
|
+
}
|
|
300
|
+
function sessionBreakerState(data, sessionId) {
|
|
301
|
+
if (!isSessionEnabled(data, sessionId)) return "off";
|
|
302
|
+
return data !== null && Array.isArray(data.holding) && data.holding.includes(sessionId) ? "holding" : "on";
|
|
303
|
+
}
|
|
304
|
+
function withSessionToggled(data, sessionId, enabled) {
|
|
305
|
+
if (data === null) return null;
|
|
306
|
+
const sessions = enabled ? data.sessions.includes(sessionId) ? data.sessions : [...data.sessions, sessionId] : data.sessions.filter((id) => id !== sessionId);
|
|
307
|
+
const holding = enabled ? data.holding : data.holding.filter((id) => id !== sessionId);
|
|
308
|
+
return { ...data, sessions, holding };
|
|
309
|
+
}
|
|
310
|
+
function withMasterToggled(data, enabled) {
|
|
311
|
+
if (data === null) return null;
|
|
312
|
+
return { ...data, enabled, holding: enabled ? data.holding : [] };
|
|
313
|
+
}
|
|
314
|
+
function shortSessionId(sessionId) {
|
|
315
|
+
const trimmed = sessionId.trim();
|
|
316
|
+
return trimmed.length <= SHORT_SESSION_ID ? trimmed : `${trimmed.slice(0, SHORT_SESSION_ID)}\u2026`;
|
|
317
|
+
}
|
|
318
|
+
function breakerPhase(data) {
|
|
319
|
+
if (data === null) return "unavailable";
|
|
320
|
+
if (!data.enabled) return "masterOff";
|
|
321
|
+
if (!data.peak) return "offPeak";
|
|
322
|
+
return data.holding.length > 0 ? "peakHolding" : "peak";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/client/breaker-state.ts
|
|
326
|
+
var import_react2 = require("react");
|
|
327
|
+
var current = null;
|
|
328
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
329
|
+
function getBreakerData() {
|
|
330
|
+
return current;
|
|
331
|
+
}
|
|
332
|
+
function publishBreakerData(data) {
|
|
333
|
+
current = data;
|
|
334
|
+
for (const listener of [...listeners]) listener();
|
|
335
|
+
}
|
|
336
|
+
function subscribeBreakerData(listener) {
|
|
337
|
+
listeners.add(listener);
|
|
338
|
+
return () => {
|
|
339
|
+
listeners.delete(listener);
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function useBreakerData() {
|
|
343
|
+
return (0, import_react2.useSyncExternalStore)(subscribeBreakerData, getBreakerData, getBreakerData);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/client/budget.ts
|
|
347
|
+
var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
|
|
239
348
|
function budgetSnapshot(spentValue, budgetValue, nowMs = Date.now()) {
|
|
240
349
|
const spent = Number.isFinite(spentValue) ? Math.max(0, spentValue) : 0;
|
|
241
350
|
const budget = Number.isFinite(budgetValue) ? Math.max(0, budgetValue) : 0;
|
|
242
|
-
const beijingNow = new Date(nowMs +
|
|
351
|
+
const beijingNow = new Date(nowMs + BEIJING_OFFSET_MS2);
|
|
243
352
|
const year = beijingNow.getUTCFullYear();
|
|
244
353
|
const month = beijingNow.getUTCMonth();
|
|
245
|
-
const start = Date.UTC(year, month, 1) -
|
|
246
|
-
const end = Date.UTC(year, month + 1, 1) -
|
|
354
|
+
const start = Date.UTC(year, month, 1) - BEIJING_OFFSET_MS2;
|
|
355
|
+
const end = Date.UTC(year, month + 1, 1) - BEIJING_OFFSET_MS2;
|
|
247
356
|
const elapsedRatio = Math.min(1, Math.max(1 / (end - start), (nowMs - start) / (end - start)));
|
|
248
357
|
const forecast = spent / elapsedRatio;
|
|
249
358
|
const ratio = budget > 0 ? spent / budget : 0;
|
|
@@ -254,12 +363,12 @@ function budgetSnapshot(spentValue, budgetValue, nowMs = Date.now()) {
|
|
|
254
363
|
}
|
|
255
364
|
|
|
256
365
|
// src/client/charts.tsx
|
|
257
|
-
var
|
|
366
|
+
var import_react4 = require("react");
|
|
258
367
|
|
|
259
368
|
// src/client/chart-focus.ts
|
|
260
|
-
function nextChartFocus(
|
|
369
|
+
function nextChartFocus(current2, count, key, horizontalStep = 1) {
|
|
261
370
|
if (count <= 0) return 0;
|
|
262
|
-
const index = Math.min(Math.max(0,
|
|
371
|
+
const index = Math.min(Math.max(0, current2), count - 1);
|
|
263
372
|
if (key === "Home") return 0;
|
|
264
373
|
if (key === "End") return count - 1;
|
|
265
374
|
const step = key === "ArrowLeft" || key === "ArrowRight" ? Math.max(1, horizontalStep) : 1;
|
|
@@ -272,8 +381,8 @@ function lastPopulatedIndex(values) {
|
|
|
272
381
|
}
|
|
273
382
|
return Math.max(0, values.length - 1);
|
|
274
383
|
}
|
|
275
|
-
function nextPinnedIndex(
|
|
276
|
-
return
|
|
384
|
+
function nextPinnedIndex(current2, tapped) {
|
|
385
|
+
return current2 === tapped ? null : tapped;
|
|
277
386
|
}
|
|
278
387
|
function isTapGesture(dx, dy, dt, moveThreshold = 10, timeThreshold = 500) {
|
|
279
388
|
return Math.hypot(dx, dy) <= moveThreshold && dt <= timeThreshold;
|
|
@@ -288,7 +397,7 @@ function maxOrOne(values) {
|
|
|
288
397
|
}
|
|
289
398
|
|
|
290
399
|
// src/client/i18n.tsx
|
|
291
|
-
var
|
|
400
|
+
var import_react3 = require("react");
|
|
292
401
|
|
|
293
402
|
// src/client/locales.ts
|
|
294
403
|
var NS = "dsh-usage-dashboard";
|
|
@@ -481,6 +590,7 @@ var zh = {
|
|
|
481
590
|
"balance.granted": "\u8D60\u9001\u989D\u5EA6 {amount}",
|
|
482
591
|
"balance.runway": "\u9884\u8BA1\u53EF\u7528",
|
|
483
592
|
"balance.runwayBreakdownTitle": "\u67E5\u770B\u9884\u8BA1\u53EF\u7528\u5929\u6570\u8BF4\u660E",
|
|
593
|
+
"balance.topUpTitle": "\u4F59\u989D\u504F\u4F4E\uFF0C\u524D\u5F80 DeepSeek \u5F00\u653E\u5E73\u53F0\u5145\u503C\uFF08\u65B0\u6807\u7B7E\u9875\u6253\u5F00\uFF09",
|
|
484
594
|
"balance.days": "{count} \u5929",
|
|
485
595
|
"balance.overYear": "> 365 \u5929",
|
|
486
596
|
"balance.noRunway": "\u8FD1 {days} \u5929\u6CA1\u6709\u7528\u91CF\uFF0C\u65E0\u6CD5\u4F30\u7B97",
|
|
@@ -519,6 +629,9 @@ var zh = {
|
|
|
519
629
|
"settings.widgetTabs": "\u5C55\u793A\u6D6E\u7A97\u7684\u9875\u9762",
|
|
520
630
|
"settings.widgetTabsHint": "\u5217\u8868\u4F1A\u968F\u5DF2\u5B89\u88C5\u7684\u9875\u9762\u81EA\u52A8\u66F4\u65B0\u3002",
|
|
521
631
|
"settings.widgetTabsEmpty": "\u5F53\u524D\u6CA1\u6709\u53EF\u914D\u7F6E\u7684\u4F1A\u8BDD\u9875\u9762\u3002",
|
|
632
|
+
"settings.peakPause": "\u9AD8\u5CF0\u6682\u505C",
|
|
633
|
+
"settings.peakPauseHint": "\u603B\u5F00\u5173\uFF1B\u5173\u95ED\u540E\u6240\u6709\u4F1A\u8BDD\u90FD\u4E0D\u6682\u505C\u3002\u6BCF\u4E2A\u4F1A\u8BDD\u7684\u5F00\u5173\u4F1A\u4FDD\u7559\uFF0C\u53EF\u968F\u65F6\u91CD\u65B0\u6253\u5F00\u3002",
|
|
634
|
+
"settings.peakPauseLoading": "\u6B63\u5728\u8BFB\u53D6\u603B\u5F00\u5173\u72B6\u6001\u2026",
|
|
522
635
|
"settings.lowBalance": "\u4F59\u989D\u9884\u8B66\u7EBF",
|
|
523
636
|
"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",
|
|
524
637
|
"settings.lowBalanceOff": "\u5DF2\u5173\u95ED\u4F59\u989D\u9884\u8B66\u3002\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u6570\u5F00\u542F\u3002",
|
|
@@ -535,6 +648,32 @@ var zh = {
|
|
|
535
648
|
"settings.tabDragAria": "\u62D6\u52A8\u300C\u989D\u5EA6\u300D\u8C03\u6574\u6807\u7B7E\u4F4D\u7F6E",
|
|
536
649
|
"settings.tabMoveEarlier": "\u524D\u79FB",
|
|
537
650
|
"settings.tabMoveLater": "\u540E\u79FB",
|
|
651
|
+
"breaker.title": "\u9AD8\u5CF0\u6682\u505C",
|
|
652
|
+
"breaker.desc": "\u5728\u5BF9\u8BDD\u8F93\u5165\u6846\u65C1\u6309\u4F1A\u8BDD\u5F00\u542F\uFF1B\u9AD8\u5CF0\u65F6\u6BB5\u81EA\u52A8\u6682\u505C\u8BE5\u4F1A\u8BDD\uFF0C18:00 \u7EE7\u7EED\u3002\u81EA\u52A8\u5316\u4EFB\u52A1\u4E0D\u53D7\u5F71\u54CD\uFF1B\u603B\u5F00\u5173\u5728\u300C\u8BBE\u7F6E\u300D\u5361\u91CC\u3002",
|
|
653
|
+
"breaker.pillOff": "\u9AD8\u5CF0\u6682\u505C \xB7 \u5173",
|
|
654
|
+
"breaker.pillOn": "\u9AD8\u5CF0\u6682\u505C \xB7 \u5F00",
|
|
655
|
+
"breaker.pillHolding": "\u5DF2\u6682\u505C \xB7 {clock}",
|
|
656
|
+
"breaker.pillAriaOff": "\u9AD8\u5CF0\u6682\u505C\uFF1A\u672C\u4F1A\u8BDD\u5173\u95ED\uFF0C\u70B9\u51FB\u5F00\u542F",
|
|
657
|
+
"breaker.pillAriaOn": "\u9AD8\u5CF0\u6682\u505C\uFF1A\u672C\u4F1A\u8BDD\u5F00\u542F\uFF0C{clock} \u7EE7\u7EED\uFF0C\u70B9\u51FB\u5173\u95ED",
|
|
658
|
+
"breaker.pillAriaHolding": "\u9AD8\u5CF0\u6682\u505C\uFF1A\u672C\u4F1A\u8BDD\u6B63\u5728\u6682\u505C\uFF0C{clock} \u7EE7\u7EED\uFF0C\u70B9\u51FB\u5173\u95ED",
|
|
659
|
+
"breaker.pillAriaMasterOff": "\u9AD8\u5CF0\u6682\u505C\uFF1A\u603B\u5F00\u5173\u5DF2\u5173\u95ED\uFF0C\u672C\u4F1A\u8BDD\u4E0D\u4F1A\u6682\u505C\uFF1B\u70B9\u51FB\u53EA\u5207\u6362\u672C\u4F1A\u8BDD\u5F00\u5173",
|
|
660
|
+
"breaker.pillTitleOff": "\u9AD8\u5CF0\u65F6\u6BB5\u6682\u505C\u672C\u4F1A\u8BDD\uFF1A\u5F53\u524D\u5173\u95ED\uFF0C\u70B9\u51FB\u5F00\u542F",
|
|
661
|
+
"breaker.pillTitleOn": "\u9AD8\u5CF0\u65F6\u6BB5\u6682\u505C\u672C\u4F1A\u8BDD\uFF1A\u5F53\u524D\u5F00\u542F\uFF0C{clock} \u7EE7\u7EED\uFF1B\u70B9\u51FB\u5173\u95ED",
|
|
662
|
+
"breaker.pillTitleHolding": "\u672C\u4F1A\u8BDD\u6B63\u5728\u6682\u505C\uFF0C{clock} \u7EE7\u7EED\uFF1B\u70B9\u51FB\u5173\u95ED",
|
|
663
|
+
"breaker.pillTitleMasterOff": "\u9AD8\u5CF0\u6682\u505C\u7684\u603B\u5F00\u5173\u5DF2\u5728\u989D\u5EA6\u9875\u5173\u95ED\uFF0C\u672C\u4F1A\u8BDD\u4E0D\u4F1A\u6682\u505C\uFF1B\u70B9\u51FB\u53EA\u5207\u6362\u672C\u4F1A\u8BDD\u5F00\u5173",
|
|
664
|
+
"breaker.actionError": "\u64CD\u4F5C\u5931\u8D25\uFF1A{error}",
|
|
665
|
+
"breaker.loading": "\u8F7D\u5165\u4E2D\u2026",
|
|
666
|
+
"breaker.statusUnavailable": "\u72B6\u6001\u6682\u4E0D\u53EF\u7528",
|
|
667
|
+
"breaker.statusMasterOff": "\u603B\u5F00\u5173\u5DF2\u5173\u95ED\uFF0C\u6240\u6709\u4F1A\u8BDD\u90FD\u4E0D\u6682\u505C\uFF1B\u53EF\u5728\u300C\u8BBE\u7F6E\u300D\u5361\u91CD\u65B0\u6253\u5F00",
|
|
668
|
+
"breaker.statusOffPeak": "\u95F2\u65F6 \xB7 \u4E0D\u6682\u505C\u4F1A\u8BDD",
|
|
669
|
+
"breaker.statusPeak": "\u9AD8\u5CF0\u65F6\u6BB5 \xB7 \u5F00\u542F\u7684\u4F1A\u8BDD\u4F1A\u81EA\u52A8\u6682\u505C",
|
|
670
|
+
"breaker.statusHolding": "\u5DF2\u6682\u505C {count} \u4E2A\u4F1A\u8BDD \xB7 {clock} \u7EE7\u7EED",
|
|
671
|
+
"breaker.statusHoldingNoClock": "\u5DF2\u6682\u505C {count} \u4E2A\u4F1A\u8BDD",
|
|
672
|
+
"breaker.release": "\u7ACB\u5373\u5168\u90E8\u7EE7\u7EED",
|
|
673
|
+
"breaker.releaseBusy": "\u6B63\u5728\u7EE7\u7EED\u2026",
|
|
674
|
+
"breaker.empty": "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u5F00\u542F\u9AD8\u5CF0\u6682\u505C\u3002\u5728\u5BF9\u8BDD\u8F93\u5165\u6846\u65C1\u7684\u300C\u9AD8\u5CF0\u6682\u505C\u300D\u5F00\u5173\u91CC\u5F00\u542F\u3002",
|
|
675
|
+
"breaker.rowDisableAria": "\u5173\u95ED\u300C{title}\u300D\u7684\u9AD8\u5CF0\u6682\u505C",
|
|
676
|
+
"breaker.holding": "\u6682\u505C\u4E2D \xB7 {clock}",
|
|
538
677
|
"panel.caption": "\u8D26\u6237\u7EA7 \xB7 \u8DE8\u5168\u90E8\u4F1A\u8BDD",
|
|
539
678
|
"panel.openAria": "\u6253\u5F00\u989D\u5EA6\u9875",
|
|
540
679
|
"widget.updatedNow": "\u521A\u521A\u66F4\u65B0",
|
|
@@ -554,6 +693,9 @@ var zh = {
|
|
|
554
693
|
"widget.expandAria": "\u5C55\u5F00\u989D\u5EA6\u7A97\u53E3",
|
|
555
694
|
"widget.collapseAria": "\u6536\u8D77\u989D\u5EA6\u7A97\u53E3",
|
|
556
695
|
"messageCost.label": "\u672C\u8F6E\u8D39\u7528",
|
|
696
|
+
"sessionCost.title": "\u4F1A\u8BDD\u8D39\u7528",
|
|
697
|
+
"sessionCost.aria": "\u672C\u4F1A\u8BDD\u4F30\u7B97\u8D39\u7528 {amount}",
|
|
698
|
+
"sessionCost.active": "\u6D3B\u8DC3\u65F6\u95F4",
|
|
557
699
|
"messageCost.tooltip": "\u7531 dsh-usage-dashboard \u63D0\u4F9B \xB7 \u672C\u8F6E\u5BF9\u8BDD\u8D39\u7528\u4E3A\u4F30\u7B97\u503C",
|
|
558
700
|
"messageCost.aria": "{amount}\uFF0C\u672C\u8F6E\u5BF9\u8BDD\u4F30\u7B97\u8D39\u7528\uFF0C\u7531 dsh-usage-dashboard \u63D0\u4F9B",
|
|
559
701
|
"boundary.title": "\u989D\u5EA6\u9762\u677F\u51FA\u9519\u4E86",
|
|
@@ -749,6 +891,7 @@ var en = {
|
|
|
749
891
|
"balance.granted": "Granted {amount}",
|
|
750
892
|
"balance.runway": "Estimated runway",
|
|
751
893
|
"balance.runwayBreakdownTitle": "View estimated runway details",
|
|
894
|
+
"balance.topUpTitle": "Low balance \u2014 top up on the DeepSeek platform (opens a new tab)",
|
|
752
895
|
"balance.days": "{count} days",
|
|
753
896
|
"balance.overYear": "> 365 days",
|
|
754
897
|
"balance.noRunway": "No usage in the last {days} days, so runway cannot be estimated",
|
|
@@ -787,6 +930,9 @@ var en = {
|
|
|
787
930
|
"settings.widgetTabs": "Show widget on",
|
|
788
931
|
"settings.widgetTabsHint": "Updates automatically as views are installed.",
|
|
789
932
|
"settings.widgetTabsEmpty": "There are no configurable conversation views right now.",
|
|
933
|
+
"settings.peakPause": "Peak pause",
|
|
934
|
+
"settings.peakPauseHint": "Master switch; when off no conversation pauses. Per-conversation switches are kept and can be turned back on at any time.",
|
|
935
|
+
"settings.peakPauseLoading": "Reading the master switch\u2026",
|
|
790
936
|
"settings.lowBalance": "Balance warning",
|
|
791
937
|
"settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
|
|
792
938
|
"settings.lowBalanceOff": "Balance warnings are off. Enter an amount above 0 to enable them.",
|
|
@@ -803,6 +949,32 @@ var en = {
|
|
|
803
949
|
"settings.tabDragAria": "Drag Usage to change its tab position",
|
|
804
950
|
"settings.tabMoveEarlier": "Move earlier",
|
|
805
951
|
"settings.tabMoveLater": "Move later",
|
|
952
|
+
"breaker.title": "Peak pause",
|
|
953
|
+
"breaker.desc": "Turn it on per conversation beside the composer; during peak hours that session pauses and resumes at 18:00. Automated tasks are unaffected; the master switch is in Settings.",
|
|
954
|
+
"breaker.pillOff": "Peak pause \xB7 off",
|
|
955
|
+
"breaker.pillOn": "Peak pause \xB7 on",
|
|
956
|
+
"breaker.pillHolding": "Paused \xB7 {clock}",
|
|
957
|
+
"breaker.pillAriaOff": "Peak pause: off for this conversation, click to turn on",
|
|
958
|
+
"breaker.pillAriaOn": "Peak pause: on for this conversation, resumes at {clock}, click to turn off",
|
|
959
|
+
"breaker.pillAriaHolding": "Peak pause: this conversation is paused now, resumes at {clock}, click to turn off",
|
|
960
|
+
"breaker.pillAriaMasterOff": "Peak pause: the master switch is off, so this conversation will not pause; clicking only flips this conversation",
|
|
961
|
+
"breaker.pillTitleOff": "Pause this conversation during peak hours: currently off, click to turn on",
|
|
962
|
+
"breaker.pillTitleOn": "Pause this conversation during peak hours: currently on, resumes at {clock}; click to turn off",
|
|
963
|
+
"breaker.pillTitleHolding": "This conversation is paused and resumes at {clock}; click to turn off",
|
|
964
|
+
"breaker.pillTitleMasterOff": "The Peak pause master switch is off on the Usage page, so this conversation will not pause; clicking only flips this conversation",
|
|
965
|
+
"breaker.actionError": "Action failed: {error}",
|
|
966
|
+
"breaker.loading": "Loading\u2026",
|
|
967
|
+
"breaker.statusUnavailable": "Status unavailable",
|
|
968
|
+
"breaker.statusMasterOff": "Master switch is off; no conversation pauses. Turn it back on in Settings.",
|
|
969
|
+
"breaker.statusOffPeak": "Off-peak \xB7 nothing is paused",
|
|
970
|
+
"breaker.statusPeak": "Peak hours \xB7 conversations with it on pause automatically",
|
|
971
|
+
"breaker.statusHolding": "{count} conversations paused \xB7 resumes at {clock}",
|
|
972
|
+
"breaker.statusHoldingNoClock": "{count} conversations paused",
|
|
973
|
+
"breaker.release": "Resume all now",
|
|
974
|
+
"breaker.releaseBusy": "Resuming\u2026",
|
|
975
|
+
"breaker.empty": "No conversation has Peak pause on yet. Turn it on with the Peak pause switch beside the composer.",
|
|
976
|
+
"breaker.rowDisableAria": "Turn off Peak pause for {title}",
|
|
977
|
+
"breaker.holding": "Paused \xB7 {clock}",
|
|
806
978
|
"panel.caption": "Account-wide \xB7 across all sessions",
|
|
807
979
|
"panel.openAria": "Open Usage",
|
|
808
980
|
"widget.updatedNow": "Updated just now",
|
|
@@ -822,6 +994,9 @@ var en = {
|
|
|
822
994
|
"widget.expandAria": "Expand balance widget",
|
|
823
995
|
"widget.collapseAria": "Collapse balance widget",
|
|
824
996
|
"messageCost.label": "Turn cost",
|
|
997
|
+
"sessionCost.title": "Session cost",
|
|
998
|
+
"sessionCost.aria": "Estimated session cost {amount}",
|
|
999
|
+
"sessionCost.active": "Active time",
|
|
825
1000
|
"messageCost.tooltip": "Provided by dsh-usage-dashboard \xB7 estimated cost for this turn",
|
|
826
1001
|
"messageCost.aria": "{amount}, estimated cost for this turn, provided by dsh-usage-dashboard",
|
|
827
1002
|
"boundary.title": "The usage dashboard crashed",
|
|
@@ -831,17 +1006,24 @@ var en = {
|
|
|
831
1006
|
|
|
832
1007
|
// src/client/i18n.tsx
|
|
833
1008
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1009
|
+
var INTL_LOCALES = {
|
|
1010
|
+
zh: "zh-CN",
|
|
1011
|
+
en: "en-US"
|
|
1012
|
+
};
|
|
1013
|
+
function intlLocale(locale) {
|
|
1014
|
+
return INTL_LOCALES[locale];
|
|
1015
|
+
}
|
|
834
1016
|
var fallbackT = (key, params) => {
|
|
835
1017
|
const template = zh[key];
|
|
836
1018
|
if (params === void 0) return template;
|
|
837
1019
|
return template.replace(/\{(\w+)\}/g, (match, name2) => name2 in params ? String(params[name2]) : match);
|
|
838
1020
|
};
|
|
839
|
-
var TranslationContext = (0,
|
|
1021
|
+
var TranslationContext = (0, import_react3.createContext)(fallbackT);
|
|
840
1022
|
function LocaleProvider(props) {
|
|
841
1023
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TranslationContext.Provider, { value: props.t, children: props.children });
|
|
842
1024
|
}
|
|
843
1025
|
function useI18n() {
|
|
844
|
-
const t = (0,
|
|
1026
|
+
const t = (0, import_react3.useContext)(TranslationContext);
|
|
845
1027
|
return { t, locale: t("meta.locale") === "en" ? "en" : "zh" };
|
|
846
1028
|
}
|
|
847
1029
|
function localizeApiError(message, t, fallback) {
|
|
@@ -872,11 +1054,11 @@ function localizeApiError(message, t, fallback) {
|
|
|
872
1054
|
// src/client/charts.tsx
|
|
873
1055
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
874
1056
|
function useChartTip() {
|
|
875
|
-
const wrapRef = (0,
|
|
876
|
-
const [tip, setTip] = (0,
|
|
877
|
-
const [pinned, setPinned] = (0,
|
|
878
|
-
const pinnedRef = (0,
|
|
879
|
-
const touchStart = (0,
|
|
1057
|
+
const wrapRef = (0, import_react4.useRef)(null);
|
|
1058
|
+
const [tip, setTip] = (0, import_react4.useState)(null);
|
|
1059
|
+
const [pinned, setPinned] = (0, import_react4.useState)(null);
|
|
1060
|
+
const pinnedRef = (0, import_react4.useRef)(null);
|
|
1061
|
+
const touchStart = (0, import_react4.useRef)(null);
|
|
880
1062
|
const setPin = (next) => {
|
|
881
1063
|
pinnedRef.current = next;
|
|
882
1064
|
setPinned(next);
|
|
@@ -915,7 +1097,7 @@ function useChartTip() {
|
|
|
915
1097
|
if (next === null) setTip(null);
|
|
916
1098
|
else showAt(e.currentTarget, text, e.clientX, e.clientY);
|
|
917
1099
|
};
|
|
918
|
-
(0,
|
|
1100
|
+
(0, import_react4.useEffect)(() => {
|
|
919
1101
|
if (pinned === null) return void 0;
|
|
920
1102
|
const onDocPointerDown = (e) => {
|
|
921
1103
|
const wrap = wrapRef.current;
|
|
@@ -929,9 +1111,9 @@ function useChartTip() {
|
|
|
929
1111
|
return { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd };
|
|
930
1112
|
}
|
|
931
1113
|
function useRovingChartPoints(count, initial, horizontalStep = 1) {
|
|
932
|
-
const refs = (0,
|
|
933
|
-
const [active, setActive] = (0,
|
|
934
|
-
(0,
|
|
1114
|
+
const refs = (0, import_react4.useRef)([]);
|
|
1115
|
+
const [active, setActive] = (0, import_react4.useState)(initial);
|
|
1116
|
+
(0, import_react4.useEffect)(() => setActive(Math.min(Math.max(0, initial), Math.max(0, count - 1))), [count, initial]);
|
|
935
1117
|
const onKeyDown = (event, index) => {
|
|
936
1118
|
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
937
1119
|
event.preventDefault();
|
|
@@ -956,10 +1138,10 @@ var MODEL_COLORS = ["#4176e6", "#2da44e", "#e16f24", "#8250df", "#bf8700", "#cf2
|
|
|
956
1138
|
function Bars(props) {
|
|
957
1139
|
const { data, height = 120, labelEvery = 1, minWidth } = props;
|
|
958
1140
|
const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
|
|
959
|
-
const scrollRef = (0,
|
|
1141
|
+
const scrollRef = (0, import_react4.useRef)(null);
|
|
960
1142
|
const initialFocus = lastPopulatedIndex(data.map((datum) => datum.value));
|
|
961
1143
|
const roving = useRovingChartPoints(data.length, initialFocus);
|
|
962
|
-
(0,
|
|
1144
|
+
(0, import_react4.useEffect)(() => {
|
|
963
1145
|
const scroll = scrollRef.current;
|
|
964
1146
|
if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
|
|
965
1147
|
}, [data.length, minWidth]);
|
|
@@ -1001,11 +1183,11 @@ function Bars(props) {
|
|
|
1001
1183
|
function GroupedBars(props) {
|
|
1002
1184
|
const { series, height = 120, labelEvery = 1, minWidth } = props;
|
|
1003
1185
|
const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
|
|
1004
|
-
const scrollRef = (0,
|
|
1186
|
+
const scrollRef = (0, import_react4.useRef)(null);
|
|
1005
1187
|
const count = series.length > 0 ? series[0].bars.length : 0;
|
|
1006
1188
|
const pointValues = Array.from({ length: count }, (_, i) => series.map((s) => s.bars[i]?.value ?? 0)).flat();
|
|
1007
1189
|
const roving = useRovingChartPoints(pointValues.length, lastPopulatedIndex(pointValues));
|
|
1008
|
-
(0,
|
|
1190
|
+
(0, import_react4.useEffect)(() => {
|
|
1009
1191
|
const scroll = scrollRef.current;
|
|
1010
1192
|
if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
|
|
1011
1193
|
}, [count, minWidth]);
|
|
@@ -1096,7 +1278,7 @@ function Heatmap(props) {
|
|
|
1096
1278
|
if (datum === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-heat-cell dq-heat-cell--pad" }, d);
|
|
1097
1279
|
const text = t("chart.tooltip", {
|
|
1098
1280
|
head: datum.date,
|
|
1099
|
-
tokens: datum.total.toLocaleString(locale
|
|
1281
|
+
tokens: datum.total.toLocaleString(intlLocale(locale)),
|
|
1100
1282
|
cost: datum.cost.toFixed(2),
|
|
1101
1283
|
calls: t("common.calls", { count: datum.calls })
|
|
1102
1284
|
});
|
|
@@ -1207,9 +1389,9 @@ function getActiveConversationViewId(frame, viewIds) {
|
|
|
1207
1389
|
}
|
|
1208
1390
|
|
|
1209
1391
|
// src/client/export.ts
|
|
1210
|
-
var
|
|
1392
|
+
var BEIJING_OFFSET_MS3 = 8 * 60 * 60 * 1e3;
|
|
1211
1393
|
function exportDateStamp(nowMs = Date.now()) {
|
|
1212
|
-
const date = new Date(nowMs +
|
|
1394
|
+
const date = new Date(nowMs + BEIJING_OFFSET_MS3);
|
|
1213
1395
|
return [
|
|
1214
1396
|
date.getUTCFullYear(),
|
|
1215
1397
|
String(date.getUTCMonth() + 1).padStart(2, "0"),
|
|
@@ -1325,19 +1507,19 @@ var isBoolean = (value) => typeof value === "boolean";
|
|
|
1325
1507
|
|
|
1326
1508
|
// src/client/store.ts
|
|
1327
1509
|
function createStore(key, isValid, fallback) {
|
|
1328
|
-
let
|
|
1329
|
-
const
|
|
1510
|
+
let current2 = loadPref(key, isValid, fallback);
|
|
1511
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
1330
1512
|
return {
|
|
1331
|
-
get: () =>
|
|
1513
|
+
get: () => current2,
|
|
1332
1514
|
set: (value) => {
|
|
1333
|
-
|
|
1515
|
+
current2 = value;
|
|
1334
1516
|
savePref(key, value);
|
|
1335
|
-
for (const listener of [...
|
|
1517
|
+
for (const listener of [...listeners2]) listener();
|
|
1336
1518
|
},
|
|
1337
1519
|
subscribe: (fn) => {
|
|
1338
|
-
|
|
1520
|
+
listeners2.add(fn);
|
|
1339
1521
|
return () => {
|
|
1340
|
-
|
|
1522
|
+
listeners2.delete(fn);
|
|
1341
1523
|
};
|
|
1342
1524
|
}
|
|
1343
1525
|
};
|
|
@@ -1389,9 +1571,27 @@ function viewOrderForIndex(entries, index, selfId = "balance", fallback = 20) {
|
|
|
1389
1571
|
return previous >= next ? previous + 0.5 : (previous + next) / 2;
|
|
1390
1572
|
}
|
|
1391
1573
|
|
|
1574
|
+
// src/client/top-up.ts
|
|
1575
|
+
var TOP_UP_URL = "https://platform.deepseek.com/top_up";
|
|
1576
|
+
var TOP_UP_FLOOR = 10;
|
|
1577
|
+
function needsTopUp(balance, warningLine) {
|
|
1578
|
+
if (!Number.isFinite(balance)) return false;
|
|
1579
|
+
return balance < TOP_UP_FLOOR || warningLine > 0 && balance < warningLine;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1392
1582
|
// src/client/dashboard.tsx
|
|
1393
1583
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1394
1584
|
var COMPOSER_FALLBACK_PX = 126;
|
|
1585
|
+
function usageTitleMap(usage) {
|
|
1586
|
+
const titles = /* @__PURE__ */ new Map();
|
|
1587
|
+
for (const session of usage?.sessions ?? []) titles.set(session.id, session.title);
|
|
1588
|
+
for (const window2 of usage?.windows ?? []) {
|
|
1589
|
+
for (const session of window2.sessions) {
|
|
1590
|
+
if (!titles.has(session.id)) titles.set(session.id, session.title);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return titles;
|
|
1594
|
+
}
|
|
1395
1595
|
function Skel(props) {
|
|
1396
1596
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel", style: { width: `${props.w}px`, height: `${props.h}px` } });
|
|
1397
1597
|
}
|
|
@@ -1441,12 +1641,12 @@ function HeatmapSkeleton() {
|
|
|
1441
1641
|
}
|
|
1442
1642
|
function Delta(props) {
|
|
1443
1643
|
const { t } = useI18n();
|
|
1444
|
-
const { current, previous, label } = props;
|
|
1644
|
+
const { current: current2, previous, label } = props;
|
|
1445
1645
|
if (previous <= 0) {
|
|
1446
|
-
if (
|
|
1646
|
+
if (current2 <= 0) return null;
|
|
1447
1647
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--new", title: t("delta.noBaselineTitle", { label }), children: t("delta.noBaseline") });
|
|
1448
1648
|
}
|
|
1449
|
-
const percent = Math.round((
|
|
1649
|
+
const percent = Math.round((current2 - previous) / previous * 100);
|
|
1450
1650
|
const title = `${label} \xA5${fmt(previous)}`;
|
|
1451
1651
|
if (percent === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--flat", title, children: t("delta.flat") });
|
|
1452
1652
|
const up = percent > 0;
|
|
@@ -1534,7 +1734,7 @@ function PricingNote(props) {
|
|
|
1534
1734
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheMiss") }),
|
|
1535
1735
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.output") })
|
|
1536
1736
|
] }) }),
|
|
1537
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react5.Fragment, { children: [
|
|
1538
1738
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1539
1739
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan: 2, children: tier.model }),
|
|
1540
1740
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.peak") }),
|
|
@@ -1563,7 +1763,7 @@ function CoverageDiagnostics(props) {
|
|
|
1563
1763
|
const { t, locale } = useI18n();
|
|
1564
1764
|
const { coverage } = props;
|
|
1565
1765
|
const hasGaps = coverage.failedSessions > 0 || coverage.skippedRecords > 0 || coverage.scannedSessions < coverage.listedSessions;
|
|
1566
|
-
const coverageTime = new Intl.DateTimeFormat(locale
|
|
1766
|
+
const coverageTime = new Intl.DateTimeFormat(intlLocale(locale), {
|
|
1567
1767
|
timeZone: "Asia/Shanghai",
|
|
1568
1768
|
year: "numeric",
|
|
1569
1769
|
month: "numeric",
|
|
@@ -1621,13 +1821,13 @@ function Link(props) {
|
|
|
1621
1821
|
}
|
|
1622
1822
|
function ExportMenu(props) {
|
|
1623
1823
|
const { t } = useI18n();
|
|
1624
|
-
const [open, setOpen] = (0,
|
|
1625
|
-
const [done, setDone] = (0,
|
|
1626
|
-
const rootRef = (0,
|
|
1627
|
-
const triggerRef = (0,
|
|
1628
|
-
const menuRef = (0,
|
|
1629
|
-
const doneTimerRef = (0,
|
|
1630
|
-
(0,
|
|
1824
|
+
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
1825
|
+
const [done, setDone] = (0, import_react5.useState)(false);
|
|
1826
|
+
const rootRef = (0, import_react5.useRef)(null);
|
|
1827
|
+
const triggerRef = (0, import_react5.useRef)(null);
|
|
1828
|
+
const menuRef = (0, import_react5.useRef)(null);
|
|
1829
|
+
const doneTimerRef = (0, import_react5.useRef)(null);
|
|
1830
|
+
(0, import_react5.useEffect)(() => {
|
|
1631
1831
|
if (!open) return;
|
|
1632
1832
|
const onDown = (event) => {
|
|
1633
1833
|
if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
|
|
@@ -1645,7 +1845,7 @@ function ExportMenu(props) {
|
|
|
1645
1845
|
document.removeEventListener("keydown", onKey);
|
|
1646
1846
|
};
|
|
1647
1847
|
}, [open]);
|
|
1648
|
-
(0,
|
|
1848
|
+
(0, import_react5.useEffect)(() => () => {
|
|
1649
1849
|
if (doneTimerRef.current !== null) window.clearTimeout(doneTimerRef.current);
|
|
1650
1850
|
}, []);
|
|
1651
1851
|
const finish = () => {
|
|
@@ -1705,7 +1905,7 @@ function SessionUsageCard(props) {
|
|
|
1705
1905
|
const { t, locale } = useI18n();
|
|
1706
1906
|
const { data } = props;
|
|
1707
1907
|
if (data.calls === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessionUsage.empty") });
|
|
1708
|
-
const rangeTime = new Intl.DateTimeFormat(locale
|
|
1908
|
+
const rangeTime = new Intl.DateTimeFormat(intlLocale(locale), {
|
|
1709
1909
|
timeZone: "Asia/Shanghai",
|
|
1710
1910
|
year: "numeric",
|
|
1711
1911
|
month: "numeric",
|
|
@@ -1919,8 +2119,8 @@ function SessionDetail(props) {
|
|
|
1919
2119
|
function SessionRanking(props) {
|
|
1920
2120
|
const { t, locale } = useI18n();
|
|
1921
2121
|
const { sessions, count, totalCost } = props;
|
|
1922
|
-
const [details, setDetails] = (0,
|
|
1923
|
-
const startedRef = (0,
|
|
2122
|
+
const [details, setDetails] = (0, import_react5.useState)({});
|
|
2123
|
+
const startedRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
|
|
1924
2124
|
const loadDetail = (id) => {
|
|
1925
2125
|
if (startedRef.current.has(id)) return;
|
|
1926
2126
|
startedRef.current.add(id);
|
|
@@ -2021,11 +2221,11 @@ function ModelRanking(props) {
|
|
|
2021
2221
|
function ModelPicker(props) {
|
|
2022
2222
|
const { t, locale } = useI18n();
|
|
2023
2223
|
const { models } = props;
|
|
2024
|
-
const [open, setOpen] = (0,
|
|
2025
|
-
const rootRef = (0,
|
|
2026
|
-
const triggerRef = (0,
|
|
2027
|
-
const menuRef = (0,
|
|
2028
|
-
(0,
|
|
2224
|
+
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
2225
|
+
const rootRef = (0, import_react5.useRef)(null);
|
|
2226
|
+
const triggerRef = (0, import_react5.useRef)(null);
|
|
2227
|
+
const menuRef = (0, import_react5.useRef)(null);
|
|
2228
|
+
(0, import_react5.useEffect)(() => {
|
|
2029
2229
|
if (!open) return;
|
|
2030
2230
|
const onDown = (e) => {
|
|
2031
2231
|
if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
|
|
@@ -2127,45 +2327,55 @@ function BalanceDashboard(props) {
|
|
|
2127
2327
|
const cachedBalance = getCachedBalance();
|
|
2128
2328
|
const cachedUsage = getCachedUsage();
|
|
2129
2329
|
const cachedUsageAt = getCachedUsageAt();
|
|
2130
|
-
const [balance, setBalance] = (0,
|
|
2131
|
-
const [usage, setUsage] = (0,
|
|
2132
|
-
const [error, setError] = (0,
|
|
2133
|
-
const [notice, setNotice] = (0,
|
|
2134
|
-
const [loadingBalance, setLoadingBalance] = (0,
|
|
2135
|
-
const [loadingUsage, setLoadingUsage] = (0,
|
|
2136
|
-
const [refreshing, setRefreshing] = (0,
|
|
2137
|
-
const [usageUpdatedAt, setUsageUpdatedAt] = (0,
|
|
2138
|
-
const [syncState, setSyncState] = (0,
|
|
2139
|
-
const [freshnessNow, setFreshnessNow] = (0,
|
|
2140
|
-
const hadDataRef = (0,
|
|
2141
|
-
const [widgetOn, setWidgetOn] = (0,
|
|
2142
|
-
const [widgetTabIds, setWidgetTabIds] = (0,
|
|
2143
|
-
const [viewTabs, setViewTabs] = (0,
|
|
2144
|
-
(0,
|
|
2145
|
-
const [lowBalance, setLowBalance] = (0,
|
|
2146
|
-
const [monthlyBudget, setMonthlyBudget] = (0,
|
|
2147
|
-
const
|
|
2148
|
-
const [
|
|
2149
|
-
const [
|
|
2150
|
-
const [
|
|
2151
|
-
const [
|
|
2152
|
-
|
|
2153
|
-
const [
|
|
2154
|
-
const
|
|
2155
|
-
const
|
|
2156
|
-
const
|
|
2157
|
-
const [
|
|
2158
|
-
const [
|
|
2159
|
-
const [
|
|
2160
|
-
const
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
const [
|
|
2164
|
-
const [
|
|
2165
|
-
const [
|
|
2166
|
-
const [
|
|
2167
|
-
const
|
|
2168
|
-
(0,
|
|
2330
|
+
const [balance, setBalance] = (0, import_react5.useState)(cachedBalance?.data ?? null);
|
|
2331
|
+
const [usage, setUsage] = (0, import_react5.useState)(cachedUsage?.data ?? null);
|
|
2332
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
2333
|
+
const [notice, setNotice] = (0, import_react5.useState)(null);
|
|
2334
|
+
const [loadingBalance, setLoadingBalance] = (0, import_react5.useState)(cachedBalance === null);
|
|
2335
|
+
const [loadingUsage, setLoadingUsage] = (0, import_react5.useState)(cachedUsage === null);
|
|
2336
|
+
const [refreshing, setRefreshing] = (0, import_react5.useState)(false);
|
|
2337
|
+
const [usageUpdatedAt, setUsageUpdatedAt] = (0, import_react5.useState)(cachedUsageAt);
|
|
2338
|
+
const [syncState, setSyncState] = (0, import_react5.useState)(cachedUsageAt === null ? "syncing" : "cached");
|
|
2339
|
+
const [freshnessNow, setFreshnessNow] = (0, import_react5.useState)(Date.now());
|
|
2340
|
+
const hadDataRef = (0, import_react5.useRef)(cachedBalance !== null || cachedUsage !== null);
|
|
2341
|
+
const [widgetOn, setWidgetOn] = (0, import_react5.useState)(widgetVisibleStore.get());
|
|
2342
|
+
const [widgetTabIds, setWidgetTabIds] = (0, import_react5.useState)(widgetTabIdsStore.get());
|
|
2343
|
+
const [viewTabs, setViewTabs] = (0, import_react5.useState)(() => props.views.list());
|
|
2344
|
+
(0, import_react5.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
|
|
2345
|
+
const [lowBalance, setLowBalance] = (0, import_react5.useState)(lowBalanceStore.get());
|
|
2346
|
+
const [monthlyBudget, setMonthlyBudget] = (0, import_react5.useState)(monthlyBudgetStore.get());
|
|
2347
|
+
const breakerData = useBreakerData();
|
|
2348
|
+
const [breakerLoadError, setBreakerLoadError] = (0, import_react5.useState)(null);
|
|
2349
|
+
const [breakerActionError, setBreakerActionError] = (0, import_react5.useState)(null);
|
|
2350
|
+
const [breakerReleasing, setBreakerReleasing] = (0, import_react5.useState)(false);
|
|
2351
|
+
const [breakerSavingId, setBreakerSavingId] = (0, import_react5.useState)(null);
|
|
2352
|
+
const [breakerSavingMaster, setBreakerSavingMaster] = (0, import_react5.useState)(false);
|
|
2353
|
+
const [breakerTitles, setBreakerTitles] = (0, import_react5.useState)({});
|
|
2354
|
+
const breakerTitleRequests = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
|
|
2355
|
+
const breakerMutationSeq = (0, import_react5.useRef)(0);
|
|
2356
|
+
const breakerMounted = (0, import_react5.useRef)(true);
|
|
2357
|
+
const [barMode, setBarMode] = (0, import_react5.useState)("daily");
|
|
2358
|
+
const [selectedModels, setSelectedModels] = (0, import_react5.useState)([]);
|
|
2359
|
+
const [windowDays, setWindowDays] = (0, import_react5.useState)(usageWindowStore.get());
|
|
2360
|
+
const [chartMetric, setChartMetric] = (0, import_react5.useState)(chartMetricStore.get());
|
|
2361
|
+
const [sidebarEntry, setSidebarEntry] = (0, import_react5.useState)(sidebarEntryStore.get());
|
|
2362
|
+
(0, import_react5.useEffect)(() => sidebarEntryStore.subscribe(() => setSidebarEntry(sidebarEntryStore.get())), []);
|
|
2363
|
+
const [tabDragging, setTabDragging] = (0, import_react5.useState)(false);
|
|
2364
|
+
const [tabDropTarget, setTabDropTarget] = (0, import_react5.useState)(null);
|
|
2365
|
+
const [retainedVision, setRetainedVision] = (0, import_react5.useState)(null);
|
|
2366
|
+
const [runwayHovering, setRunwayHovering] = (0, import_react5.useState)(false);
|
|
2367
|
+
const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react5.useState)(false);
|
|
2368
|
+
const [remainingHovering, setRemainingHovering] = (0, import_react5.useState)(false);
|
|
2369
|
+
const [remainingManuallyOpened, setRemainingManuallyOpened] = (0, import_react5.useState)(false);
|
|
2370
|
+
const budgetInputRef = (0, import_react5.useRef)(null);
|
|
2371
|
+
const rootRef = (0, import_react5.useRef)(null);
|
|
2372
|
+
const [composerHeight, setComposerHeight] = (0, import_react5.useState)(null);
|
|
2373
|
+
const [composerWidth, setComposerWidth] = (0, import_react5.useState)(null);
|
|
2374
|
+
const [sessionUsage, setSessionUsage] = (0, import_react5.useState)(null);
|
|
2375
|
+
const [loadingSessionUsage, setLoadingSessionUsage] = (0, import_react5.useState)(props.sessionId !== void 0 && props.sessionId !== "");
|
|
2376
|
+
const [sessionUsageError, setSessionUsageError] = (0, import_react5.useState)(null);
|
|
2377
|
+
const sessionUsageIdRef = (0, import_react5.useRef)(null);
|
|
2378
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2169
2379
|
const node = rootRef.current;
|
|
2170
2380
|
if (node === null) return;
|
|
2171
2381
|
const measure = () => {
|
|
@@ -2196,13 +2406,13 @@ function BalanceDashboard(props) {
|
|
|
2196
2406
|
window.removeEventListener("resize", measure);
|
|
2197
2407
|
};
|
|
2198
2408
|
}, []);
|
|
2199
|
-
(0,
|
|
2409
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2200
2410
|
const node = rootRef.current;
|
|
2201
2411
|
if (node === null) return;
|
|
2202
2412
|
const scrollport = node.closest("[data-conversation-scroll]");
|
|
2203
2413
|
if (scrollport instanceof HTMLElement) scrollport.scrollTop = 0;
|
|
2204
2414
|
}, [props.sessionId]);
|
|
2205
|
-
(0,
|
|
2415
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2206
2416
|
quotaViewActiveStore.set(true);
|
|
2207
2417
|
return () => quotaViewActiveStore.set(false);
|
|
2208
2418
|
}, []);
|
|
@@ -2276,10 +2486,10 @@ function BalanceDashboard(props) {
|
|
|
2276
2486
|
if (sessionUsageIdRef.current === id) setLoadingSessionUsage(false);
|
|
2277
2487
|
}
|
|
2278
2488
|
};
|
|
2279
|
-
(0,
|
|
2489
|
+
(0, import_react5.useEffect)(() => {
|
|
2280
2490
|
void load(false);
|
|
2281
2491
|
}, []);
|
|
2282
|
-
(0,
|
|
2492
|
+
(0, import_react5.useEffect)(() => {
|
|
2283
2493
|
const id = props.sessionId;
|
|
2284
2494
|
sessionUsageIdRef.current = id ?? null;
|
|
2285
2495
|
if (id === void 0 || id === "") {
|
|
@@ -2293,7 +2503,57 @@ function BalanceDashboard(props) {
|
|
|
2293
2503
|
setLoadingSessionUsage(true);
|
|
2294
2504
|
void loadSessionUsage(id, false);
|
|
2295
2505
|
}, [props.sessionId]);
|
|
2296
|
-
(0,
|
|
2506
|
+
(0, import_react5.useEffect)(() => {
|
|
2507
|
+
let cancelled = false;
|
|
2508
|
+
let timer;
|
|
2509
|
+
let fast = false;
|
|
2510
|
+
const pull = async () => {
|
|
2511
|
+
const seqAtStart = breakerMutationSeq.current;
|
|
2512
|
+
const res = await fetchBreaker();
|
|
2513
|
+
if (cancelled) return;
|
|
2514
|
+
if (!breakerIsUsable(res)) {
|
|
2515
|
+
setBreakerLoadError(res.error ?? "");
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
fast = res.data.enabled && res.data.peak && res.data.sessions.length > 0;
|
|
2519
|
+
setBreakerLoadError(null);
|
|
2520
|
+
if (seqAtStart !== breakerMutationSeq.current) return;
|
|
2521
|
+
publishBreakerData(res.data);
|
|
2522
|
+
};
|
|
2523
|
+
const loop = async () => {
|
|
2524
|
+
await pull();
|
|
2525
|
+
if (cancelled) return;
|
|
2526
|
+
timer = window.setTimeout(() => {
|
|
2527
|
+
void loop();
|
|
2528
|
+
}, fast ? 5e3 : 3e4);
|
|
2529
|
+
};
|
|
2530
|
+
void loop();
|
|
2531
|
+
return () => {
|
|
2532
|
+
cancelled = true;
|
|
2533
|
+
if (timer !== void 0) window.clearTimeout(timer);
|
|
2534
|
+
};
|
|
2535
|
+
}, []);
|
|
2536
|
+
(0, import_react5.useEffect)(() => {
|
|
2537
|
+
breakerMounted.current = true;
|
|
2538
|
+
return () => {
|
|
2539
|
+
breakerMounted.current = false;
|
|
2540
|
+
};
|
|
2541
|
+
}, []);
|
|
2542
|
+
(0, import_react5.useEffect)(() => {
|
|
2543
|
+
if (breakerData === null) return;
|
|
2544
|
+
const known = usageTitleMap(usage);
|
|
2545
|
+
for (const sessionId of breakerData.sessions) {
|
|
2546
|
+
if (known.has(sessionId) || breakerTitles[sessionId] !== void 0) continue;
|
|
2547
|
+
if (breakerTitleRequests.current.has(sessionId)) continue;
|
|
2548
|
+
breakerTitleRequests.current.add(sessionId);
|
|
2549
|
+
void fetchSessionUsage(sessionId).then((res) => {
|
|
2550
|
+
if (!breakerMounted.current) return;
|
|
2551
|
+
const title = res.ok === true ? res.data?.title?.trim() ?? "" : "";
|
|
2552
|
+
setBreakerTitles((current2) => ({ ...current2, [sessionId]: title }));
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
}, [breakerData, usage, breakerTitles]);
|
|
2556
|
+
(0, import_react5.useEffect)(() => {
|
|
2297
2557
|
const timer = window.setInterval(() => setFreshnessNow(Date.now()), 3e4);
|
|
2298
2558
|
return () => window.clearInterval(timer);
|
|
2299
2559
|
}, []);
|
|
@@ -2317,6 +2577,66 @@ function BalanceDashboard(props) {
|
|
|
2317
2577
|
setMonthlyBudget(value);
|
|
2318
2578
|
monthlyBudgetStore.set(value);
|
|
2319
2579
|
};
|
|
2580
|
+
const disableBreakerSession = async (sessionId) => {
|
|
2581
|
+
if (breakerSavingId !== null || breakerReleasing || breakerSavingMaster) return;
|
|
2582
|
+
const previous = breakerData;
|
|
2583
|
+
publishBreakerData(withSessionToggled(previous, sessionId, false));
|
|
2584
|
+
setBreakerActionError(null);
|
|
2585
|
+
setBreakerSavingId(sessionId);
|
|
2586
|
+
const seq = ++breakerMutationSeq.current;
|
|
2587
|
+
try {
|
|
2588
|
+
const res = await updateBreaker({ sessionId, enabled: false });
|
|
2589
|
+
if (seq !== breakerMutationSeq.current) return;
|
|
2590
|
+
if (breakerIsUsable(res)) {
|
|
2591
|
+
publishBreakerData(res.data);
|
|
2592
|
+
setBreakerLoadError(null);
|
|
2593
|
+
} else {
|
|
2594
|
+
publishBreakerData(previous);
|
|
2595
|
+
setBreakerActionError(res.error ?? "");
|
|
2596
|
+
}
|
|
2597
|
+
} finally {
|
|
2598
|
+
setBreakerSavingId(null);
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
const toggleBreakerMaster = async (enabled) => {
|
|
2602
|
+
if (breakerSavingMaster || breakerSavingId !== null || breakerReleasing) return;
|
|
2603
|
+
const previous = breakerData;
|
|
2604
|
+
publishBreakerData(withMasterToggled(previous, enabled));
|
|
2605
|
+
setBreakerActionError(null);
|
|
2606
|
+
setBreakerSavingMaster(true);
|
|
2607
|
+
const seq = ++breakerMutationSeq.current;
|
|
2608
|
+
try {
|
|
2609
|
+
const res = await updateBreaker({ enabled });
|
|
2610
|
+
if (seq !== breakerMutationSeq.current) return;
|
|
2611
|
+
if (breakerIsUsable(res)) {
|
|
2612
|
+
publishBreakerData(res.data);
|
|
2613
|
+
setBreakerLoadError(null);
|
|
2614
|
+
} else {
|
|
2615
|
+
publishBreakerData(previous);
|
|
2616
|
+
setBreakerActionError(res.error ?? "");
|
|
2617
|
+
}
|
|
2618
|
+
} finally {
|
|
2619
|
+
setBreakerSavingMaster(false);
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
const releaseBreakerNow = async () => {
|
|
2623
|
+
if (breakerReleasing || breakerSavingId !== null || breakerSavingMaster) return;
|
|
2624
|
+
setBreakerActionError(null);
|
|
2625
|
+
setBreakerReleasing(true);
|
|
2626
|
+
const seq = ++breakerMutationSeq.current;
|
|
2627
|
+
try {
|
|
2628
|
+
const res = await releaseBreaker();
|
|
2629
|
+
if (seq !== breakerMutationSeq.current) return;
|
|
2630
|
+
if (breakerIsUsable(res)) {
|
|
2631
|
+
publishBreakerData(res.data);
|
|
2632
|
+
setBreakerLoadError(null);
|
|
2633
|
+
} else {
|
|
2634
|
+
setBreakerActionError(res.error ?? "");
|
|
2635
|
+
}
|
|
2636
|
+
} finally {
|
|
2637
|
+
setBreakerReleasing(false);
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2320
2640
|
const changeWindowDays = (value) => {
|
|
2321
2641
|
setWindowDays(value);
|
|
2322
2642
|
usageWindowStore.set(value);
|
|
@@ -2395,6 +2715,7 @@ function BalanceDashboard(props) {
|
|
|
2395
2715
|
const avgDailyCost = recentDays.length > 0 ? recentDays.reduce((sum, d) => sum + d.cost, 0) / recentDays.length : 0;
|
|
2396
2716
|
const balanceValue = primary !== null ? Number(primary.total) : Number.NaN;
|
|
2397
2717
|
const balanceLow = lowBalance > 0 && Number.isFinite(balanceValue) && balanceValue < lowBalance;
|
|
2718
|
+
const topUpPrompt = needsTopUp(balanceValue, lowBalance);
|
|
2398
2719
|
const daysLeft = avgDailyCost > 0 && Number.isFinite(balanceValue) ? balanceValue / avgDailyCost : null;
|
|
2399
2720
|
const daysLeftText = daysLeft === null ? "\u2014" : daysLeft >= 365 ? t("balance.overYear") : t("balance.days", { count: daysLeft < 10 ? daysLeft.toFixed(1) : Math.round(daysLeft) });
|
|
2400
2721
|
const runwayTitle = daysLeft === null ? t("balance.noRunway", { days: recentDays.length || 7 }) : t("balance.runwayTitle", { days: recentDays.length, cost: fmt(avgDailyCost) });
|
|
@@ -2411,7 +2732,7 @@ function BalanceDashboard(props) {
|
|
|
2411
2732
|
costTotal: window2.totals.cost
|
|
2412
2733
|
});
|
|
2413
2734
|
const liveVision = visionOpen && activeWindow !== null && visionStats !== void 0 ? visionPayload(activeWindow, visionStats, activeWindowName) : null;
|
|
2414
|
-
(0,
|
|
2735
|
+
(0, import_react5.useEffect)(() => {
|
|
2415
2736
|
if (visionOpen && activeWindow !== null && visionStats !== void 0) {
|
|
2416
2737
|
setRetainedVision(visionPayload(activeWindow, visionStats, activeWindowName));
|
|
2417
2738
|
return;
|
|
@@ -2495,6 +2816,15 @@ function BalanceDashboard(props) {
|
|
|
2495
2816
|
{ error: localizeApiError(notice.error, t, "error.query") }
|
|
2496
2817
|
);
|
|
2497
2818
|
const errorText = error === null ? null : localizeApiError(error, t, "error.query");
|
|
2819
|
+
const breakerPhaseNow = breakerLoadError !== null ? "unavailable" : breakerPhase(breakerData);
|
|
2820
|
+
const breakerResumeClock = beijingClock(breakerData?.resumeAt ?? null);
|
|
2821
|
+
const breakerHeldCount = breakerData?.holding.length ?? 0;
|
|
2822
|
+
const breakerStatusText = breakerPhaseNow === "unavailable" ? t("breaker.statusUnavailable") : breakerPhaseNow === "masterOff" ? t("breaker.statusMasterOff") : breakerPhaseNow === "peakHolding" ? breakerResumeClock === null ? t("breaker.statusHoldingNoClock", { count: breakerHeldCount }) : t("breaker.statusHolding", { count: breakerHeldCount, clock: breakerResumeClock }) : breakerPhaseNow === "peak" ? t("breaker.statusPeak") : t("breaker.statusOffPeak");
|
|
2823
|
+
const breakerUsageTitles = usageTitleMap(usage);
|
|
2824
|
+
const breakerTitleFor = (sessionId) => {
|
|
2825
|
+
const title = breakerUsageTitles.get(sessionId) ?? breakerTitles[sessionId];
|
|
2826
|
+
return title !== void 0 && title !== "" ? title : shortSessionId(sessionId);
|
|
2827
|
+
};
|
|
2498
2828
|
const composerSafeAreaStyle = {
|
|
2499
2829
|
"--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px`,
|
|
2500
2830
|
// Only set once measured; .dq-balance's max-width falls back to 860px
|
|
@@ -2511,7 +2841,7 @@ function BalanceDashboard(props) {
|
|
|
2511
2841
|
className: `dq-sync dq-sync--${syncState}`,
|
|
2512
2842
|
role: "status",
|
|
2513
2843
|
"aria-live": "polite",
|
|
2514
|
-
title: usageUpdatedAt === null ? t("status.neverSynced") : t("status.usageTime", { time: new Date(usageUpdatedAt).toLocaleString(locale
|
|
2844
|
+
title: usageUpdatedAt === null ? t("status.neverSynced") : t("status.usageTime", { time: new Date(usageUpdatedAt).toLocaleString(intlLocale(locale), { timeZone: "Asia/Shanghai" }) }),
|
|
2515
2845
|
children: [
|
|
2516
2846
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sync-dot", "aria-hidden": "true" }),
|
|
2517
2847
|
syncStatusText(syncState, usageUpdatedAt, freshnessNow, t)
|
|
@@ -2545,7 +2875,7 @@ function BalanceDashboard(props) {
|
|
|
2545
2875
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: t("alert.lowTitle", { balance: fmt(balanceValue), currency: primary?.currency, threshold: fmt(lowBalance) }) }),
|
|
2546
2876
|
daysLeft !== null && t("alert.runway", { days: daysLeftText }),
|
|
2547
2877
|
t("common.period"),
|
|
2548
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: "dq-alert-link", href:
|
|
2878
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: "dq-alert-link", href: TOP_UP_URL, target: "_blank", rel: "noreferrer", children: t("alert.topUp") })
|
|
2549
2879
|
] })
|
|
2550
2880
|
] }),
|
|
2551
2881
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--hero", children: [
|
|
@@ -2586,29 +2916,35 @@ function BalanceDashboard(props) {
|
|
|
2586
2916
|
] }),
|
|
2587
2917
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
2588
2918
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("balance.runway") }),
|
|
2589
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
e
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2919
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-runway-line", children: [
|
|
2920
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2921
|
+
"details",
|
|
2922
|
+
{
|
|
2923
|
+
className: `dq-stat-value dq-runway${daysLeft !== null && daysLeft < 3 ? " dq-stat-value--bad" : ""}`,
|
|
2924
|
+
open: runwayHovering || runwayManuallyOpened,
|
|
2925
|
+
onPointerEnter: () => setRunwayHovering(true),
|
|
2926
|
+
onPointerLeave: () => setRunwayHovering(false),
|
|
2927
|
+
children: [
|
|
2928
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2929
|
+
"summary",
|
|
2930
|
+
{
|
|
2931
|
+
title: t("balance.runwayBreakdownTitle"),
|
|
2932
|
+
onClick: (e) => {
|
|
2933
|
+
e.preventDefault();
|
|
2934
|
+
setRunwayManuallyOpened((v) => !v);
|
|
2935
|
+
},
|
|
2936
|
+
children: daysLeftText
|
|
2937
|
+
}
|
|
2938
|
+
),
|
|
2939
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-runway-detail", children: runwayTitle })
|
|
2940
|
+
]
|
|
2941
|
+
}
|
|
2942
|
+
),
|
|
2943
|
+
topUpPrompt && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("a", { className: "dq-topup", href: TOP_UP_URL, target: "_blank", rel: "noreferrer", title: t("balance.topUpTitle"), children: [
|
|
2944
|
+
t("alert.topUp"),
|
|
2945
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-topup-arrow", "aria-hidden": "true", children: "\u2197" })
|
|
2946
|
+
] })
|
|
2947
|
+
] })
|
|
2612
2948
|
] }),
|
|
2613
2949
|
balance?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("common.status"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value dq-stat-value--bad", children: t("common.unavailable") }) })
|
|
2614
2950
|
] }) : loadingBalance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BalanceSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("balance.empty") })
|
|
@@ -2767,6 +3103,57 @@ function BalanceDashboard(props) {
|
|
|
2767
3103
|
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
|
|
2768
3104
|
] })
|
|
2769
3105
|
] }),
|
|
3106
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
3107
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("breaker.title") }),
|
|
3108
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-hint dq-breaker-desc", children: t("breaker.desc") }),
|
|
3109
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-breaker-status", children: [
|
|
3110
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3111
|
+
"span",
|
|
3112
|
+
{
|
|
3113
|
+
role: "status",
|
|
3114
|
+
className: breakerPhaseNow === "peakHolding" ? "dq-warn" : void 0,
|
|
3115
|
+
title: breakerLoadError !== null && breakerLoadError !== "" ? breakerLoadError : void 0,
|
|
3116
|
+
children: breakerStatusText
|
|
3117
|
+
}
|
|
3118
|
+
),
|
|
3119
|
+
breakerPhaseNow === "peakHolding" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3120
|
+
"button",
|
|
3121
|
+
{
|
|
3122
|
+
type: "button",
|
|
3123
|
+
className: "dq-refresh-btn",
|
|
3124
|
+
disabled: breakerReleasing || breakerSavingId !== null,
|
|
3125
|
+
"aria-busy": breakerReleasing,
|
|
3126
|
+
onClick: () => {
|
|
3127
|
+
void releaseBreakerNow();
|
|
3128
|
+
},
|
|
3129
|
+
children: breakerReleasing ? t("breaker.releaseBusy") : t("breaker.release")
|
|
3130
|
+
}
|
|
3131
|
+
)
|
|
3132
|
+
] }),
|
|
3133
|
+
breakerData === null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: breakerLoadError === null ? t("breaker.loading") : t("breaker.statusUnavailable") }) : breakerData.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("breaker.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: `dq-breaker-list${breakerPhaseNow === "masterOff" ? " dq-breaker-list--muted" : ""}`, children: breakerData.sessions.map((sessionId) => {
|
|
3134
|
+
const title = breakerTitleFor(sessionId);
|
|
3135
|
+
const holding = breakerData.holding.includes(sessionId);
|
|
3136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "dq-breaker-row", children: [
|
|
3137
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dq-breaker-row-main", children: [
|
|
3138
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-breaker-row-title", title, children: title }),
|
|
3139
|
+
holding && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-breaker-row-state", children: t("breaker.holding", { clock: breakerResumeClock ?? "18:00" }) })
|
|
3140
|
+
] }),
|
|
3141
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-breaker-switch", title: t("breaker.rowDisableAria", { title }), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3142
|
+
"input",
|
|
3143
|
+
{
|
|
3144
|
+
type: "checkbox",
|
|
3145
|
+
checked: true,
|
|
3146
|
+
disabled: breakerSavingId !== null || breakerReleasing || breakerSavingMaster,
|
|
3147
|
+
"aria-label": t("breaker.rowDisableAria", { title }),
|
|
3148
|
+
onChange: () => {
|
|
3149
|
+
void disableBreakerSession(sessionId);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
) })
|
|
3153
|
+
] }, sessionId);
|
|
3154
|
+
}) }),
|
|
3155
|
+
breakerActionError !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-breaker-error dq-error", role: "alert", children: t("breaker.actionError", { error: localizeApiError(breakerActionError, t, "error.query") }) })
|
|
3156
|
+
] }),
|
|
2770
3157
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2771
3158
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
|
|
2772
3159
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-links-title", children: t("links.title") }),
|
|
@@ -2797,6 +3184,22 @@ function BalanceDashboard(props) {
|
|
|
2797
3184
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: widgetTabs.length === 0 ? t("settings.widgetTabsEmpty") : t("settings.widgetTabsHint") })
|
|
2798
3185
|
] })
|
|
2799
3186
|
] }),
|
|
3187
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-toggle", children: [
|
|
3188
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3189
|
+
"input",
|
|
3190
|
+
{
|
|
3191
|
+
type: "checkbox",
|
|
3192
|
+
checked: breakerData === null ? true : breakerData.enabled,
|
|
3193
|
+
disabled: breakerData === null || breakerSavingMaster,
|
|
3194
|
+
"aria-describedby": "dq-peak-pause-hint",
|
|
3195
|
+
onChange: (e) => {
|
|
3196
|
+
void toggleBreakerMaster(e.target.checked);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
),
|
|
3200
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.peakPause") })
|
|
3201
|
+
] }),
|
|
3202
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { id: "dq-peak-pause-hint", className: "dq-toggle-hint", children: breakerData === null ? breakerLoadError === null ? t("settings.peakPauseLoading") : t("breaker.statusUnavailable") : t("settings.peakPauseHint") }),
|
|
2800
3203
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
|
|
2801
3204
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
|
|
2802
3205
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
|
|
@@ -2868,21 +3271,21 @@ function BalanceDashboard(props) {
|
|
|
2868
3271
|
onDragOver: onOrderDragOver,
|
|
2869
3272
|
onDrop: onOrderDrop,
|
|
2870
3273
|
children: viewTabs.map((tab, index) => {
|
|
2871
|
-
const
|
|
2872
|
-
const dropTarget = tabDragging && !
|
|
3274
|
+
const current2 = tab.id === "balance";
|
|
3275
|
+
const dropTarget = tabDragging && !current2 && tabDropTarget === index;
|
|
2873
3276
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2874
3277
|
"span",
|
|
2875
3278
|
{
|
|
2876
|
-
className: `dq-tab-chip${
|
|
2877
|
-
draggable:
|
|
2878
|
-
tabIndex:
|
|
2879
|
-
role:
|
|
2880
|
-
"aria-label":
|
|
2881
|
-
onDragStart:
|
|
2882
|
-
onDragEnd:
|
|
3279
|
+
className: `dq-tab-chip${current2 ? " dq-tab-chip--current" : ""}${current2 && tabDragging ? " dq-tab-chip--dragging" : ""}${dropTarget ? " dq-tab-chip--drop-target" : ""}`,
|
|
3280
|
+
draggable: current2,
|
|
3281
|
+
tabIndex: current2 ? 0 : void 0,
|
|
3282
|
+
role: current2 ? "button" : void 0,
|
|
3283
|
+
"aria-label": current2 ? t("settings.tabDragAria") : void 0,
|
|
3284
|
+
onDragStart: current2 ? onQuotaTabDragStart : void 0,
|
|
3285
|
+
onDragEnd: current2 ? endTabDrag : void 0,
|
|
2883
3286
|
onDragOver: (event) => onChipDragOver(event, index),
|
|
2884
3287
|
onDrop: (event) => onChipDrop(event, index),
|
|
2885
|
-
onKeyDown:
|
|
3288
|
+
onKeyDown: current2 ? onQuotaTabKeyDown : void 0,
|
|
2886
3289
|
children: tab.label
|
|
2887
3290
|
},
|
|
2888
3291
|
tab.id
|
|
@@ -2894,13 +3297,145 @@ function BalanceDashboard(props) {
|
|
|
2894
3297
|
] })
|
|
2895
3298
|
] })
|
|
2896
3299
|
] }),
|
|
2897
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.8.
|
|
3300
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.8.3" })
|
|
2898
3301
|
] });
|
|
2899
3302
|
}
|
|
2900
3303
|
|
|
2901
|
-
// src/client/
|
|
2902
|
-
var
|
|
3304
|
+
// src/client/breaker-pill.tsx
|
|
3305
|
+
var import_react6 = require("react");
|
|
2903
3306
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
3307
|
+
var POLL_MS = 1e4;
|
|
3308
|
+
function BreakerIcon({ state }) {
|
|
3309
|
+
if (state === "off") {
|
|
3310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3311
|
+
"svg",
|
|
3312
|
+
{
|
|
3313
|
+
width: "14",
|
|
3314
|
+
height: "14",
|
|
3315
|
+
viewBox: "0 0 16 16",
|
|
3316
|
+
fill: "none",
|
|
3317
|
+
stroke: "currentColor",
|
|
3318
|
+
strokeWidth: "1.2",
|
|
3319
|
+
strokeLinecap: "round",
|
|
3320
|
+
strokeLinejoin: "round",
|
|
3321
|
+
"aria-hidden": "true",
|
|
3322
|
+
focusable: "false",
|
|
3323
|
+
children: [
|
|
3324
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "8", cy: "8", r: "5.9" }),
|
|
3325
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6.5 5.9v4.2" }),
|
|
3326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M9.5 5.9v4.2" })
|
|
3327
|
+
]
|
|
3328
|
+
}
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3332
|
+
"path",
|
|
3333
|
+
{
|
|
3334
|
+
fillRule: "evenodd",
|
|
3335
|
+
clipRule: "evenodd",
|
|
3336
|
+
d: "M8 1.6a6.4 6.4 0 1 0 0 12.8A6.4 6.4 0 0 0 8 1.6Zm-1.85 3.5h1.3v5.8h-1.3V5.1Zm2.4 0h1.3v5.8h-1.3V5.1Z"
|
|
3337
|
+
}
|
|
3338
|
+
) });
|
|
3339
|
+
}
|
|
3340
|
+
function BreakerPill({ sessionId }) {
|
|
3341
|
+
const { t } = useI18n();
|
|
3342
|
+
const data = useBreakerData();
|
|
3343
|
+
const [loadError, setLoadError] = (0, import_react6.useState)(null);
|
|
3344
|
+
const [actionError, setActionError] = (0, import_react6.useState)(null);
|
|
3345
|
+
const [saving, setSaving] = (0, import_react6.useState)(false);
|
|
3346
|
+
const savingRef = (0, import_react6.useRef)(false);
|
|
3347
|
+
const mutationSeq = (0, import_react6.useRef)(0);
|
|
3348
|
+
const sessionIdRef = (0, import_react6.useRef)(sessionId);
|
|
3349
|
+
(0, import_react6.useEffect)(() => {
|
|
3350
|
+
sessionIdRef.current = sessionId;
|
|
3351
|
+
}, [sessionId]);
|
|
3352
|
+
(0, import_react6.useEffect)(() => {
|
|
3353
|
+
if (sessionId === void 0 || sessionId === "") return;
|
|
3354
|
+
let cancelled = false;
|
|
3355
|
+
setLoadError(null);
|
|
3356
|
+
setActionError(null);
|
|
3357
|
+
const pull = async () => {
|
|
3358
|
+
const seqAtStart = mutationSeq.current;
|
|
3359
|
+
const res = await fetchBreaker();
|
|
3360
|
+
if (cancelled) return;
|
|
3361
|
+
if (!breakerIsUsable(res)) {
|
|
3362
|
+
setLoadError(res.error ?? "");
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
setLoadError(null);
|
|
3366
|
+
if (seqAtStart !== mutationSeq.current || savingRef.current) return;
|
|
3367
|
+
publishBreakerData(res.data);
|
|
3368
|
+
};
|
|
3369
|
+
void pull();
|
|
3370
|
+
const timer = window.setInterval(() => {
|
|
3371
|
+
void pull();
|
|
3372
|
+
}, POLL_MS);
|
|
3373
|
+
return () => {
|
|
3374
|
+
cancelled = true;
|
|
3375
|
+
window.clearInterval(timer);
|
|
3376
|
+
};
|
|
3377
|
+
}, [sessionId]);
|
|
3378
|
+
if (sessionId === void 0 || sessionId === "") return null;
|
|
3379
|
+
const state = sessionBreakerState(data, sessionId);
|
|
3380
|
+
const on = state !== "off";
|
|
3381
|
+
const masterOff = data !== null && !data.enabled;
|
|
3382
|
+
const optedIn = isSessionOptedIn(data, sessionId);
|
|
3383
|
+
const resumeClock = beijingClock(data?.resumeAt ?? null) ?? "18:00";
|
|
3384
|
+
const errorSource = actionError ?? loadError;
|
|
3385
|
+
const errorText = errorSource === null ? null : t("breaker.actionError", { error: localizeApiError(errorSource, t, "error.query") });
|
|
3386
|
+
const label = state === "holding" ? t("breaker.pillHolding", { clock: resumeClock }) : state === "on" ? t("breaker.pillOn") : t("breaker.pillOff");
|
|
3387
|
+
const baseTitle = masterOff ? t("breaker.pillTitleMasterOff") : state === "holding" ? t("breaker.pillTitleHolding", { clock: resumeClock }) : state === "on" ? t("breaker.pillTitleOn", { clock: resumeClock }) : t("breaker.pillTitleOff");
|
|
3388
|
+
const title = errorText === null ? baseTitle : `${baseTitle} \xB7 ${errorText}`;
|
|
3389
|
+
const baseAria = masterOff ? t("breaker.pillAriaMasterOff") : state === "holding" ? t("breaker.pillAriaHolding", { clock: resumeClock }) : state === "on" ? t("breaker.pillAriaOn", { clock: resumeClock }) : t("breaker.pillAriaOff");
|
|
3390
|
+
const ariaLabel = errorText === null ? baseAria : `${baseAria} \xB7 ${errorText}`;
|
|
3391
|
+
const toggle = async () => {
|
|
3392
|
+
if (savingRef.current) return;
|
|
3393
|
+
const next = !optedIn;
|
|
3394
|
+
const previous = data;
|
|
3395
|
+
publishBreakerData(withSessionToggled(previous, sessionId, next));
|
|
3396
|
+
setActionError(null);
|
|
3397
|
+
savingRef.current = true;
|
|
3398
|
+
setSaving(true);
|
|
3399
|
+
const seq = ++mutationSeq.current;
|
|
3400
|
+
try {
|
|
3401
|
+
const res = await updateBreaker({ sessionId, enabled: next });
|
|
3402
|
+
if (seq !== mutationSeq.current || sessionIdRef.current !== sessionId) return;
|
|
3403
|
+
if (breakerIsUsable(res)) {
|
|
3404
|
+
publishBreakerData(res.data);
|
|
3405
|
+
setLoadError(null);
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
publishBreakerData(previous);
|
|
3409
|
+
setActionError(res.error ?? "");
|
|
3410
|
+
} finally {
|
|
3411
|
+
savingRef.current = false;
|
|
3412
|
+
setSaving(false);
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-cost-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3416
|
+
"button",
|
|
3417
|
+
{
|
|
3418
|
+
type: "button",
|
|
3419
|
+
className: `dq-cost-pill dq-breaker-pill dq-breaker-pill--${state}`,
|
|
3420
|
+
"aria-pressed": on,
|
|
3421
|
+
"aria-busy": saving,
|
|
3422
|
+
"aria-label": ariaLabel,
|
|
3423
|
+
title,
|
|
3424
|
+
disabled: saving,
|
|
3425
|
+
onClick: () => {
|
|
3426
|
+
void toggle();
|
|
3427
|
+
},
|
|
3428
|
+
children: [
|
|
3429
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BreakerIcon, { state }),
|
|
3430
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-cost-pill-label", children: label })
|
|
3431
|
+
]
|
|
3432
|
+
}
|
|
3433
|
+
) });
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
// src/client/message-cost.tsx
|
|
3437
|
+
var import_react7 = require("react");
|
|
3438
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2904
3439
|
var fmtTurnCost = (cost) => cost.toLocaleString(void 0, {
|
|
2905
3440
|
minimumFractionDigits: cost > 0 && cost < 0.01 ? 4 : 2,
|
|
2906
3441
|
maximumFractionDigits: cost > 0 && cost < 0.01 ? 6 : 4
|
|
@@ -2911,8 +3446,8 @@ var cachedData = (sessionId) => {
|
|
|
2911
3446
|
};
|
|
2912
3447
|
function MessageCost({ sessionId, messageId }) {
|
|
2913
3448
|
const { t } = useI18n();
|
|
2914
|
-
const [data, setData] = (0,
|
|
2915
|
-
(0,
|
|
3449
|
+
const [data, setData] = (0, import_react7.useState)(() => cachedData(sessionId));
|
|
3450
|
+
(0, import_react7.useEffect)(() => {
|
|
2916
3451
|
setData(cachedData(sessionId));
|
|
2917
3452
|
const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData(sessionId)));
|
|
2918
3453
|
const hit = cachedData(sessionId)?.turns.some((turn2) => turn2.messageId === messageId) ?? false;
|
|
@@ -2923,7 +3458,7 @@ function MessageCost({ sessionId, messageId }) {
|
|
|
2923
3458
|
if (turn === void 0) return null;
|
|
2924
3459
|
const amount = `\xA5${fmtTurnCost(turn.cost)}`;
|
|
2925
3460
|
const tooltip = t("messageCost.tooltip");
|
|
2926
|
-
return /* @__PURE__ */ (0,
|
|
3461
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2927
3462
|
"span",
|
|
2928
3463
|
{
|
|
2929
3464
|
className: "dq-message-cost",
|
|
@@ -2931,12 +3466,260 @@ function MessageCost({ sessionId, messageId }) {
|
|
|
2931
3466
|
"data-dq-tooltip": tooltip,
|
|
2932
3467
|
"aria-label": t("messageCost.aria", { amount }),
|
|
2933
3468
|
children: [
|
|
2934
|
-
/* @__PURE__ */ (0,
|
|
2935
|
-
/* @__PURE__ */ (0,
|
|
3469
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-message-cost-separator", "aria-hidden": true, children: "\xB7" }),
|
|
3470
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-message-cost-amount", children: amount })
|
|
3471
|
+
]
|
|
3472
|
+
}
|
|
3473
|
+
);
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
// src/client/session-cost-pill.tsx
|
|
3477
|
+
var import_react8 = require("react");
|
|
3478
|
+
var import_react_dom = require("react-dom");
|
|
3479
|
+
|
|
3480
|
+
// src/pricing.ts
|
|
3481
|
+
var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
|
|
3482
|
+
var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
3483
|
+
var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
3484
|
+
var PEAK_WINDOWS = [[9, 12], [14, 18]];
|
|
3485
|
+
var BEIJING_OFFSET_MS4 = 8 * 36e5;
|
|
3486
|
+
var FLAT_RATES = {
|
|
3487
|
+
pro: { cacheHit: 0.025, input: 3, output: 6 },
|
|
3488
|
+
flash: { cacheHit: 0.02, input: 1, output: 2 },
|
|
3489
|
+
// The vision model billed at flash rates from day one, so its pre-switch
|
|
3490
|
+
// history is costed at the same flat prices as flash.
|
|
3491
|
+
vision: { cacheHit: 0.02, input: 1, output: 2 }
|
|
3492
|
+
};
|
|
3493
|
+
var PEAK_RATES_08_17 = {
|
|
3494
|
+
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
3495
|
+
flash: { cacheHit: 0.1, input: 3, output: 9 },
|
|
3496
|
+
// Official policy: the vision model is priced identically to flash; images
|
|
3497
|
+
// cost tokens, not their own multiplier.
|
|
3498
|
+
vision: { cacheHit: 0.1, input: 3, output: 9 }
|
|
3499
|
+
};
|
|
3500
|
+
var PEAK_RATES_09_10 = {
|
|
3501
|
+
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
3502
|
+
flash: { cacheHit: 0.04, input: 2, output: 8 },
|
|
3503
|
+
vision: { cacheHit: 0.04, input: 2, output: 8 }
|
|
3504
|
+
};
|
|
3505
|
+
var PRICE_ERAS = [
|
|
3506
|
+
{ fromMs: PEAK_PRICING_FROM_MS, peak: PEAK_RATES_08_17, weekdaysOnly: false },
|
|
3507
|
+
{ fromMs: V41_FLASH_PRICING_FROM_MS, peak: PEAK_RATES_09_10, weekdaysOnly: true }
|
|
3508
|
+
];
|
|
3509
|
+
function eraAt(timeMs) {
|
|
3510
|
+
let era = PRICE_ERAS[0];
|
|
3511
|
+
for (const candidate of PRICE_ERAS) if (timeMs >= candidate.fromMs) era = candidate;
|
|
3512
|
+
return era;
|
|
3513
|
+
}
|
|
3514
|
+
var halved = (rates) => ({
|
|
3515
|
+
cacheHit: rates.cacheHit / 2,
|
|
3516
|
+
input: rates.input / 2,
|
|
3517
|
+
output: rates.output / 2
|
|
3518
|
+
});
|
|
3519
|
+
function tierOf(model) {
|
|
3520
|
+
const name2 = model.toLowerCase();
|
|
3521
|
+
if (name2.includes("vision")) return "vision";
|
|
3522
|
+
if (name2.includes("flash")) return "flash";
|
|
3523
|
+
return "pro";
|
|
3524
|
+
}
|
|
3525
|
+
function billedTierOf(timeMs, model) {
|
|
3526
|
+
const tier = tierOf(model);
|
|
3527
|
+
return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
|
|
3528
|
+
}
|
|
3529
|
+
function isBeijingWeekday(timeMs) {
|
|
3530
|
+
const day = new Date(timeMs + BEIJING_OFFSET_MS4).getUTCDay();
|
|
3531
|
+
return day >= 1 && day <= 5;
|
|
3532
|
+
}
|
|
3533
|
+
function isPeak(timeMs) {
|
|
3534
|
+
if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
|
|
3535
|
+
const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS4) % 864e5 + 864e5) % 864e5 / 36e5);
|
|
3536
|
+
return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
|
|
3537
|
+
}
|
|
3538
|
+
function ratesAt(timeMs, model) {
|
|
3539
|
+
if (timeMs < PEAK_PRICING_FROM_MS) return FLAT_RATES[tierOf(model)];
|
|
3540
|
+
const peak = eraAt(timeMs).peak[billedTierOf(timeMs, model)];
|
|
3541
|
+
return isPeak(timeMs) ? peak : halved(peak);
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
// src/client/session-cost-pill.tsx
|
|
3545
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
3546
|
+
var PANEL_MARGIN = 12;
|
|
3547
|
+
var PANEL_GAP = 8;
|
|
3548
|
+
var cachedData2 = (sessionId) => {
|
|
3549
|
+
const response = getCachedSessionUsage(sessionId);
|
|
3550
|
+
return response?.ok === true ? response.data ?? null : null;
|
|
3551
|
+
};
|
|
3552
|
+
function CostIcon() {
|
|
3553
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3554
|
+
"svg",
|
|
3555
|
+
{
|
|
3556
|
+
width: "14",
|
|
3557
|
+
height: "14",
|
|
3558
|
+
viewBox: "0 0 16 16",
|
|
3559
|
+
fill: "none",
|
|
3560
|
+
stroke: "currentColor",
|
|
3561
|
+
strokeWidth: "1",
|
|
3562
|
+
strokeLinecap: "round",
|
|
3563
|
+
strokeLinejoin: "round",
|
|
3564
|
+
"aria-hidden": "true",
|
|
3565
|
+
focusable: "false",
|
|
3566
|
+
children: [
|
|
3567
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "8", cy: "8", r: "6" }),
|
|
3568
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5.5 5.3 8 8.4l2.5-3.1" }),
|
|
3569
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 8.4v2.9" }),
|
|
3570
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5.8 9.5h4.4" }),
|
|
3571
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5.8 10.9h4.4" })
|
|
2936
3572
|
]
|
|
2937
3573
|
}
|
|
2938
3574
|
);
|
|
2939
3575
|
}
|
|
3576
|
+
function SessionCostPill({ sessionId }) {
|
|
3577
|
+
const { t, locale } = useI18n();
|
|
3578
|
+
const [data, setData] = (0, import_react8.useState)(
|
|
3579
|
+
() => sessionId === void 0 || sessionId === "" ? null : cachedData2(sessionId)
|
|
3580
|
+
);
|
|
3581
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
3582
|
+
const anchorRef = (0, import_react8.useRef)(null);
|
|
3583
|
+
const panelRef = (0, import_react8.useRef)(null);
|
|
3584
|
+
const [pos, setPos] = (0, import_react8.useState)(null);
|
|
3585
|
+
(0, import_react8.useEffect)(() => {
|
|
3586
|
+
if (sessionId === void 0 || sessionId === "") return;
|
|
3587
|
+
setData(cachedData2(sessionId));
|
|
3588
|
+
const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData2(sessionId)));
|
|
3589
|
+
if (getCachedSessionUsage(sessionId) === null) void fetchSessionUsage(sessionId);
|
|
3590
|
+
return unsubscribe;
|
|
3591
|
+
}, [sessionId]);
|
|
3592
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
3593
|
+
if (!open) {
|
|
3594
|
+
setPos(null);
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
const anchor = anchorRef.current;
|
|
3598
|
+
const panel = panelRef.current;
|
|
3599
|
+
if (anchor === null || panel === null) return;
|
|
3600
|
+
const pill = anchor.getBoundingClientRect();
|
|
3601
|
+
const width = panel.getBoundingClientRect().width;
|
|
3602
|
+
setPos({
|
|
3603
|
+
left: Math.min(Math.max(pill.left, PANEL_MARGIN), Math.max(PANEL_MARGIN, window.innerWidth - width - PANEL_MARGIN)),
|
|
3604
|
+
bottom: window.innerHeight - pill.top + PANEL_GAP
|
|
3605
|
+
});
|
|
3606
|
+
}, [open, data]);
|
|
3607
|
+
(0, import_react8.useEffect)(() => {
|
|
3608
|
+
if (!open) return;
|
|
3609
|
+
const close = () => setOpen(false);
|
|
3610
|
+
const onPointerDown = (event) => {
|
|
3611
|
+
const target = event.target;
|
|
3612
|
+
if (!(target instanceof Node)) return;
|
|
3613
|
+
if (anchorRef.current?.contains(target) === true || panelRef.current?.contains(target) === true) return;
|
|
3614
|
+
close();
|
|
3615
|
+
};
|
|
3616
|
+
const onKeyDown = (event) => {
|
|
3617
|
+
if (event.key === "Escape") close();
|
|
3618
|
+
};
|
|
3619
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
3620
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3621
|
+
window.addEventListener("resize", close);
|
|
3622
|
+
document.addEventListener("scroll", close, true);
|
|
3623
|
+
return () => {
|
|
3624
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
3625
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
3626
|
+
window.removeEventListener("resize", close);
|
|
3627
|
+
document.removeEventListener("scroll", close, true);
|
|
3628
|
+
};
|
|
3629
|
+
}, [open]);
|
|
3630
|
+
if (sessionId === void 0 || sessionId === "" || data === null || data.calls === 0) return null;
|
|
3631
|
+
const amount = `\xA5${fmtTurnCost(data.cost)}`;
|
|
3632
|
+
const priceAt = data.lastActive ?? Date.now();
|
|
3633
|
+
const split = data.models.reduce(
|
|
3634
|
+
(sum, model) => {
|
|
3635
|
+
const rates = ratesAt(priceAt, model.model);
|
|
3636
|
+
const weighted = {
|
|
3637
|
+
input: model.input * rates.input,
|
|
3638
|
+
cache: model.cache * rates.cacheHit,
|
|
3639
|
+
output: model.output * rates.output
|
|
3640
|
+
};
|
|
3641
|
+
const weight = weighted.input + weighted.cache + weighted.output;
|
|
3642
|
+
if (weight <= 0) return sum;
|
|
3643
|
+
sum.input += model.cost * (weighted.input / weight);
|
|
3644
|
+
sum.cache += model.cost * (weighted.cache / weight);
|
|
3645
|
+
sum.output += model.cost * (weighted.output / weight);
|
|
3646
|
+
return sum;
|
|
3647
|
+
},
|
|
3648
|
+
{ input: 0, cache: 0, output: 0 }
|
|
3649
|
+
);
|
|
3650
|
+
const rangeTime = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "zh-CN", {
|
|
3651
|
+
timeZone: "Asia/Shanghai",
|
|
3652
|
+
month: "numeric",
|
|
3653
|
+
day: "numeric",
|
|
3654
|
+
hour: "2-digit",
|
|
3655
|
+
minute: "2-digit",
|
|
3656
|
+
hour12: false
|
|
3657
|
+
});
|
|
3658
|
+
const range = data.firstActive !== null && data.lastActive !== null ? `${rangeTime.format(data.firstActive)} \u2013 ${rangeTime.format(data.lastActive)}` : null;
|
|
3659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3660
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { ref: anchorRef, className: "dq-cost-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3661
|
+
"button",
|
|
3662
|
+
{
|
|
3663
|
+
type: "button",
|
|
3664
|
+
className: "dq-cost-pill",
|
|
3665
|
+
"aria-haspopup": "dialog",
|
|
3666
|
+
"aria-expanded": open,
|
|
3667
|
+
"aria-label": t("sessionCost.aria", { amount }),
|
|
3668
|
+
onClick: () => setOpen((value) => !value),
|
|
3669
|
+
children: [
|
|
3670
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CostIcon, {}),
|
|
3671
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-pill-label", children: amount })
|
|
3672
|
+
]
|
|
3673
|
+
}
|
|
3674
|
+
) }),
|
|
3675
|
+
open && (0, import_react_dom.createPortal)(
|
|
3676
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3677
|
+
"div",
|
|
3678
|
+
{
|
|
3679
|
+
ref: panelRef,
|
|
3680
|
+
className: "dq-cost-panel",
|
|
3681
|
+
role: "dialog",
|
|
3682
|
+
"aria-label": t("sessionCost.title"),
|
|
3683
|
+
style: pos === null ? { visibility: "hidden", left: 0, top: 0 } : { visibility: "visible", left: pos.left, bottom: pos.bottom, top: "auto" },
|
|
3684
|
+
children: [
|
|
3685
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dq-cost-panel-title", children: [
|
|
3686
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dq-cost-panel-title-label", children: [
|
|
3687
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CostIcon, {}),
|
|
3688
|
+
t("sessionCost.title")
|
|
3689
|
+
] }),
|
|
3690
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-panel-value", children: amount })
|
|
3691
|
+
] }),
|
|
3692
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dq-cost-panel-rule", "aria-hidden": true }),
|
|
3693
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dl", { className: "dq-cost-details", children: [
|
|
3694
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.input") }),
|
|
3695
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
|
|
3696
|
+
"\xA5",
|
|
3697
|
+
fmtTurnCost(split.input)
|
|
3698
|
+
] }),
|
|
3699
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.cacheHit") }),
|
|
3700
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
|
|
3701
|
+
"\xA5",
|
|
3702
|
+
fmtTurnCost(split.cache)
|
|
3703
|
+
] }),
|
|
3704
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.output") }),
|
|
3705
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
|
|
3706
|
+
"\xA5",
|
|
3707
|
+
fmtTurnCost(split.output)
|
|
3708
|
+
] }),
|
|
3709
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("sessionUsage.calls") }),
|
|
3710
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: fmtInt(data.calls) }),
|
|
3711
|
+
range !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3712
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("sessionCost.active") }),
|
|
3713
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: range })
|
|
3714
|
+
] })
|
|
3715
|
+
] })
|
|
3716
|
+
]
|
|
3717
|
+
}
|
|
3718
|
+
),
|
|
3719
|
+
document.body
|
|
3720
|
+
)
|
|
3721
|
+
] });
|
|
3722
|
+
}
|
|
2940
3723
|
|
|
2941
3724
|
// src/client/styles.ts
|
|
2942
3725
|
var css = `
|
|
@@ -2976,12 +3759,43 @@ var css = `
|
|
|
2976
3759
|
.dq-message-cost-amount{text-decoration-line:underline;text-decoration-style:dotted;text-decoration-color:currentColor;text-underline-offset:4px}
|
|
2977
3760
|
.dq-message-cost:hover{color:var(--dsw-alias-label-secondary,#3f4852)}
|
|
2978
3761
|
.dq-message-cost:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px;border-radius:4px}
|
|
2979
|
-
.dq-message-cost::after{content:attr(data-dq-tooltip);position:absolute;left:0;
|
|
3762
|
+
.dq-message-cost::after{content:attr(data-dq-tooltip);position:absolute;left:0;bottom:calc(100% + 6px);top:auto;z-index:2147483001;width:max-content;max-width:min(240px,calc(100vw - 24px));padding:6px 9px;border-radius:8px;background:var(--dsw-alias-tooltip-bg,#1f2328);color:var(--dsw-static-neutral-bluish-00,#fff);box-shadow:0 6px 18px rgba(0,0,0,.22);font-size:12px;font-weight:400;line-height:1.5;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}
|
|
2980
3763
|
.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}
|
|
2981
3764
|
@media (hover:hover){
|
|
2982
3765
|
[data-time-hover-root] .dq-message-cost{opacity:0;transition:opacity 80ms ease}
|
|
2983
3766
|
[data-time-hover-root]:hover .dq-message-cost,[data-time-hover-root]:focus-within .dq-message-cost{opacity:1}
|
|
2984
3767
|
}
|
|
3768
|
+
/* Composer-dock session cost pill: one more reading beside the host's own
|
|
3769
|
+
\u300C\u4F1A\u8BDD\u7EDF\u8BA1\u300D/\u300CToken \u7528\u91CF\u300Dpills (same slot, same compact skin). */
|
|
3770
|
+
.dq-cost-anchor{display:inline-flex;min-width:0;font-size:calc(var(--dsh-content-font-size-secondary,13px) - 1px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px))}
|
|
3771
|
+
.dq-cost-pill{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;max-width:100%;padding:1px 8px;border:0;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-tertiary,#59636e);font:inherit;font-variant-numeric:tabular-nums;line-height:inherit;white-space:nowrap;cursor:pointer}
|
|
3772
|
+
.dq-cost-pill:hover,.dq-cost-pill[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover,rgba(120,130,150,.12));color:var(--dsw-alias-label-secondary,#59636e)}
|
|
3773
|
+
.dq-cost-pill:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
3774
|
+
.dq-cost-pill svg{flex:none}
|
|
3775
|
+
.dq-cost-pill-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
3776
|
+
/* \u9AD8\u5CF0\u6682\u505C pill: same dock/skin as the cost pill. The three states never rely
|
|
3777
|
+
on colour alone \u2014 copy changes and the glyph switches outline\u2192solid \u2014 but the
|
|
3778
|
+
tone reinforces them: off stays neutral tertiary, on uses the primary colour,
|
|
3779
|
+
holding uses the warn tone. Nothing here may widen the dock row. */
|
|
3780
|
+
.dq-breaker-pill{font-variant-numeric:normal}
|
|
3781
|
+
.dq-breaker-pill--off{color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
3782
|
+
.dq-breaker-pill--off:hover{color:var(--dsw-alias-label-secondary,#59636e)}
|
|
3783
|
+
.dq-breaker-pill--on{color:var(--dsw-alias-state-business-primary,#0969da)}
|
|
3784
|
+
.dq-breaker-pill--on:hover{color:var(--dsw-alias-state-business-primary,#0969da)}
|
|
3785
|
+
.dq-breaker-pill--holding{color:var(--dq-text-warn,#9a6700)}
|
|
3786
|
+
.dq-breaker-pill--holding:hover{color:var(--dq-text-warn,#9a6700)}
|
|
3787
|
+
.dq-breaker-pill:disabled{cursor:default;opacity:.7}
|
|
3788
|
+
/* Click-open breakdown: the host stat-dialog skin (menu surface, prominent
|
|
3789
|
+
elevation, 12/18 rows), portaled to the body and placed above the pill. */
|
|
3790
|
+
.dq-cost-panel{position:fixed;z-index:1100;box-sizing:border-box;width:max-content;min-width:min(300px,calc(100vw - 24px));max-width:min(440px,calc(100vw - 24px));padding:16px;border:0;border-radius:var(--dsw-radius-lg,16px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-overlay,#fff));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%));box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.24));font-family:var(--dsw-font-family,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#59636e);cursor:default}
|
|
3791
|
+
.dq-cost-panel-title{display:flex;justify-content:space-between;gap:16px;margin-bottom:8px;color:var(--dsw-alias-label-primary,#1f2328);font-weight:500}
|
|
3792
|
+
.dq-cost-panel-title-label{display:inline-flex;align-items:center;gap:6px;min-width:0}
|
|
3793
|
+
.dq-cost-panel-title-label svg{flex:none}
|
|
3794
|
+
.dq-cost-panel-value{font-variant-numeric:tabular-nums}
|
|
3795
|
+
.dq-cost-panel-rule{margin-bottom:10px;border-top:.5px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1))}
|
|
3796
|
+
.dq-cost-details{display:grid;grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
3797
|
+
.dq-cost-details dt,.dq-cost-details dd{min-width:0;margin:0}
|
|
3798
|
+
.dq-cost-details dd{color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums;text-align:right}
|
|
2985
3799
|
/* \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
|
|
2986
3800
|
\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
|
|
2987
3801
|
padding\uFF0816px/20px\uFF0C\u5361\u7247\u5185\u90E8\u547C\u5438\u611F\uFF0C\u8F6E\u6B21 36 \u5DF2\u5B9A\uFF09\u6DF7\u540C\u3002 */
|
|
@@ -3077,6 +3891,17 @@ var css = `
|
|
|
3077
3891
|
.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;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
3078
3892
|
.dq-remaining[open] .dq-remaining-breakdown{display:flex}
|
|
3079
3893
|
.dq-remaining-granted{color:var(--dsw-alias-label-secondary,#59636e);font-weight:400;font-size:12px}
|
|
3894
|
+
/* Low-balance top-up shortcut beside the runway value. It is an <a>, not a
|
|
3895
|
+
<button>, because it leaves the app; the \u2197 glyph states that. Colours come
|
|
3896
|
+
from the host's primary-button tokens (the same pair DSH's own filled
|
|
3897
|
+
buttons use) so the page keeps exactly one filled call to action instead of
|
|
3898
|
+
reading as another filter chip. Height stays on the runway text line so the
|
|
3899
|
+
hero row does not grow. */
|
|
3900
|
+
.dq-runway-line{display:flex;align-items:center;flex-wrap:wrap;gap:4px 10px}
|
|
3901
|
+
.dq-topup{display:inline-flex;align-items:center;gap:2px;height:26px;padding:0 10px;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-alias-button-primary-fill,#0969da);color:var(--dsw-alias-label-primary-foreground,#fff);font-size:12px;font-weight:600;line-height:1;text-decoration:none;white-space:nowrap;transition:background .15s ease}
|
|
3902
|
+
.dq-topup:hover{background:var(--dsw-alias-button-primary-hover,#0757ba)}
|
|
3903
|
+
.dq-topup:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
3904
|
+
.dq-topup-arrow{font-size:10px;line-height:1;transform:translateY(-1px)}
|
|
3080
3905
|
/* narrow screens: .dq-balance-grid wraps its 3 stats onto separate lines, so the absolutely-positioned
|
|
3081
3906
|
.dq-remaining-breakdown/.dq-runway-detail popovers can float down over whichever stat now sits in the
|
|
3082
3907
|
next row (e.g. "\u4F59\u989D\u660E\u7EC6" open covering "\u9884\u8BA1\u53EF\u7528"'s clickable summary) \u2014 force each stat onto its own
|
|
@@ -3233,6 +4058,22 @@ var css = `
|
|
|
3233
4058
|
package.json (see build.mjs) so it can never drift from the release. */
|
|
3234
4059
|
.dq-footer{margin:16px 0 0;font-size:12px;text-align:center;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
3235
4060
|
.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}
|
|
4061
|
+
/* \u9AD8\u5CF0\u6682\u505C\u5361\uFF1A\u8BF4\u660E\u3001\u72B6\u6001\u884C\u4E0E\u4F1A\u8BDD\u5217\u8868\u3002320px \u4E0B\u6807\u9898\u7701\u7565\u3001\u72B6\u6001\u4E0E\u5F00\u5173\u6362\u884C\uFF0C
|
|
4062
|
+
\u5217\u8868\u884C\u4E0D\u5141\u8BB8\u6491\u51FA\u5361\u7247\u5BBD\u5EA6\u3002\u603B\u5F00\u5173\u5173\u95ED\u65F6\u5217\u8868\u6574\u4F53\u5F31\u5316\uFF0C\u4F46\u6BCF\u4F1A\u8BDD\u5F00\u5173\u4ECD\u53EF\u64CD\u4F5C\u3002 */
|
|
4063
|
+
.dq-breaker-desc{margin:-6px 0 0}
|
|
4064
|
+
.dq-breaker-status{display:flex;align-items:center;flex-wrap:wrap;gap:8px;min-width:0;margin-top:10px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,#59636e)}
|
|
4065
|
+
.dq-breaker-list{display:flex;flex-direction:column;margin:12px 0 0;padding:0;list-style:none;min-width:0}
|
|
4066
|
+
.dq-breaker-list--muted{opacity:.62}
|
|
4067
|
+
.dq-breaker-row{display:flex;align-items:center;gap:12px;min-width:0;padding:9px 0;border-top:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.10))}
|
|
4068
|
+
.dq-breaker-row:first-child{border-top:0;padding-top:0}
|
|
4069
|
+
.dq-breaker-row-main{display:flex;align-items:center;flex-wrap:wrap;gap:4px 8px;flex:1 1 auto;min-width:0}
|
|
4070
|
+
.dq-breaker-row-title{min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
4071
|
+
.dq-breaker-row-state{font-size:12px;line-height:1.5;white-space:nowrap;color:var(--dq-text-warn)}
|
|
4072
|
+
.dq-breaker-switch{display:inline-flex;align-items:center;justify-content:center;flex:none;cursor:pointer}
|
|
4073
|
+
.dq-breaker-switch input{width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#0969da);cursor:pointer}
|
|
4074
|
+
.dq-breaker-switch input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
|
|
4075
|
+
.dq-breaker-switch input:disabled{cursor:default;opacity:.6}
|
|
4076
|
+
.dq-breaker-error{margin-top:8px;font-size:12px;line-height:1.5}
|
|
3236
4077
|
.dq-number:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;border-color:transparent}
|
|
3237
4078
|
.dq-links-title{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328);margin:0 0 12px}
|
|
3238
4079
|
.dq-links{display:flex;flex-wrap:wrap;gap:8px}
|
|
@@ -3449,7 +4290,7 @@ html[data-dq-quota-view] .dq-balance{padding-bottom:48px}
|
|
|
3449
4290
|
`;
|
|
3450
4291
|
|
|
3451
4292
|
// src/client/widget.tsx
|
|
3452
|
-
var
|
|
4293
|
+
var import_react9 = require("react");
|
|
3453
4294
|
|
|
3454
4295
|
// src/client/sessions.ts
|
|
3455
4296
|
function currentSessionIdOf(state) {
|
|
@@ -3458,7 +4299,7 @@ function currentSessionIdOf(state) {
|
|
|
3458
4299
|
}
|
|
3459
4300
|
|
|
3460
4301
|
// src/client/widget.tsx
|
|
3461
|
-
var
|
|
4302
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3462
4303
|
var MARGIN = 16;
|
|
3463
4304
|
var CORNER_KEY = "widget.corner";
|
|
3464
4305
|
var COLLAPSED_KEY = "widget.collapsed";
|
|
@@ -3509,47 +4350,47 @@ function cornerPos(corner, node, bounds) {
|
|
|
3509
4350
|
function QuotaWidget(props) {
|
|
3510
4351
|
const { t } = useI18n();
|
|
3511
4352
|
const sessionId = props.useSessions(currentSessionIdOf);
|
|
3512
|
-
const [visible, setVisible] = (0,
|
|
3513
|
-
(0,
|
|
3514
|
-
const [quotaViewActive2, setQuotaViewActive] = (0,
|
|
3515
|
-
(0,
|
|
3516
|
-
const [lowBalance, setLowBalance] = (0,
|
|
3517
|
-
(0,
|
|
3518
|
-
const [selectedTabIds, setSelectedTabIds] = (0,
|
|
3519
|
-
(0,
|
|
3520
|
-
const [viewTabs, setViewTabs] = (0,
|
|
3521
|
-
(0,
|
|
3522
|
-
const [activeViewId, setActiveViewId] = (0,
|
|
4353
|
+
const [visible, setVisible] = (0, import_react9.useState)(widgetVisibleStore.get());
|
|
4354
|
+
(0, import_react9.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
|
|
4355
|
+
const [quotaViewActive2, setQuotaViewActive] = (0, import_react9.useState)(quotaViewActiveStore.get());
|
|
4356
|
+
(0, import_react9.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
|
|
4357
|
+
const [lowBalance, setLowBalance] = (0, import_react9.useState)(lowBalanceStore.get());
|
|
4358
|
+
(0, import_react9.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
|
|
4359
|
+
const [selectedTabIds, setSelectedTabIds] = (0, import_react9.useState)(widgetTabIdsStore.get());
|
|
4360
|
+
(0, import_react9.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
|
|
4361
|
+
const [viewTabs, setViewTabs] = (0, import_react9.useState)(() => props.views.list());
|
|
4362
|
+
(0, import_react9.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
|
|
4363
|
+
const [activeViewId, setActiveViewId] = (0, import_react9.useState)(null);
|
|
3523
4364
|
const cachedBalance = getCachedBalance();
|
|
3524
|
-
const [data, setData] = (0,
|
|
3525
|
-
const [error, setError] = (0,
|
|
3526
|
-
const [loading, setLoading] = (0,
|
|
3527
|
-
const [collapsed, setCollapsed] = (0,
|
|
3528
|
-
const [pos, setPos] = (0,
|
|
3529
|
-
const [corner, setCorner] = (0,
|
|
3530
|
-
const [dragging, setDragging] = (0,
|
|
3531
|
-
const rootRef = (0,
|
|
3532
|
-
const dragRef = (0,
|
|
4365
|
+
const [data, setData] = (0, import_react9.useState)(cachedBalance?.data ?? null);
|
|
4366
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
4367
|
+
const [loading, setLoading] = (0, import_react9.useState)(cachedBalance === null);
|
|
4368
|
+
const [collapsed, setCollapsed] = (0, import_react9.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
|
|
4369
|
+
const [pos, setPos] = (0, import_react9.useState)(null);
|
|
4370
|
+
const [corner, setCorner] = (0, import_react9.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
|
|
4371
|
+
const [dragging, setDragging] = (0, import_react9.useState)(false);
|
|
4372
|
+
const rootRef = (0, import_react9.useRef)(null);
|
|
4373
|
+
const dragRef = (0, import_react9.useRef)(null);
|
|
3533
4374
|
const readUsage = () => {
|
|
3534
4375
|
const cached = getCachedUsage();
|
|
3535
4376
|
return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
|
|
3536
4377
|
};
|
|
3537
|
-
const [usage, setUsage] = (0,
|
|
3538
|
-
(0,
|
|
4378
|
+
const [usage, setUsage] = (0, import_react9.useState)(readUsage);
|
|
4379
|
+
(0, import_react9.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
|
|
3539
4380
|
const readSessionUsage = (id) => {
|
|
3540
4381
|
if (id === void 0) return null;
|
|
3541
4382
|
const response = getCachedSessionUsage(id);
|
|
3542
4383
|
return response?.ok === true ? response.data ?? null : null;
|
|
3543
4384
|
};
|
|
3544
|
-
const [sessionUsage, setSessionUsage] = (0,
|
|
3545
|
-
(0,
|
|
4385
|
+
const [sessionUsage, setSessionUsage] = (0, import_react9.useState)(() => readSessionUsage(sessionId));
|
|
4386
|
+
(0, import_react9.useEffect)(() => {
|
|
3546
4387
|
setSessionUsage(readSessionUsage(sessionId));
|
|
3547
4388
|
if (sessionId === void 0) return;
|
|
3548
4389
|
const unsubscribe = subscribeSessionUsage(sessionId, () => setSessionUsage(readSessionUsage(sessionId)));
|
|
3549
4390
|
void fetchSessionUsage(sessionId);
|
|
3550
4391
|
return unsubscribe;
|
|
3551
4392
|
}, [sessionId]);
|
|
3552
|
-
(0,
|
|
4393
|
+
(0, import_react9.useLayoutEffect)(() => {
|
|
3553
4394
|
const viewIds = viewTabs.map((tab) => tab.id);
|
|
3554
4395
|
const read = () => {
|
|
3555
4396
|
const next = getActiveConversationViewId(getShellFrame(rootRef.current), viewIds);
|
|
@@ -3586,14 +4427,14 @@ function QuotaWidget(props) {
|
|
|
3586
4427
|
setLoading(false);
|
|
3587
4428
|
}
|
|
3588
4429
|
};
|
|
3589
|
-
(0,
|
|
4430
|
+
(0, import_react9.useEffect)(() => {
|
|
3590
4431
|
void load(true);
|
|
3591
4432
|
const id = setInterval(() => {
|
|
3592
4433
|
void load(false);
|
|
3593
4434
|
}, 6e4);
|
|
3594
4435
|
return () => clearInterval(id);
|
|
3595
4436
|
}, []);
|
|
3596
|
-
(0,
|
|
4437
|
+
(0, import_react9.useLayoutEffect)(() => {
|
|
3597
4438
|
if (!visible || quotaViewActive2) return;
|
|
3598
4439
|
const node = rootRef.current;
|
|
3599
4440
|
if (node === null) return;
|
|
@@ -3721,9 +4562,9 @@ function QuotaWidget(props) {
|
|
|
3721
4562
|
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";
|
|
3722
4563
|
const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
|
|
3723
4564
|
const usageAgeText = ageMinutes === null || ageMinutes < 1 ? t("widget.updatedNow") : t("widget.stale", { minutes: ageMinutes });
|
|
3724
|
-
const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0,
|
|
3725
|
-
/* @__PURE__ */ (0,
|
|
3726
|
-
/* @__PURE__ */ (0,
|
|
4565
|
+
const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
|
|
4566
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
|
|
4567
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-quota-value", children: [
|
|
3727
4568
|
"\xA5",
|
|
3728
4569
|
fmtTurnCost(sessionUsage.cost)
|
|
3729
4570
|
] })
|
|
@@ -3731,50 +4572,50 @@ function QuotaWidget(props) {
|
|
|
3731
4572
|
let body = null;
|
|
3732
4573
|
if (!collapsed) {
|
|
3733
4574
|
if (loading && data === null && error === null) {
|
|
3734
|
-
body = /* @__PURE__ */ (0,
|
|
3735
|
-
/* @__PURE__ */ (0,
|
|
4575
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-body", children: [
|
|
4576
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: t("widget.loading") }),
|
|
3736
4577
|
sessionCostRow
|
|
3737
4578
|
] });
|
|
3738
4579
|
} else if (error !== null && data === null) {
|
|
3739
|
-
body = /* @__PURE__ */ (0,
|
|
3740
|
-
/* @__PURE__ */ (0,
|
|
4580
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-body", children: [
|
|
4581
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
|
|
3741
4582
|
sessionCostRow
|
|
3742
4583
|
] });
|
|
3743
4584
|
} else if (primary !== null) {
|
|
3744
|
-
body = /* @__PURE__ */ (0,
|
|
3745
|
-
/* @__PURE__ */ (0,
|
|
3746
|
-
/* @__PURE__ */ (0,
|
|
3747
|
-
/* @__PURE__ */ (0,
|
|
3748
|
-
/* @__PURE__ */ (0,
|
|
4585
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-body", children: [
|
|
4586
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
|
|
4587
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
|
|
4588
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
|
|
4589
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
|
|
3749
4590
|
] }),
|
|
3750
|
-
usage.data !== null && /* @__PURE__ */ (0,
|
|
3751
|
-
/* @__PURE__ */ (0,
|
|
3752
|
-
/* @__PURE__ */ (0,
|
|
4591
|
+
usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
|
|
4592
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
|
|
4593
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-quota-value", children: [
|
|
3753
4594
|
"\xA5",
|
|
3754
4595
|
fmt(usage.data.summary.today.cost)
|
|
3755
4596
|
] })
|
|
3756
4597
|
] }),
|
|
3757
4598
|
sessionCostRow,
|
|
3758
|
-
data?.isAvailable === false && /* @__PURE__ */ (0,
|
|
3759
|
-
/* @__PURE__ */ (0,
|
|
3760
|
-
/* @__PURE__ */ (0,
|
|
4599
|
+
data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-row", children: [
|
|
4600
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
|
|
4601
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
|
|
3761
4602
|
] })
|
|
3762
4603
|
] });
|
|
3763
4604
|
} else {
|
|
3764
|
-
body = /* @__PURE__ */ (0,
|
|
3765
|
-
/* @__PURE__ */ (0,
|
|
4605
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-body", children: [
|
|
4606
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
|
|
3766
4607
|
sessionCostRow
|
|
3767
4608
|
] });
|
|
3768
4609
|
}
|
|
3769
4610
|
}
|
|
3770
4611
|
const style = pos !== null ? { left: `${pos.x}px`, top: `${pos.y}px`, right: "auto", bottom: "auto" } : void 0;
|
|
3771
|
-
return /* @__PURE__ */ (0,
|
|
4612
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3772
4613
|
"div",
|
|
3773
4614
|
{
|
|
3774
4615
|
className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${hidden ? " dsh-quota-root--hidden" : ""}`,
|
|
3775
4616
|
style,
|
|
3776
4617
|
"aria-hidden": hidden,
|
|
3777
|
-
children: /* @__PURE__ */ (0,
|
|
4618
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
3778
4619
|
"div",
|
|
3779
4620
|
{
|
|
3780
4621
|
ref: rootRef,
|
|
@@ -3786,14 +4627,14 @@ function QuotaWidget(props) {
|
|
|
3786
4627
|
onPointerCancel: () => endDrag(false),
|
|
3787
4628
|
onLostPointerCapture: () => endDrag(false),
|
|
3788
4629
|
children: [
|
|
3789
|
-
/* @__PURE__ */ (0,
|
|
3790
|
-
/* @__PURE__ */ (0,
|
|
3791
|
-
/* @__PURE__ */ (0,
|
|
3792
|
-
/* @__PURE__ */ (0,
|
|
3793
|
-
/* @__PURE__ */ (0,
|
|
4630
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-header", children: [
|
|
4631
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-title", children: [
|
|
4632
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
|
|
4633
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: dotClass }),
|
|
4634
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
|
|
3794
4635
|
] }),
|
|
3795
|
-
/* @__PURE__ */ (0,
|
|
3796
|
-
/* @__PURE__ */ (0,
|
|
4636
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-actions", children: [
|
|
4637
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3797
4638
|
"button",
|
|
3798
4639
|
{
|
|
3799
4640
|
className: "dsh-quota-btn",
|
|
@@ -3809,7 +4650,7 @@ function QuotaWidget(props) {
|
|
|
3809
4650
|
children: "\u21BB"
|
|
3810
4651
|
}
|
|
3811
4652
|
),
|
|
3812
|
-
/* @__PURE__ */ (0,
|
|
4653
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3813
4654
|
"button",
|
|
3814
4655
|
{
|
|
3815
4656
|
className: "dsh-quota-btn",
|
|
@@ -3827,7 +4668,7 @@ function QuotaWidget(props) {
|
|
|
3827
4668
|
)
|
|
3828
4669
|
] })
|
|
3829
4670
|
] }),
|
|
3830
|
-
collapsed && primary !== null && /* @__PURE__ */ (0,
|
|
4671
|
+
collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
|
|
3831
4672
|
fmt(primary.total),
|
|
3832
4673
|
" ",
|
|
3833
4674
|
primary.currency
|
|
@@ -3858,7 +4699,7 @@ function conversationViewsSource(slots) {
|
|
|
3858
4699
|
}
|
|
3859
4700
|
|
|
3860
4701
|
// src/client/index.tsx
|
|
3861
|
-
var
|
|
4702
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
3862
4703
|
var name = "dsh-usage-dashboard";
|
|
3863
4704
|
var inject = ["slots", "locale", "layout"];
|
|
3864
4705
|
var openQuotaTab = null;
|
|
@@ -3879,7 +4720,15 @@ function apply(ctx) {
|
|
|
3879
4720
|
});
|
|
3880
4721
|
ctx.slots.inject("shell.overlay", () => ctx.slots.register(
|
|
3881
4722
|
{ name: "shell.overlay", id: "deepseek-quota", order: 1e3, locale: NS, label: () => t("widget.title") },
|
|
3882
|
-
({ t: slotT, useSessions }) => /* @__PURE__ */ (0,
|
|
4723
|
+
({ t: slotT, useSessions }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuotaWidget, { useSessions, views }) }) })
|
|
4724
|
+
));
|
|
4725
|
+
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register(
|
|
4726
|
+
{ name: "conversation.composer.dock", id: "quota-cost", order: 2, locale: NS },
|
|
4727
|
+
({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SessionCostPill, { sessionId }) }) })
|
|
4728
|
+
));
|
|
4729
|
+
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register(
|
|
4730
|
+
{ name: "conversation.composer.dock", id: "peak-breaker", order: 3, locale: NS },
|
|
4731
|
+
({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BreakerPill, { sessionId }) }) })
|
|
3883
4732
|
));
|
|
3884
4733
|
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register(
|
|
3885
4734
|
{
|
|
@@ -3889,13 +4738,13 @@ function apply(ctx) {
|
|
|
3889
4738
|
locale: NS,
|
|
3890
4739
|
label: () => t("messageCost.label")
|
|
3891
4740
|
},
|
|
3892
|
-
({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0,
|
|
4741
|
+
({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageCost, { sessionId, messageId }) }) })
|
|
3893
4742
|
));
|
|
3894
4743
|
const QuotaView = ({ t: slotT, sessionId, openView }) => {
|
|
3895
|
-
(0,
|
|
4744
|
+
(0, import_react10.useEffect)(() => {
|
|
3896
4745
|
if (openView !== void 0) openQuotaTab = openView;
|
|
3897
4746
|
}, [openView]);
|
|
3898
|
-
return /* @__PURE__ */ (0,
|
|
4747
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BalanceDashboard, { sessionId, views }) }) });
|
|
3899
4748
|
};
|
|
3900
4749
|
ctx.effect(() => {
|
|
3901
4750
|
let disposeView = null;
|
|
@@ -3931,14 +4780,14 @@ function apply(ctx) {
|
|
|
3931
4780
|
disposeView?.();
|
|
3932
4781
|
};
|
|
3933
4782
|
}, "dsh-usage-dashboard: quota tab registration");
|
|
3934
|
-
const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0,
|
|
3935
|
-
/* @__PURE__ */ (0,
|
|
3936
|
-
/* @__PURE__ */ (0,
|
|
3937
|
-
/* @__PURE__ */ (0,
|
|
4783
|
+
const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dq-panel", children: [
|
|
4784
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dq-panel-head", "data-window-drag": true, children: [
|
|
4785
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { className: "dq-panel-title", children: slotT("nav.quota") }),
|
|
4786
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dq-panel-caption", children: slotT("panel.caption") })
|
|
3938
4787
|
] }),
|
|
3939
|
-
/* @__PURE__ */ (0,
|
|
4788
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BalanceDashboard, { views }) }) })
|
|
3940
4789
|
] });
|
|
3941
|
-
const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0,
|
|
4790
|
+
const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
3942
4791
|
"svg",
|
|
3943
4792
|
{
|
|
3944
4793
|
className: "dq-side-icon",
|
|
@@ -3954,23 +4803,23 @@ function apply(ctx) {
|
|
|
3954
4803
|
"aria-hidden": "true",
|
|
3955
4804
|
focusable: "false",
|
|
3956
4805
|
children: [
|
|
3957
|
-
/* @__PURE__ */ (0,
|
|
3958
|
-
/* @__PURE__ */ (0,
|
|
3959
|
-
/* @__PURE__ */ (0,
|
|
3960
|
-
/* @__PURE__ */ (0,
|
|
4806
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M3.5 3.3 8 8.7l4.5-5.4" }),
|
|
4807
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M8 8.7V13.2" }),
|
|
4808
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M3.5 10.2h9" }),
|
|
4809
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M3.5 12.1h9" })
|
|
3961
4810
|
]
|
|
3962
4811
|
}
|
|
3963
4812
|
);
|
|
3964
4813
|
const QuotaFooterButton = ({ wide, t: slotT }) => {
|
|
3965
|
-
const [active, setActive] = (0,
|
|
3966
|
-
(0,
|
|
4814
|
+
const [active, setActive] = (0, import_react10.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
|
|
4815
|
+
(0, import_react10.useEffect)(() => {
|
|
3967
4816
|
const sync = () => {
|
|
3968
4817
|
setActive(ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
|
|
3969
4818
|
};
|
|
3970
4819
|
sync();
|
|
3971
4820
|
return ctx.layout.panelInfo.subscribe(sync);
|
|
3972
4821
|
}, []);
|
|
3973
|
-
return /* @__PURE__ */ (0,
|
|
4822
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
3974
4823
|
"button",
|
|
3975
4824
|
{
|
|
3976
4825
|
type: "button",
|
|
@@ -3982,8 +4831,8 @@ function apply(ctx) {
|
|
|
3982
4831
|
ctx.layout.selectPanel(active ? null : QUOTA_PANEL_ID);
|
|
3983
4832
|
},
|
|
3984
4833
|
children: [
|
|
3985
|
-
/* @__PURE__ */ (0,
|
|
3986
|
-
wide && /* @__PURE__ */ (0,
|
|
4834
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dq-side-entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuotaGlyph, { size: 16 }) }),
|
|
4835
|
+
wide && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dq-side-entry-label", children: slotT("nav.quota") })
|
|
3987
4836
|
]
|
|
3988
4837
|
}
|
|
3989
4838
|
);
|