@cassius0924/dsh-usage-dashboard 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 import_react10 = require("react");
29
+ var import_react8 = 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 current2 = `${storageKey}:v${LS_VERSION}`;
92
+ const current = `${storageKey}:v${LS_VERSION}`;
93
93
  for (const key of Object.keys(localStorage)) {
94
- if (key.startsWith(`${storageKey}:v`) && key !== current2) localStorage.removeItem(key);
94
+ if (key.startsWith(`${storageKey}:v`) && key !== current) localStorage.removeItem(key);
95
95
  }
96
96
  } catch {
97
97
  }
@@ -118,17 +118,6 @@ 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
- };
132
121
 
133
122
  // src/client/api.ts
134
123
  var BALANCE_TTL_MS = 6e4;
@@ -206,15 +195,15 @@ var sessionUsageCache = /* @__PURE__ */ new Map();
206
195
  var sessionUsageListeners = /* @__PURE__ */ new Map();
207
196
  var getCachedSessionUsage = (sessionId) => sessionUsageCache.get(sessionId) ?? null;
208
197
  var subscribeSessionUsage = (sessionId, fn) => {
209
- let listeners2 = sessionUsageListeners.get(sessionId);
210
- if (listeners2 === void 0) {
211
- listeners2 = /* @__PURE__ */ new Set();
212
- sessionUsageListeners.set(sessionId, listeners2);
198
+ let listeners = sessionUsageListeners.get(sessionId);
199
+ if (listeners === void 0) {
200
+ listeners = /* @__PURE__ */ new Set();
201
+ sessionUsageListeners.set(sessionId, listeners);
213
202
  }
214
- listeners2.add(fn);
203
+ listeners.add(fn);
215
204
  return () => {
216
- listeners2?.delete(fn);
217
- if (listeners2?.size === 0) sessionUsageListeners.delete(sessionId);
205
+ listeners?.delete(fn);
206
+ if (listeners?.size === 0) sessionUsageListeners.delete(sessionId);
218
207
  };
219
208
  };
220
209
  var publishSessionUsage = (sessionId, response) => {
@@ -241,118 +230,20 @@ async function fetchSessionUsage(sessionId, force = false) {
241
230
  sessionUsageInflight.set(sessionId, tracked);
242
231
  return tracked;
243
232
  }
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
- }
270
233
 
271
234
  // src/client/dashboard.tsx
272
- var import_react5 = require("react");
273
-
274
- // src/client/breaker.ts
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
- }
235
+ var import_react4 = require("react");
345
236
 
346
237
  // src/client/budget.ts
347
- var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
238
+ var BEIJING_OFFSET_MS = 8 * 60 * 60 * 1e3;
348
239
  function budgetSnapshot(spentValue, budgetValue, nowMs = Date.now()) {
349
240
  const spent = Number.isFinite(spentValue) ? Math.max(0, spentValue) : 0;
350
241
  const budget = Number.isFinite(budgetValue) ? Math.max(0, budgetValue) : 0;
351
- const beijingNow = new Date(nowMs + BEIJING_OFFSET_MS2);
242
+ const beijingNow = new Date(nowMs + BEIJING_OFFSET_MS);
352
243
  const year = beijingNow.getUTCFullYear();
353
244
  const month = beijingNow.getUTCMonth();
354
- const start = Date.UTC(year, month, 1) - BEIJING_OFFSET_MS2;
355
- const end = Date.UTC(year, month + 1, 1) - BEIJING_OFFSET_MS2;
245
+ const start = Date.UTC(year, month, 1) - BEIJING_OFFSET_MS;
246
+ const end = Date.UTC(year, month + 1, 1) - BEIJING_OFFSET_MS;
356
247
  const elapsedRatio = Math.min(1, Math.max(1 / (end - start), (nowMs - start) / (end - start)));
357
248
  const forecast = spent / elapsedRatio;
358
249
  const ratio = budget > 0 ? spent / budget : 0;
@@ -363,12 +254,12 @@ function budgetSnapshot(spentValue, budgetValue, nowMs = Date.now()) {
363
254
  }
364
255
 
365
256
  // src/client/charts.tsx
366
- var import_react4 = require("react");
257
+ var import_react3 = require("react");
367
258
 
368
259
  // src/client/chart-focus.ts
369
- function nextChartFocus(current2, count, key, horizontalStep = 1) {
260
+ function nextChartFocus(current, count, key, horizontalStep = 1) {
370
261
  if (count <= 0) return 0;
371
- const index = Math.min(Math.max(0, current2), count - 1);
262
+ const index = Math.min(Math.max(0, current), count - 1);
372
263
  if (key === "Home") return 0;
373
264
  if (key === "End") return count - 1;
374
265
  const step = key === "ArrowLeft" || key === "ArrowRight" ? Math.max(1, horizontalStep) : 1;
@@ -381,8 +272,8 @@ function lastPopulatedIndex(values) {
381
272
  }
382
273
  return Math.max(0, values.length - 1);
383
274
  }
384
- function nextPinnedIndex(current2, tapped) {
385
- return current2 === tapped ? null : tapped;
275
+ function nextPinnedIndex(current, tapped) {
276
+ return current === tapped ? null : tapped;
386
277
  }
387
278
  function isTapGesture(dx, dy, dt, moveThreshold = 10, timeThreshold = 500) {
388
279
  return Math.hypot(dx, dy) <= moveThreshold && dt <= timeThreshold;
@@ -397,7 +288,7 @@ function maxOrOne(values) {
397
288
  }
398
289
 
399
290
  // src/client/i18n.tsx
400
- var import_react3 = require("react");
291
+ var import_react2 = require("react");
401
292
 
402
293
  // src/client/locales.ts
403
294
  var NS = "dsh-usage-dashboard";
@@ -472,17 +363,24 @@ var zh = {
472
363
  "export.jsonHint": "\u542B\u56FE\u8868\u4E0E\u6392\u884C",
473
364
  "cache.empty": "\u6682\u65E0\u7F13\u5B58\u547D\u4E2D\u6570\u636E\u3002",
474
365
  "cache.rate": "\u7F13\u5B58\u547D\u4E2D\u7387",
475
- "cache.saved": "\u5DF2\u8282\u7701\u8D39\u7528\uFF08\u4F30\u7B97\uFF09",
476
- "cache.allMiss": "\u82E5\u5168\u90E8\u672A\u547D\u4E2D",
366
+ "cache.saved": "\u8F83\u5168\u672A\u547D\u4E2D\u7701\u4E0B",
367
+ "cache.allMiss": "\u82E5\u5168\u672A\u547D\u4E2D",
477
368
  "cache.aria": "\u7F13\u5B58\u547D\u4E2D {hit}%\uFF0C\u672A\u547D\u4E2D {miss}%",
478
369
  "cache.hitLegend": "\u7F13\u5B58\u547D\u4E2D {tokens}",
479
370
  "cache.missLegend": "\u672A\u547D\u4E2D {tokens}\uFF08\u6309\u672A\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF09",
371
+ "cache.basis": "\u5B9E\u4ED8 \xA5{cost}\uFF1B\u82E5\u5168\u90E8\u672A\u547D\u4E2D\u7EA6 \xA5{miss}\uFF0C\u4E24\u8005\u5DEE\u989D\u5373\u547D\u4E2D\u7F13\u5B58\u7701\u4E0B\u7684\u8D39\u7528\u3002",
480
372
  "peak.empty": "\u6682\u65E0\u7528\u91CF\u3002",
481
373
  "peak.aria": "\u9AD8\u5CF0\u65F6\u6BB5 {peak}%\uFF0C\u95F2\u65F6 {offPeak}%",
482
374
  "peak.peakLegend": "\u9AD8\u5CF0 {share}% \xB7 {tokens} tokens \xB7 {calls}",
483
375
  "peak.offPeakLegend": "\u95F2\u65F6 {share}% \xB7 {tokens} tokens \xB7 {calls}",
484
- "peak.shift": "\u82E5\u9AD8\u5CF0\u7528\u91CF\u90FD\u632A\u5230\u95F2\u65F6",
485
- "peak.saving": "\u53EF\u7701 \xA5{amount}",
376
+ "peak.shift": "\u9AD8\u5CF0\u6539\u8D70\u95F2\u65F6\u53EF\u7701",
377
+ "peak.saving": "\xA5{amount}",
378
+ "peak.basis": "\u5168\u90E8\u6539\u8D70\u95F2\u65F6\u7EA6 \xA5{total}\uFF1B\u6B64\u5904\u6309\u5F53\u524D\u4EF7\u76EE\u4F30\u7B97\uFF0C\u4E0E\u300C\u6D88\u8017\u6982\u89C8 \xB7 \u7D2F\u8BA1\u300D\u7684\u5B9E\u9645\u82B1\u8D39\u53E3\u5F84\u4E0D\u540C\u3002",
379
+ "peak.clockTitle": "\u4ECA\u65E5\u4EF7\u683C\u65F6\u6BB5",
380
+ "peak.nowPeak": "\u73B0\u5728\u9AD8\u5CF0 \xB7 \u5168\u4EF7",
381
+ "peak.nowOff": "\u73B0\u5728\u95F2\u65F6 \xB7 \u534A\u4EF7",
382
+ "peak.clockAria": "24 \u5C0F\u65F6\u4EF7\u683C\u65F6\u6BB5\uFF0C{state}",
383
+ "peak.clockHint": "\u5DE5\u4F5C\u65E5 09\u201312\u300114\u201318 \u4E3A\u9AD8\u5CF0\uFF0C\u5176\u4F59\u65F6\u6BB5\u534A\u4EF7\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09",
486
384
  "vision.empty": "{window}\u6CA1\u6709\u56FE\u7247\u8F93\u5165\u3002",
487
385
  "vision.title": "\u56FE\u7247 / \u591A\u6A21\u6001\u7528\u91CF",
488
386
  "vision.images": "\u56FE\u7247\u5F20\u6570",
@@ -555,7 +453,7 @@ var zh = {
555
453
  "sync.first": "\u9996\u6B21\u540C\u6B65\u4E2D",
556
454
  "sync.syncing": "\u540C\u6B65\u4E2D \xB7 \u5F53\u524D\u6570\u636E{age}",
557
455
  "sync.fresh": "\u5DF2\u540C\u6B65 \xB7 {age}",
558
- "sync.cached": "\u7F13\u5B58\u6570\u636E \xB7 {age}",
456
+ "sync.cached": "\u672C\u5730\u7F13\u5B58 \xB7 {age}",
559
457
  "sync.fallback": "\u7F13\u5B58\u56DE\u9000 \xB7 {age}",
560
458
  "sync.error": "\u540C\u6B65\u5931\u8D25 \xB7 \u6682\u65E0\u53EF\u663E\u793A\u7684\u7528\u91CF",
561
459
  "status.neverSynced": "\u8FD8\u6CA1\u6709\u6210\u529F\u540C\u6B65\u8FC7\u7528\u91CF\u6570\u636E",
@@ -620,18 +518,28 @@ var zh = {
620
518
  "models.title": "\u6A21\u578B\u6210\u672C\u6392\u884C",
621
519
  "sessions.title": "\u4F1A\u8BDD\u6210\u672C\u6392\u884C",
622
520
  "links.title": "\u5FEB\u6377\u5165\u53E3",
521
+ "nav.label": "\u9875\u9762\u5927\u7EB2",
522
+ "nav.balance": "\u4F59\u989D",
523
+ "nav.session": "\u5F53\u524D\u4F1A\u8BDD",
524
+ "nav.overview": "\u6D88\u8017\u6982\u89C8",
525
+ "nav.trend": "\u7528\u91CF\u8D8B\u52BF",
526
+ "nav.heatmap": "\u7528\u91CF\u70ED\u529B\u56FE",
527
+ "nav.models": "\u6A21\u578B\u6210\u672C\u6392\u884C",
528
+ "nav.sessions": "\u4F1A\u8BDD\u6210\u672C\u6392\u884C",
529
+ "nav.peakcache": "\u9AD8\u5CF0 / \u7F13\u5B58",
530
+ "nav.settings": "\u8BBE\u7F6E",
623
531
  "links.usage": "\u67E5\u770B\u989D\u5EA6 / \u7528\u91CF",
624
532
  "links.apiKey": "\u751F\u6210 API Key",
625
533
  "links.status": "\u670D\u52A1\u72B6\u6001",
626
534
  "settings.title": "\u8BBE\u7F6E",
535
+ "settings.groupDisplay": "\u663E\u793A",
536
+ "settings.groupAlerts": "\u63D0\u9192",
537
+ "settings.groupLayout": "\u5E03\u5C40\u4E0E\u5165\u53E3",
627
538
  "settings.widget": "\u5728\u5176\u4ED6\u9875\u9762\u663E\u793A\u53F3\u4E0B\u89D2\u60AC\u6D6E\u989D\u5EA6\u7A97\u53E3",
628
539
  "settings.widgetHint": "\u989D\u5EA6\u9875\u81EA\u52A8\u9690\u85CF\uFF1B\u5176\u4ED6\u9875\u9762\u6309\u4E0B\u65B9\u9009\u62E9\u663E\u793A\u3002",
629
540
  "settings.widgetTabs": "\u5C55\u793A\u6D6E\u7A97\u7684\u9875\u9762",
630
541
  "settings.widgetTabsHint": "\u5217\u8868\u4F1A\u968F\u5DF2\u5B89\u88C5\u7684\u9875\u9762\u81EA\u52A8\u66F4\u65B0\u3002",
631
542
  "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",
635
543
  "settings.lowBalance": "\u4F59\u989D\u9884\u8B66\u7EBF",
636
544
  "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",
637
545
  "settings.lowBalanceOff": "\u5DF2\u5173\u95ED\u4F59\u989D\u9884\u8B66\u3002\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u6570\u5F00\u542F\u3002",
@@ -648,32 +556,6 @@ var zh = {
648
556
  "settings.tabDragAria": "\u62D6\u52A8\u300C\u989D\u5EA6\u300D\u8C03\u6574\u6807\u7B7E\u4F4D\u7F6E",
649
557
  "settings.tabMoveEarlier": "\u524D\u79FB",
650
558
  "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}",
677
559
  "panel.caption": "\u8D26\u6237\u7EA7 \xB7 \u8DE8\u5168\u90E8\u4F1A\u8BDD",
678
560
  "panel.openAria": "\u6253\u5F00\u989D\u5EA6\u9875",
679
561
  "widget.updatedNow": "\u521A\u521A\u66F4\u65B0",
@@ -773,17 +655,24 @@ var en = {
773
655
  "export.jsonHint": "Charts and rankings included",
774
656
  "cache.empty": "No cache data yet.",
775
657
  "cache.rate": "Cache hit rate",
776
- "cache.saved": "Estimated savings",
777
- "cache.allMiss": "Cost with no hits",
658
+ "cache.saved": "Saved vs. all-miss",
659
+ "cache.allMiss": "Cost if all miss",
778
660
  "cache.aria": "{hit}% cache hits, {miss}% misses",
779
661
  "cache.hitLegend": "Cache hits {tokens}",
780
662
  "cache.missLegend": "Misses {tokens} (charged at miss rate)",
663
+ "cache.basis": "You paid \xA5{cost}; with no cache hits it would be about \xA5{miss}. The difference is what caching saved.",
781
664
  "peak.empty": "No usage yet.",
782
665
  "peak.aria": "{peak}% peak, {offPeak}% off-peak",
783
666
  "peak.peakLegend": "Peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
784
667
  "peak.offPeakLegend": "Off-peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
785
- "peak.shift": "If all peak usage moved off-peak",
786
- "peak.saving": "Save \xA5{amount}",
668
+ "peak.shift": "Saving if peak runs off-peak",
669
+ "peak.saving": "\xA5{amount}",
670
+ "peak.basis": "All off-peak would cost about \xA5{total}, estimated at current prices \u2014 a different basis from \u201CSpend overview \xB7 All time\u201D.",
671
+ "peak.clockTitle": "Today's price windows",
672
+ "peak.nowPeak": "Peak now \xB7 full price",
673
+ "peak.nowOff": "Off-peak now \xB7 half price",
674
+ "peak.clockAria": "24-hour price schedule, {state}",
675
+ "peak.clockHint": "Peak 09\u201312 & 14\u201318 on weekdays; half price otherwise (Beijing time)",
787
676
  "vision.empty": "No image input in {window}.",
788
677
  "vision.title": "Image / multimodal usage",
789
678
  "vision.images": "Images",
@@ -856,7 +745,7 @@ var en = {
856
745
  "sync.first": "Syncing for the first time",
857
746
  "sync.syncing": "Syncing \xB7 current data {age}",
858
747
  "sync.fresh": "Synced \xB7 {age}",
859
- "sync.cached": "Cached data \xB7 {age}",
748
+ "sync.cached": "Local cache \xB7 {age}",
860
749
  "sync.fallback": "Using cache \xB7 {age}",
861
750
  "sync.error": "Sync failed \xB7 no usage to display",
862
751
  "status.neverSynced": "Usage has not synced successfully yet",
@@ -921,18 +810,28 @@ var en = {
921
810
  "models.title": "Model cost ranking",
922
811
  "sessions.title": "Session cost ranking",
923
812
  "links.title": "Quick Links",
813
+ "nav.label": "On this page",
814
+ "nav.balance": "Balance",
815
+ "nav.session": "This session",
816
+ "nav.overview": "Spend overview",
817
+ "nav.trend": "Usage trend",
818
+ "nav.heatmap": "Usage heatmap",
819
+ "nav.models": "Model cost",
820
+ "nav.sessions": "Session cost",
821
+ "nav.peakcache": "Peak & cache",
822
+ "nav.settings": "Settings",
924
823
  "links.usage": "View balance / usage",
925
824
  "links.apiKey": "Create API key",
926
825
  "links.status": "Service status",
927
826
  "settings.title": "Settings",
827
+ "settings.groupDisplay": "Display",
828
+ "settings.groupAlerts": "Alerts",
829
+ "settings.groupLayout": "Layout & entry",
928
830
  "settings.widget": "Show the floating balance widget on other views",
929
831
  "settings.widgetHint": "Hidden on Usage; shown on the views selected below.",
930
832
  "settings.widgetTabs": "Show widget on",
931
833
  "settings.widgetTabsHint": "Updates automatically as views are installed.",
932
834
  "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",
936
835
  "settings.lowBalance": "Balance warning",
937
836
  "settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
938
837
  "settings.lowBalanceOff": "Balance warnings are off. Enter an amount above 0 to enable them.",
@@ -949,32 +848,6 @@ var en = {
949
848
  "settings.tabDragAria": "Drag Usage to change its tab position",
950
849
  "settings.tabMoveEarlier": "Move earlier",
951
850
  "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}",
978
851
  "panel.caption": "Account-wide \xB7 across all sessions",
979
852
  "panel.openAria": "Open Usage",
980
853
  "widget.updatedNow": "Updated just now",
@@ -1018,12 +891,12 @@ var fallbackT = (key, params) => {
1018
891
  if (params === void 0) return template;
1019
892
  return template.replace(/\{(\w+)\}/g, (match, name2) => name2 in params ? String(params[name2]) : match);
1020
893
  };
1021
- var TranslationContext = (0, import_react3.createContext)(fallbackT);
894
+ var TranslationContext = (0, import_react2.createContext)(fallbackT);
1022
895
  function LocaleProvider(props) {
1023
896
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TranslationContext.Provider, { value: props.t, children: props.children });
1024
897
  }
1025
898
  function useI18n() {
1026
- const t = (0, import_react3.useContext)(TranslationContext);
899
+ const t = (0, import_react2.useContext)(TranslationContext);
1027
900
  return { t, locale: t("meta.locale") === "en" ? "en" : "zh" };
1028
901
  }
1029
902
  function localizeApiError(message, t, fallback) {
@@ -1054,11 +927,11 @@ function localizeApiError(message, t, fallback) {
1054
927
  // src/client/charts.tsx
1055
928
  var import_jsx_runtime3 = require("react/jsx-runtime");
1056
929
  function useChartTip() {
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);
930
+ const wrapRef = (0, import_react3.useRef)(null);
931
+ const [tip, setTip] = (0, import_react3.useState)(null);
932
+ const [pinned, setPinned] = (0, import_react3.useState)(null);
933
+ const pinnedRef = (0, import_react3.useRef)(null);
934
+ const touchStart = (0, import_react3.useRef)(null);
1062
935
  const setPin = (next) => {
1063
936
  pinnedRef.current = next;
1064
937
  setPinned(next);
@@ -1097,7 +970,7 @@ function useChartTip() {
1097
970
  if (next === null) setTip(null);
1098
971
  else showAt(e.currentTarget, text, e.clientX, e.clientY);
1099
972
  };
1100
- (0, import_react4.useEffect)(() => {
973
+ (0, import_react3.useEffect)(() => {
1101
974
  if (pinned === null) return void 0;
1102
975
  const onDocPointerDown = (e) => {
1103
976
  const wrap = wrapRef.current;
@@ -1111,9 +984,9 @@ function useChartTip() {
1111
984
  return { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd };
1112
985
  }
1113
986
  function useRovingChartPoints(count, initial, horizontalStep = 1) {
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]);
987
+ const refs = (0, import_react3.useRef)([]);
988
+ const [active, setActive] = (0, import_react3.useState)(initial);
989
+ (0, import_react3.useEffect)(() => setActive(Math.min(Math.max(0, initial), Math.max(0, count - 1))), [count, initial]);
1117
990
  const onKeyDown = (event, index) => {
1118
991
  if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
1119
992
  event.preventDefault();
@@ -1137,11 +1010,12 @@ function ChartTip(props) {
1137
1010
  var MODEL_COLORS = ["#4176e6", "#2da44e", "#e16f24", "#8250df", "#bf8700", "#cf222e", "#1f883d", "#0969da"];
1138
1011
  function Bars(props) {
1139
1012
  const { data, height = 120, labelEvery = 1, minWidth } = props;
1013
+ const { locale } = useI18n();
1140
1014
  const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
1141
- const scrollRef = (0, import_react4.useRef)(null);
1015
+ const scrollRef = (0, import_react3.useRef)(null);
1142
1016
  const initialFocus = lastPopulatedIndex(data.map((datum) => datum.value));
1143
1017
  const roving = useRovingChartPoints(data.length, initialFocus);
1144
- (0, import_react4.useEffect)(() => {
1018
+ (0, import_react3.useEffect)(() => {
1145
1019
  const scroll = scrollRef.current;
1146
1020
  if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
1147
1021
  }, [data.length, minWidth]);
@@ -1149,87 +1023,102 @@ function Bars(props) {
1149
1023
  for (const datum of data) if (datum.value > max) max = datum.value;
1150
1024
  const scale = max > 0 ? max : 1;
1151
1025
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-chart-wrap", ref: wrapRef, onPointerLeave: hide, children: [
1152
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: data.map((datum, i) => {
1153
- const h = Math.max(1, Math.round(datum.value / scale * height));
1154
- const text = datum.title ?? `${datum.label}: ${datum.value.toLocaleString()}`;
1155
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1156
- "div",
1157
- {
1158
- className: `dq-bar-col${pinned === i ? " dq-bar-col--pinned" : ""}`,
1159
- role: "img",
1160
- "aria-label": text,
1161
- "aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
1162
- tabIndex: i === roving.active ? 0 : -1,
1163
- ref: (element) => {
1164
- roving.refs.current[i] = element;
1026
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-plot", children: [
1027
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-yaxis", style: { height: `${height}px` }, "aria-hidden": "true", children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: max > 0 ? fmtCompact(max, locale) : "" }),
1029
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "0" })
1030
+ ] }),
1031
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: data.map((datum, i) => {
1032
+ const h = Math.max(1, Math.round(datum.value / scale * height));
1033
+ const text = datum.title ?? `${datum.label}: ${datum.value.toLocaleString()}`;
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1035
+ "div",
1036
+ {
1037
+ className: `dq-bar-col${pinned === i ? " dq-bar-col--pinned" : ""}`,
1038
+ role: "img",
1039
+ "aria-label": text,
1040
+ "aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
1041
+ tabIndex: i === roving.active ? 0 : -1,
1042
+ ref: (element) => {
1043
+ roving.refs.current[i] = element;
1044
+ },
1045
+ onPointerMove: (e) => show(e, text),
1046
+ onPointerDown: onPointStart,
1047
+ onPointerUp: (e) => onPointEnd(e, i, text),
1048
+ onFocus: (e) => showFocused(e, text),
1049
+ onBlur: hide,
1050
+ onKeyDown: (e) => roving.onKeyDown(e, i),
1051
+ children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar", style: { height: `${h}px` } }),
1053
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: datum.label })
1054
+ ]
1165
1055
  },
1166
- onPointerMove: (e) => show(e, text),
1167
- onPointerDown: onPointStart,
1168
- onPointerUp: (e) => onPointEnd(e, i, text),
1169
- onFocus: (e) => showFocused(e, text),
1170
- onBlur: hide,
1171
- onKeyDown: (e) => roving.onKeyDown(e, i),
1172
- children: [
1173
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar", style: { height: `${h}px` } }),
1174
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: datum.label })
1175
- ]
1176
- },
1177
- i
1178
- );
1179
- }) }) }),
1056
+ i
1057
+ );
1058
+ }) }) })
1059
+ ] }),
1180
1060
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartTip, { tip })
1181
1061
  ] });
1182
1062
  }
1183
1063
  function GroupedBars(props) {
1184
1064
  const { series, height = 120, labelEvery = 1, minWidth } = props;
1065
+ const { locale } = useI18n();
1185
1066
  const { wrapRef, tip, pinned, show, showFocused, hide, onPointStart, onPointEnd } = useChartTip();
1186
- const scrollRef = (0, import_react4.useRef)(null);
1067
+ const scrollRef = (0, import_react3.useRef)(null);
1187
1068
  const count = series.length > 0 ? series[0].bars.length : 0;
1188
1069
  const pointValues = Array.from({ length: count }, (_, i) => series.map((s) => s.bars[i]?.value ?? 0)).flat();
1189
1070
  const roving = useRovingChartPoints(pointValues.length, lastPopulatedIndex(pointValues));
1190
- (0, import_react4.useEffect)(() => {
1071
+ (0, import_react3.useEffect)(() => {
1191
1072
  const scroll = scrollRef.current;
1192
1073
  if (scroll !== null && minWidth !== void 0) scroll.scrollLeft = scroll.scrollWidth;
1193
1074
  }, [count, minWidth]);
1194
- const scale = maxOrOne(stackedTotals(series, count));
1075
+ const totals = stackedTotals(series, count);
1076
+ const scale = maxOrOne(totals);
1077
+ const axisMax = totals.reduce((m, v) => Math.max(m, v), 0);
1195
1078
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-chart-wrap", ref: wrapRef, onPointerLeave: hide, children: [
1196
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1197
- "div",
1198
- {
1199
- className: "dq-bar-col",
1200
- children: [
1201
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-group", children: series.map((s, seriesIndex) => {
1202
- const datum = s.bars[i];
1203
- const h = Math.max(1, Math.round(datum.value / scale * height));
1204
- const text = datum.title ?? `${s.key}: ${datum.value.toLocaleString()}`;
1205
- const pointIndex = i * series.length + seriesIndex;
1206
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1207
- "div",
1208
- {
1209
- className: `dq-bar${pinned === pointIndex ? " dq-bar--pinned" : ""}`,
1210
- style: { height: `${h}px`, background: s.color },
1211
- role: "img",
1212
- "aria-label": text,
1213
- "aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
1214
- tabIndex: pointIndex === roving.active ? 0 : -1,
1215
- ref: (element) => {
1216
- roving.refs.current[pointIndex] = element;
1079
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-plot", children: [
1080
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dq-yaxis", style: { height: `${height}px` }, "aria-hidden": "true", children: [
1081
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: axisMax > 0 ? fmtCompact(axisMax, locale) : "" }),
1082
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "0" })
1083
+ ] }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars-scroll", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bars", style: { height: `${height + 18}px`, minWidth: minWidth === void 0 ? void 0 : `${minWidth}px` }, children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1085
+ "div",
1086
+ {
1087
+ className: "dq-bar-col",
1088
+ children: [
1089
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-group", children: series.map((s, seriesIndex) => {
1090
+ const datum = s.bars[i];
1091
+ const h = Math.max(1, Math.round(datum.value / scale * height));
1092
+ const text = datum.title ?? `${s.key}: ${datum.value.toLocaleString()}`;
1093
+ const pointIndex = i * series.length + seriesIndex;
1094
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1095
+ "div",
1096
+ {
1097
+ className: `dq-bar${pinned === pointIndex ? " dq-bar--pinned" : ""}`,
1098
+ style: { height: `${h}px`, background: s.color },
1099
+ role: "img",
1100
+ "aria-label": text,
1101
+ "aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Home End",
1102
+ tabIndex: pointIndex === roving.active ? 0 : -1,
1103
+ ref: (element) => {
1104
+ roving.refs.current[pointIndex] = element;
1105
+ },
1106
+ onPointerMove: (e) => show(e, text),
1107
+ onPointerDown: onPointStart,
1108
+ onPointerUp: (e) => onPointEnd(e, pointIndex, text),
1109
+ onFocus: (e) => showFocused(e, text),
1110
+ onBlur: hide,
1111
+ onKeyDown: (e) => roving.onKeyDown(e, pointIndex)
1217
1112
  },
1218
- onPointerMove: (e) => show(e, text),
1219
- onPointerDown: onPointStart,
1220
- onPointerUp: (e) => onPointEnd(e, pointIndex, text),
1221
- onFocus: (e) => showFocused(e, text),
1222
- onBlur: hide,
1223
- onKeyDown: (e) => roving.onKeyDown(e, pointIndex)
1224
- },
1225
- s.key
1226
- );
1227
- }) }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: series[0].bars[i].label })
1229
- ]
1230
- },
1231
- i
1232
- )) }) }),
1113
+ s.key
1114
+ );
1115
+ }) }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dq-bar-label", style: { visibility: i % labelEvery === 0 ? "visible" : "hidden" }, children: series[0].bars[i].label })
1117
+ ]
1118
+ },
1119
+ i
1120
+ )) }) })
1121
+ ] }),
1233
1122
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartTip, { tip })
1234
1123
  ] });
1235
1124
  }
@@ -1389,9 +1278,9 @@ function getActiveConversationViewId(frame, viewIds) {
1389
1278
  }
1390
1279
 
1391
1280
  // src/client/export.ts
1392
- var BEIJING_OFFSET_MS3 = 8 * 60 * 60 * 1e3;
1281
+ var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
1393
1282
  function exportDateStamp(nowMs = Date.now()) {
1394
- const date = new Date(nowMs + BEIJING_OFFSET_MS3);
1283
+ const date = new Date(nowMs + BEIJING_OFFSET_MS2);
1395
1284
  return [
1396
1285
  date.getUTCFullYear(),
1397
1286
  String(date.getUTCMonth() + 1).padStart(2, "0"),
@@ -1507,19 +1396,19 @@ var isBoolean = (value) => typeof value === "boolean";
1507
1396
 
1508
1397
  // src/client/store.ts
1509
1398
  function createStore(key, isValid, fallback) {
1510
- let current2 = loadPref(key, isValid, fallback);
1511
- const listeners2 = /* @__PURE__ */ new Set();
1399
+ let current = loadPref(key, isValid, fallback);
1400
+ const listeners = /* @__PURE__ */ new Set();
1512
1401
  return {
1513
- get: () => current2,
1402
+ get: () => current,
1514
1403
  set: (value) => {
1515
- current2 = value;
1404
+ current = value;
1516
1405
  savePref(key, value);
1517
- for (const listener of [...listeners2]) listener();
1406
+ for (const listener of [...listeners]) listener();
1518
1407
  },
1519
1408
  subscribe: (fn) => {
1520
- listeners2.add(fn);
1409
+ listeners.add(fn);
1521
1410
  return () => {
1522
- listeners2.delete(fn);
1411
+ listeners.delete(fn);
1523
1412
  };
1524
1413
  }
1525
1414
  };
@@ -1582,16 +1471,6 @@ function needsTopUp(balance, warningLine) {
1582
1471
  // src/client/dashboard.tsx
1583
1472
  var import_jsx_runtime4 = require("react/jsx-runtime");
1584
1473
  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
- }
1595
1474
  function Skel(props) {
1596
1475
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel", style: { width: `${props.w}px`, height: `${props.h}px` } });
1597
1476
  }
@@ -1641,18 +1520,19 @@ function HeatmapSkeleton() {
1641
1520
  }
1642
1521
  function Delta(props) {
1643
1522
  const { t } = useI18n();
1644
- const { current: current2, previous, label } = props;
1523
+ const { current, previous, label } = props;
1645
1524
  if (previous <= 0) {
1646
- if (current2 <= 0) return null;
1525
+ if (current <= 0) return null;
1647
1526
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--new", title: t("delta.noBaselineTitle", { label }), children: t("delta.noBaseline") });
1648
1527
  }
1649
- const percent = Math.round((current2 - previous) / previous * 100);
1528
+ const percent = Math.round((current - previous) / previous * 100);
1650
1529
  const title = `${label} \xA5${fmt(previous)}`;
1651
1530
  if (percent === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--flat", title, children: t("delta.flat") });
1652
1531
  const up = percent > 0;
1532
+ const magnitude = Math.abs(percent) >= 1e3 ? "999+" : String(Math.abs(percent));
1653
1533
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: `dq-delta ${up ? "dq-delta--up" : "dq-delta--down"}`, title, children: [
1654
1534
  up ? "\u2191" : "\u2193",
1655
- Math.abs(percent),
1535
+ magnitude,
1656
1536
  "% ",
1657
1537
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta-label", children: label })
1658
1538
  ] });
@@ -1734,7 +1614,7 @@ function PricingNote(props) {
1734
1614
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheMiss") }),
1735
1615
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.output") })
1736
1616
  ] }) }),
1737
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react5.Fragment, { children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react4.Fragment, { children: [
1738
1618
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1739
1619
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan: 2, children: tier.model }),
1740
1620
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.peak") }),
@@ -1810,6 +1690,137 @@ function CardTitle(props) {
1810
1690
  props.caption !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-card-caption", children: props.caption })
1811
1691
  ] });
1812
1692
  }
1693
+ function SectionNav(props) {
1694
+ const { rootRef, label, sections } = props;
1695
+ const [targets, setTargets] = (0, import_react4.useState)([]);
1696
+ const [active, setActive] = (0, import_react4.useState)(0);
1697
+ const [pos, setPos] = (0, import_react4.useState)({ right: 0, visible: false });
1698
+ const signature = sections.map((section) => `${section.key}:${section.label}`).join("|");
1699
+ (0, import_react4.useEffect)(() => {
1700
+ const root = rootRef.current;
1701
+ if (root === null) return;
1702
+ const nodes = Array.from(root.querySelectorAll(":scope > .dq-card, :scope > .dq-card-pair"));
1703
+ setTargets(nodes.map((el, i) => ({ el, label: sections[i]?.label ?? "" })).filter((item) => item.label !== ""));
1704
+ }, [rootRef, signature]);
1705
+ (0, import_react4.useEffect)(() => {
1706
+ const root = rootRef.current;
1707
+ if (root === null) return;
1708
+ const scrollport = root.closest("[data-conversation-scroll]");
1709
+ const measure = () => {
1710
+ const rect = root.getBoundingClientRect();
1711
+ setPos({ right: window.innerWidth - rect.left + 12, visible: window.innerWidth >= 1200 && rect.left >= 170 });
1712
+ };
1713
+ measure();
1714
+ const scroller = scrollport ?? window;
1715
+ scroller.addEventListener("scroll", measure, { passive: true });
1716
+ window.addEventListener("resize", measure);
1717
+ return () => {
1718
+ scroller.removeEventListener("scroll", measure);
1719
+ window.removeEventListener("resize", measure);
1720
+ };
1721
+ }, [rootRef]);
1722
+ (0, import_react4.useEffect)(() => {
1723
+ if (targets.length === 0) return;
1724
+ const root = rootRef.current;
1725
+ if (root === null) return;
1726
+ const scrollport = root.closest("[data-conversation-scroll]");
1727
+ const onScroll = () => {
1728
+ const base = scrollport === null ? 0 : scrollport.getBoundingClientRect().top;
1729
+ let current = 0;
1730
+ targets.forEach((target, i) => {
1731
+ if (target.el.getBoundingClientRect().top - base <= 120) current = i;
1732
+ });
1733
+ setActive(current);
1734
+ };
1735
+ onScroll();
1736
+ const scroller = scrollport ?? window;
1737
+ scroller.addEventListener("scroll", onScroll, { passive: true });
1738
+ return () => scroller.removeEventListener("scroll", onScroll);
1739
+ }, [targets, rootRef]);
1740
+ if (targets.length < 3) return null;
1741
+ const go = (el) => {
1742
+ const root = rootRef.current;
1743
+ const scrollport = root?.closest("[data-conversation-scroll]");
1744
+ const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1745
+ const behavior = reduce ? "auto" : "smooth";
1746
+ if (scrollport !== null) {
1747
+ const base = scrollport.getBoundingClientRect().top;
1748
+ scrollport.scrollTo({ top: scrollport.scrollTop + el.getBoundingClientRect().top - base - 12, behavior });
1749
+ } else {
1750
+ window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 12, behavior });
1751
+ }
1752
+ };
1753
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: `dq-sectionnav${pos.visible ? "" : " dq-sectionnav--hidden"}`, style: { right: `${pos.right}px` }, "aria-label": label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { children: targets.map((target, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1754
+ "button",
1755
+ {
1756
+ type: "button",
1757
+ className: `dq-sectionnav-dot${i === active ? " dq-sectionnav-dot--active" : ""}`,
1758
+ "aria-current": i === active ? "true" : void 0,
1759
+ onClick: () => go(target.el),
1760
+ children: [
1761
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sectionnav-label", children: target.label }),
1762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sectionnav-pip", "aria-hidden": "true" })
1763
+ ]
1764
+ }
1765
+ ) }, `${target.label}-${String(i)}`)) }) });
1766
+ }
1767
+ function Collapsible(props) {
1768
+ const ref = (0, import_react4.useRef)(null);
1769
+ const animationRef = (0, import_react4.useRef)(null);
1770
+ const open = props.open;
1771
+ (0, import_react4.useLayoutEffect)(() => {
1772
+ const el = ref.current;
1773
+ if (el === null) return;
1774
+ animationRef.current?.cancel();
1775
+ animationRef.current = null;
1776
+ const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1777
+ const canAnimate = typeof el.animate === "function" && !reduce;
1778
+ const reset = () => {
1779
+ el.style.height = "";
1780
+ el.style.overflow = "";
1781
+ el.style.opacity = "";
1782
+ };
1783
+ if (open) {
1784
+ if (!canAnimate) {
1785
+ el.style.display = "";
1786
+ reset();
1787
+ return;
1788
+ }
1789
+ el.style.display = "block";
1790
+ el.style.overflow = "hidden";
1791
+ el.style.height = "0px";
1792
+ el.style.opacity = "0";
1793
+ const height2 = el.scrollHeight;
1794
+ const animation2 = el.animate(
1795
+ [{ height: "0px", opacity: 0 }, { height: `${height2}px`, opacity: 1 }],
1796
+ { duration: 300, easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
1797
+ );
1798
+ animationRef.current = animation2;
1799
+ animation2.addEventListener("finish", reset);
1800
+ return () => animation2.cancel();
1801
+ }
1802
+ if (el.style.display === "none") return;
1803
+ if (!canAnimate) {
1804
+ reset();
1805
+ el.style.display = "none";
1806
+ return;
1807
+ }
1808
+ const height = el.getBoundingClientRect().height || el.scrollHeight;
1809
+ el.style.overflow = "hidden";
1810
+ el.style.height = `${height}px`;
1811
+ const animation = el.animate(
1812
+ [{ height: `${height}px`, opacity: 1 }, { height: "0px", opacity: 0 }],
1813
+ { duration: 220, easing: "cubic-bezier(0.4, 0, 1, 1)" }
1814
+ );
1815
+ animationRef.current = animation;
1816
+ animation.addEventListener("finish", () => {
1817
+ reset();
1818
+ el.style.display = "none";
1819
+ });
1820
+ return () => animation.cancel();
1821
+ }, [open]);
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, children: props.children });
1823
+ }
1813
1824
  function Stat(props) {
1814
1825
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
1815
1826
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: props.label }),
@@ -1821,13 +1832,13 @@ function Link(props) {
1821
1832
  }
1822
1833
  function ExportMenu(props) {
1823
1834
  const { t } = useI18n();
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)(() => {
1835
+ const [open, setOpen] = (0, import_react4.useState)(false);
1836
+ const [done, setDone] = (0, import_react4.useState)(false);
1837
+ const rootRef = (0, import_react4.useRef)(null);
1838
+ const triggerRef = (0, import_react4.useRef)(null);
1839
+ const menuRef = (0, import_react4.useRef)(null);
1840
+ const doneTimerRef = (0, import_react4.useRef)(null);
1841
+ (0, import_react4.useEffect)(() => {
1831
1842
  if (!open) return;
1832
1843
  const onDown = (event) => {
1833
1844
  if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
@@ -1845,7 +1856,7 @@ function ExportMenu(props) {
1845
1856
  document.removeEventListener("keydown", onKey);
1846
1857
  };
1847
1858
  }, [open]);
1848
- (0, import_react5.useEffect)(() => () => {
1859
+ (0, import_react4.useEffect)(() => () => {
1849
1860
  if (doneTimerRef.current !== null) window.clearTimeout(doneTimerRef.current);
1850
1861
  }, []);
1851
1862
  const finish = () => {
@@ -2015,7 +2026,8 @@ function CacheCard(props) {
2015
2026
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--miss" }),
2016
2027
  t("cache.missLegend", { tokens: fmtCompact(totals.input, locale) })
2017
2028
  ] })
2018
- ] })
2029
+ ] }),
2030
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: t("cache.basis", { cost: fmt(totals.cost), miss: fmt(wouldHaveCost) }) })
2019
2031
  ] });
2020
2032
  }
2021
2033
  function VisionCard(props) {
@@ -2058,11 +2070,44 @@ function VisionCard(props) {
2058
2070
  function PeakCard(props) {
2059
2071
  const { t, locale } = useI18n();
2060
2072
  const { split, pricing } = props;
2073
+ const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
2074
+ (0, import_react4.useEffect)(() => {
2075
+ const id = setInterval(() => setNowMs(Date.now()), 6e4);
2076
+ return () => clearInterval(id);
2077
+ }, []);
2061
2078
  const total = split.peak.total + split.offPeak.total;
2062
2079
  if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") });
2063
2080
  const peakShare = split.peak.total / total;
2064
2081
  const shiftSaving = split.peakEraCost - split.offPeakEraCost;
2082
+ const beijing = new Date(nowMs + 8 * 36e5);
2083
+ const hour = beijing.getUTCHours();
2084
+ const minute = beijing.getUTCMinutes();
2085
+ const weekday = beijing.getUTCDay();
2086
+ const weekend = weekday === 0 || weekday === 6;
2087
+ const windows = pricing.peakWindows.map((w) => w.match(/(\d{1,2}):\d{2}\D+(\d{1,2}):\d{2}/)).filter((m) => m !== null).map((m) => [Number(m[1]), Number(m[2])]);
2088
+ const isPeakNow = !weekend && windows.some(([from, to]) => hour >= from && hour < to);
2089
+ const nowPercent = (hour + minute / 60) / 24 * 100;
2090
+ const clockCells = Array.from({ length: 24 }, (_, h) => !weekend && windows.some(([from, to]) => h >= from && h < to));
2091
+ const nowState = isPeakNow ? t("peak.nowPeak") : t("peak.nowOff");
2065
2092
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak", children: [
2093
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock", children: [
2094
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-head", children: [
2095
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-clock-title", children: t("peak.clockTitle") }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-clock-status ${isPeakNow ? "dq-clock-status--peak" : "dq-clock-status--off"}`, children: nowState })
2097
+ ] }),
2098
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-track", role: "img", "aria-label": t("peak.clockAria", { state: nowState }), children: [
2099
+ clockCells.map((peak, h) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-clock-cell${peak ? " dq-clock-cell--peak" : ""}` }, h)),
2100
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-clock-now", style: { left: `${nowPercent}%` }, "aria-hidden": "true" })
2101
+ ] }),
2102
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-scale", "aria-hidden": "true", children: [
2103
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "0" }),
2104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "6" }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "12" }),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "18" }),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "24" })
2108
+ ] }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-clock-hint", children: t("peak.clockHint") })
2110
+ ] }),
2066
2111
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2067
2112
  "div",
2068
2113
  {
@@ -2092,12 +2137,9 @@ function PeakCard(props) {
2092
2137
  ] }),
2093
2138
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-figures", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2094
2139
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("peak.shift") }),
2095
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
2096
- "\xA5",
2097
- fmt(split.offPeakEraCost),
2098
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
2099
- ] })
2100
- ] }) })
2140
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value dq-stat-value--ok", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
2141
+ ] }) }),
2142
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-peak-foot", children: t("peak.basis", { total: fmt(split.offPeakEraCost) }) })
2101
2143
  ] });
2102
2144
  }
2103
2145
  function agoText(ms, t) {
@@ -2119,8 +2161,8 @@ function SessionDetail(props) {
2119
2161
  function SessionRanking(props) {
2120
2162
  const { t, locale } = useI18n();
2121
2163
  const { sessions, count, totalCost } = props;
2122
- const [details, setDetails] = (0, import_react5.useState)({});
2123
- const startedRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
2164
+ const [details, setDetails] = (0, import_react4.useState)({});
2165
+ const startedRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
2124
2166
  const loadDetail = (id) => {
2125
2167
  if (startedRef.current.has(id)) return;
2126
2168
  startedRef.current.add(id);
@@ -2221,11 +2263,11 @@ function ModelRanking(props) {
2221
2263
  function ModelPicker(props) {
2222
2264
  const { t, locale } = useI18n();
2223
2265
  const { models } = props;
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)(() => {
2266
+ const [open, setOpen] = (0, import_react4.useState)(false);
2267
+ const rootRef = (0, import_react4.useRef)(null);
2268
+ const triggerRef = (0, import_react4.useRef)(null);
2269
+ const menuRef = (0, import_react4.useRef)(null);
2270
+ (0, import_react4.useEffect)(() => {
2229
2271
  if (!open) return;
2230
2272
  const onDown = (e) => {
2231
2273
  if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
@@ -2327,55 +2369,46 @@ function BalanceDashboard(props) {
2327
2369
  const cachedBalance = getCachedBalance();
2328
2370
  const cachedUsage = getCachedUsage();
2329
2371
  const cachedUsageAt = getCachedUsageAt();
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)(() => {
2372
+ const [balance, setBalance] = (0, import_react4.useState)(cachedBalance?.data ?? null);
2373
+ const [usage, setUsage] = (0, import_react4.useState)(cachedUsage?.data ?? null);
2374
+ const [error, setError] = (0, import_react4.useState)(null);
2375
+ const [notice, setNotice] = (0, import_react4.useState)(null);
2376
+ const [loadingBalance, setLoadingBalance] = (0, import_react4.useState)(cachedBalance === null);
2377
+ const [loadingUsage, setLoadingUsage] = (0, import_react4.useState)(cachedUsage === null);
2378
+ const [refreshing, setRefreshing] = (0, import_react4.useState)(false);
2379
+ const [usageUpdatedAt, setUsageUpdatedAt] = (0, import_react4.useState)(cachedUsageAt);
2380
+ const [syncState, setSyncState] = (0, import_react4.useState)(cachedUsageAt === null ? "syncing" : "cached");
2381
+ const [freshnessNow, setFreshnessNow] = (0, import_react4.useState)(Date.now());
2382
+ const [settingsOpen, setSettingsOpen] = (0, import_react4.useState)(false);
2383
+ const hadDataRef = (0, import_react4.useRef)(cachedBalance !== null || cachedUsage !== null);
2384
+ const [widgetOn, setWidgetOn] = (0, import_react4.useState)(widgetVisibleStore.get());
2385
+ const [widgetTabIds, setWidgetTabIds] = (0, import_react4.useState)(widgetTabIdsStore.get());
2386
+ const [viewTabs, setViewTabs] = (0, import_react4.useState)(() => props.views.list());
2387
+ (0, import_react4.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
2388
+ const [lowBalance, setLowBalance] = (0, import_react4.useState)(lowBalanceStore.get());
2389
+ const [monthlyBudget, setMonthlyBudget] = (0, import_react4.useState)(monthlyBudgetStore.get());
2390
+ const [barMode, setBarMode] = (0, import_react4.useState)("daily");
2391
+ const [selectedModels, setSelectedModels] = (0, import_react4.useState)([]);
2392
+ const [windowDays, setWindowDays] = (0, import_react4.useState)(usageWindowStore.get());
2393
+ const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
2394
+ const [sidebarEntry, setSidebarEntry] = (0, import_react4.useState)(sidebarEntryStore.get());
2395
+ (0, import_react4.useEffect)(() => sidebarEntryStore.subscribe(() => setSidebarEntry(sidebarEntryStore.get())), []);
2396
+ const [tabDragging, setTabDragging] = (0, import_react4.useState)(false);
2397
+ const [tabDropTarget, setTabDropTarget] = (0, import_react4.useState)(null);
2398
+ const [retainedVision, setRetainedVision] = (0, import_react4.useState)(null);
2399
+ const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
2400
+ const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
2401
+ const [remainingHovering, setRemainingHovering] = (0, import_react4.useState)(false);
2402
+ const [remainingManuallyOpened, setRemainingManuallyOpened] = (0, import_react4.useState)(false);
2403
+ const budgetInputRef = (0, import_react4.useRef)(null);
2404
+ const rootRef = (0, import_react4.useRef)(null);
2405
+ const [composerHeight, setComposerHeight] = (0, import_react4.useState)(null);
2406
+ const [composerWidth, setComposerWidth] = (0, import_react4.useState)(null);
2407
+ const [sessionUsage, setSessionUsage] = (0, import_react4.useState)(null);
2408
+ const [loadingSessionUsage, setLoadingSessionUsage] = (0, import_react4.useState)(props.sessionId !== void 0 && props.sessionId !== "");
2409
+ const [sessionUsageError, setSessionUsageError] = (0, import_react4.useState)(null);
2410
+ const sessionUsageIdRef = (0, import_react4.useRef)(null);
2411
+ (0, import_react4.useLayoutEffect)(() => {
2379
2412
  const node = rootRef.current;
2380
2413
  if (node === null) return;
2381
2414
  const measure = () => {
@@ -2406,13 +2439,13 @@ function BalanceDashboard(props) {
2406
2439
  window.removeEventListener("resize", measure);
2407
2440
  };
2408
2441
  }, []);
2409
- (0, import_react5.useLayoutEffect)(() => {
2442
+ (0, import_react4.useLayoutEffect)(() => {
2410
2443
  const node = rootRef.current;
2411
2444
  if (node === null) return;
2412
2445
  const scrollport = node.closest("[data-conversation-scroll]");
2413
2446
  if (scrollport instanceof HTMLElement) scrollport.scrollTop = 0;
2414
2447
  }, [props.sessionId]);
2415
- (0, import_react5.useLayoutEffect)(() => {
2448
+ (0, import_react4.useLayoutEffect)(() => {
2416
2449
  quotaViewActiveStore.set(true);
2417
2450
  return () => quotaViewActiveStore.set(false);
2418
2451
  }, []);
@@ -2486,10 +2519,10 @@ function BalanceDashboard(props) {
2486
2519
  if (sessionUsageIdRef.current === id) setLoadingSessionUsage(false);
2487
2520
  }
2488
2521
  };
2489
- (0, import_react5.useEffect)(() => {
2522
+ (0, import_react4.useEffect)(() => {
2490
2523
  void load(false);
2491
2524
  }, []);
2492
- (0, import_react5.useEffect)(() => {
2525
+ (0, import_react4.useEffect)(() => {
2493
2526
  const id = props.sessionId;
2494
2527
  sessionUsageIdRef.current = id ?? null;
2495
2528
  if (id === void 0 || id === "") {
@@ -2503,57 +2536,7 @@ function BalanceDashboard(props) {
2503
2536
  setLoadingSessionUsage(true);
2504
2537
  void loadSessionUsage(id, false);
2505
2538
  }, [props.sessionId]);
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)(() => {
2539
+ (0, import_react4.useEffect)(() => {
2557
2540
  const timer = window.setInterval(() => setFreshnessNow(Date.now()), 3e4);
2558
2541
  return () => window.clearInterval(timer);
2559
2542
  }, []);
@@ -2577,66 +2560,6 @@ function BalanceDashboard(props) {
2577
2560
  setMonthlyBudget(value);
2578
2561
  monthlyBudgetStore.set(value);
2579
2562
  };
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
- };
2640
2563
  const changeWindowDays = (value) => {
2641
2564
  setWindowDays(value);
2642
2565
  usageWindowStore.set(value);
@@ -2732,7 +2655,7 @@ function BalanceDashboard(props) {
2732
2655
  costTotal: window2.totals.cost
2733
2656
  });
2734
2657
  const liveVision = visionOpen && activeWindow !== null && visionStats !== void 0 ? visionPayload(activeWindow, visionStats, activeWindowName) : null;
2735
- (0, import_react5.useEffect)(() => {
2658
+ (0, import_react4.useEffect)(() => {
2736
2659
  if (visionOpen && activeWindow !== null && visionStats !== void 0) {
2737
2660
  setRetainedVision(visionPayload(activeWindow, visionStats, activeWindowName));
2738
2661
  return;
@@ -2816,15 +2739,6 @@ function BalanceDashboard(props) {
2816
2739
  { error: localizeApiError(notice.error, t, "error.query") }
2817
2740
  );
2818
2741
  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
- };
2828
2742
  const composerSafeAreaStyle = {
2829
2743
  "--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px`,
2830
2744
  // Only set once measured; .dq-balance's max-width falls back to 860px
@@ -2832,7 +2746,18 @@ function BalanceDashboard(props) {
2832
2746
  // column, then narrows in place — no flash of the old width).
2833
2747
  ...composerWidth !== null ? { "--dq-composer-w": `${composerWidth}px` } : {}
2834
2748
  };
2749
+ const navSections = [
2750
+ { key: "balance", label: t("nav.balance") },
2751
+ ...props.sessionId !== void 0 && props.sessionId !== "" ? [{ key: "session", label: t("nav.session") }] : [],
2752
+ { key: "overview", label: t("nav.overview") },
2753
+ { key: "trend", label: t("nav.trend") },
2754
+ { key: "heatmap", label: t("nav.heatmap") },
2755
+ { key: "models", label: t("nav.models") },
2756
+ { key: "sessions", label: t("nav.sessions") },
2757
+ { key: "peakcache", label: t("nav.peakcache") }
2758
+ ];
2835
2759
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance", ref: rootRef, style: composerSafeAreaStyle, children: [
2760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SectionNav, { rootRef, label: t("nav.label"), sections: navSections }),
2836
2761
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-row", children: [
2837
2762
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-messages", children: [
2838
2763
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2851,23 +2776,39 @@ function BalanceDashboard(props) {
2851
2776
  noticeText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-warn", children: noticeText }),
2852
2777
  errorText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-error", children: errorText })
2853
2778
  ] }),
2854
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2855
- "button",
2856
- {
2857
- type: "button",
2858
- className: "dq-refresh-btn",
2859
- title: t("status.forceRefresh"),
2860
- disabled: refreshing,
2861
- "aria-busy": refreshing,
2862
- onClick: () => {
2863
- void load(true);
2864
- },
2865
- children: [
2866
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-refresh-icon${refreshing ? " dq-refresh-icon--spin" : ""}`, children: "\u21BB" }),
2867
- refreshing ? t("status.refreshing") : t("status.refresh")
2868
- ]
2869
- }
2870
- )
2779
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-actions", children: [
2780
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2781
+ "button",
2782
+ {
2783
+ type: "button",
2784
+ className: "dq-settings-btn",
2785
+ "aria-expanded": settingsOpen,
2786
+ "aria-controls": "dq-settings-panel",
2787
+ onClick: () => setSettingsOpen((open) => !open),
2788
+ children: [
2789
+ t("settings.title"),
2790
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-settings-caret${settingsOpen ? " dq-settings-caret--open" : ""}`, "aria-hidden": "true", children: "\u25BE" })
2791
+ ]
2792
+ }
2793
+ ),
2794
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2795
+ "button",
2796
+ {
2797
+ type: "button",
2798
+ className: "dq-refresh-btn",
2799
+ title: t("status.forceRefresh"),
2800
+ disabled: refreshing,
2801
+ "aria-busy": refreshing,
2802
+ onClick: () => {
2803
+ void load(true);
2804
+ },
2805
+ children: [
2806
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-refresh-icon${refreshing ? " dq-refresh-icon--spin" : ""}`, children: "\u21BB" }),
2807
+ refreshing ? t("status.refreshing") : t("status.refresh")
2808
+ ]
2809
+ }
2810
+ )
2811
+ ] })
2871
2812
  ] }),
2872
2813
  balanceLow && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-alert", role: "status", children: [
2873
2814
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-alert-icon", "aria-hidden": "true", children: "!" }),
@@ -2949,6 +2890,144 @@ function BalanceDashboard(props) {
2949
2890
  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") }) })
2950
2891
  ] }) : loadingBalance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BalanceSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("balance.empty") })
2951
2892
  ] }),
2893
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Collapsible, { open: settingsOpen, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-settings-panel", id: "dq-settings-panel", role: "region", "aria-label": t("settings.title"), children: [
2894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
2895
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2896
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupDisplay") }),
2897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-toggle", children: [
2898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: widgetOn, onChange: toggle }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.widget") })
2900
+ ] }),
2901
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-toggle-hint", children: t("settings.widgetHint") }),
2902
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting dq-setting--tabs", children: [
2903
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-widget-tabs-label", children: t("settings.widgetTabs") }),
2904
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control dq-widget-tabs", role: "group", "aria-labelledby": "dq-widget-tabs-label", children: [
2905
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-widget-tab-list", children: widgetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-widget-tab", children: [
2906
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2907
+ "input",
2908
+ {
2909
+ type: "checkbox",
2910
+ checked: widgetTabIds === null || widgetTabIds.includes(tab.id),
2911
+ onChange: () => toggleWidgetTab(tab.id)
2912
+ }
2913
+ ),
2914
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: tab.label })
2915
+ ] }, tab.id)) }),
2916
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: widgetTabs.length === 0 ? t("settings.widgetTabsEmpty") : t("settings.widgetTabsHint") })
2917
+ ] })
2918
+ ] })
2919
+ ] }),
2920
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2921
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupAlerts") }),
2922
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2923
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
2924
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2925
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2926
+ "input",
2927
+ {
2928
+ id: "dq-low-balance",
2929
+ className: "dq-number",
2930
+ type: "number",
2931
+ min: 0,
2932
+ step: 1,
2933
+ value: lowBalance,
2934
+ onChange: (e) => changeLowBalance(Math.max(0, Number(e.target.value) || 0))
2935
+ }
2936
+ ),
2937
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: lowBalance > 0 ? t("settings.lowBalanceOn", { amount: fmt(lowBalance), currency: primary?.currency ?? "CNY" }) : t("settings.lowBalanceOff") })
2938
+ ] })
2939
+ ] }),
2940
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2941
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-monthly-budget", children: t("settings.monthlyBudget") }),
2942
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2943
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2944
+ "input",
2945
+ {
2946
+ ref: budgetInputRef,
2947
+ id: "dq-monthly-budget",
2948
+ className: "dq-number",
2949
+ type: "number",
2950
+ min: 0,
2951
+ step: 10,
2952
+ value: monthlyBudget,
2953
+ "aria-describedby": "dq-monthly-budget-hint",
2954
+ onChange: (e) => changeMonthlyBudget(Math.max(0, Number(e.target.value) || 0))
2955
+ }
2956
+ ),
2957
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-monthly-budget-hint", className: "dq-setting-hint", children: monthlyBudget > 0 ? t("settings.monthlyBudgetOn", { amount: fmt(monthlyBudget) }) : t("settings.monthlyBudgetOff") })
2958
+ ] })
2959
+ ] })
2960
+ ] }),
2961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2962
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupLayout") }),
2963
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2964
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-sidebar-entry", children: t("settings.sidebarEntry") }),
2965
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2966
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2967
+ "select",
2968
+ {
2969
+ id: "dq-sidebar-entry",
2970
+ className: "dq-select",
2971
+ value: sidebarEntry,
2972
+ onChange: (e) => changeSidebarEntry(e.target.value),
2973
+ children: [
2974
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: t("settings.sidebarEntryOff") }),
2975
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "top", children: t("settings.sidebarEntryTop") }),
2976
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "bottom", children: t("settings.sidebarEntryBottom") })
2977
+ ]
2978
+ }
2979
+ ),
2980
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: t("settings.sidebarEntryHint") })
2981
+ ] })
2982
+ ] }),
2983
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2984
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-tab-order-label", children: t("settings.tabPosition") }),
2985
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2986
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2987
+ "div",
2988
+ {
2989
+ className: "dq-tab-order",
2990
+ role: "group",
2991
+ "aria-labelledby": "dq-tab-order-label",
2992
+ "aria-describedby": "dq-tab-order-hint",
2993
+ onDragOver: onOrderDragOver,
2994
+ onDrop: onOrderDrop,
2995
+ children: viewTabs.map((tab, index) => {
2996
+ const current = tab.id === "balance";
2997
+ const dropTarget = tabDragging && !current && tabDropTarget === index;
2998
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2999
+ "span",
3000
+ {
3001
+ className: `dq-tab-chip${current ? " dq-tab-chip--current" : ""}${current && tabDragging ? " dq-tab-chip--dragging" : ""}${dropTarget ? " dq-tab-chip--drop-target" : ""}`,
3002
+ draggable: current,
3003
+ tabIndex: current ? 0 : void 0,
3004
+ role: current ? "button" : void 0,
3005
+ "aria-label": current ? t("settings.tabDragAria") : void 0,
3006
+ onDragStart: current ? onQuotaTabDragStart : void 0,
3007
+ onDragEnd: current ? endTabDrag : void 0,
3008
+ onDragOver: (event) => onChipDragOver(event, index),
3009
+ onDrop: (event) => onChipDrop(event, index),
3010
+ onKeyDown: current ? onQuotaTabKeyDown : void 0,
3011
+ children: tab.label
3012
+ },
3013
+ tab.id
3014
+ );
3015
+ })
3016
+ }
3017
+ ),
3018
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-tab-order-hint", className: "dq-setting-hint", children: t("settings.tabPositionHint") })
3019
+ ] })
3020
+ ] })
3021
+ ] }),
3022
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
3023
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("links.title") }),
3024
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-links", children: [
3025
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/usage", children: t("links.usage") }),
3026
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/api_keys", children: t("links.apiKey") }),
3027
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://status.deepseek.com", children: t("links.status") })
3028
+ ] })
3029
+ ] })
3030
+ ] }) }),
2952
3031
  props.sessionId !== void 0 && props.sessionId !== "" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
2953
3032
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: t("common.estimated"), children: t("sessionUsage.title") }),
2954
3033
  sessionUsage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageCard, { data: sessionUsage }) : loadingSessionUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SessionUsageSkeleton, {}) : sessionUsageError !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty dq-empty--error", children: localizeApiError(sessionUsageError, t, "error.query") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessionUsage.empty") })
@@ -2975,7 +3054,10 @@ function BalanceDashboard(props) {
2975
3054
  {
2976
3055
  spent: usage.summary.month.cost,
2977
3056
  budget: monthlyBudget,
2978
- onConfigure: () => budgetInputRef.current?.focus()
3057
+ onConfigure: () => {
3058
+ setSettingsOpen(true);
3059
+ window.setTimeout(() => budgetInputRef.current?.focus(), 80);
3060
+ }
2979
3061
  }
2980
3062
  ),
2981
3063
  usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
@@ -3103,339 +3185,13 @@ function BalanceDashboard(props) {
3103
3185
  ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
3104
3186
  ] })
3105
3187
  ] }),
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
- ] }),
3157
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
3158
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
3159
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-links-title", children: t("links.title") }),
3160
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-links", children: [
3161
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/usage", children: t("links.usage") }),
3162
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/api_keys", children: t("links.apiKey") }),
3163
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://status.deepseek.com", children: t("links.status") })
3164
- ] }),
3165
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-toggle", children: [
3166
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: widgetOn, onChange: toggle }),
3167
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.widget") })
3168
- ] }),
3169
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-toggle-hint", children: t("settings.widgetHint") }),
3170
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting dq-setting--tabs", children: [
3171
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-widget-tabs-label", children: t("settings.widgetTabs") }),
3172
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control dq-widget-tabs", role: "group", "aria-labelledby": "dq-widget-tabs-label", children: [
3173
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-widget-tab-list", children: widgetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-widget-tab", children: [
3174
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3175
- "input",
3176
- {
3177
- type: "checkbox",
3178
- checked: widgetTabIds === null || widgetTabIds.includes(tab.id),
3179
- onChange: () => toggleWidgetTab(tab.id)
3180
- }
3181
- ),
3182
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: tab.label })
3183
- ] }, tab.id)) }),
3184
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: widgetTabs.length === 0 ? t("settings.widgetTabsEmpty") : t("settings.widgetTabsHint") })
3185
- ] })
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") }),
3203
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
3204
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
3205
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
3206
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3207
- "input",
3208
- {
3209
- id: "dq-low-balance",
3210
- className: "dq-number",
3211
- type: "number",
3212
- min: 0,
3213
- step: 1,
3214
- value: lowBalance,
3215
- onChange: (e) => changeLowBalance(Math.max(0, Number(e.target.value) || 0))
3216
- }
3217
- ),
3218
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: lowBalance > 0 ? t("settings.lowBalanceOn", { amount: fmt(lowBalance), currency: primary?.currency ?? "CNY" }) : t("settings.lowBalanceOff") })
3219
- ] })
3220
- ] }),
3221
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
3222
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-monthly-budget", children: t("settings.monthlyBudget") }),
3223
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
3224
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3225
- "input",
3226
- {
3227
- ref: budgetInputRef,
3228
- id: "dq-monthly-budget",
3229
- className: "dq-number",
3230
- type: "number",
3231
- min: 0,
3232
- step: 10,
3233
- value: monthlyBudget,
3234
- "aria-describedby": "dq-monthly-budget-hint",
3235
- onChange: (e) => changeMonthlyBudget(Math.max(0, Number(e.target.value) || 0))
3236
- }
3237
- ),
3238
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-monthly-budget-hint", className: "dq-setting-hint", children: monthlyBudget > 0 ? t("settings.monthlyBudgetOn", { amount: fmt(monthlyBudget) }) : t("settings.monthlyBudgetOff") })
3239
- ] })
3240
- ] }),
3241
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
3242
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-sidebar-entry", children: t("settings.sidebarEntry") }),
3243
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
3244
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3245
- "select",
3246
- {
3247
- id: "dq-sidebar-entry",
3248
- className: "dq-select",
3249
- value: sidebarEntry,
3250
- onChange: (e) => changeSidebarEntry(e.target.value),
3251
- children: [
3252
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: t("settings.sidebarEntryOff") }),
3253
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "top", children: t("settings.sidebarEntryTop") }),
3254
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "bottom", children: t("settings.sidebarEntryBottom") })
3255
- ]
3256
- }
3257
- ),
3258
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: t("settings.sidebarEntryHint") })
3259
- ] })
3260
- ] }),
3261
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
3262
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-tab-order-label", children: t("settings.tabPosition") }),
3263
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
3264
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3265
- "div",
3266
- {
3267
- className: "dq-tab-order",
3268
- role: "group",
3269
- "aria-labelledby": "dq-tab-order-label",
3270
- "aria-describedby": "dq-tab-order-hint",
3271
- onDragOver: onOrderDragOver,
3272
- onDrop: onOrderDrop,
3273
- children: viewTabs.map((tab, index) => {
3274
- const current2 = tab.id === "balance";
3275
- const dropTarget = tabDragging && !current2 && tabDropTarget === index;
3276
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3277
- "span",
3278
- {
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,
3286
- onDragOver: (event) => onChipDragOver(event, index),
3287
- onDrop: (event) => onChipDrop(event, index),
3288
- onKeyDown: current2 ? onQuotaTabKeyDown : void 0,
3289
- children: tab.label
3290
- },
3291
- tab.id
3292
- );
3293
- })
3294
- }
3295
- ),
3296
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-tab-order-hint", className: "dq-setting-hint", children: t("settings.tabPositionHint") })
3297
- ] })
3298
- ] })
3299
- ] }),
3300
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.8.3" })
3188
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.9.0" })
3301
3189
  ] });
3302
3190
  }
3303
3191
 
3304
- // src/client/breaker-pill.tsx
3305
- var import_react6 = require("react");
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
3192
  // src/client/message-cost.tsx
3437
- var import_react7 = require("react");
3438
- var import_jsx_runtime6 = require("react/jsx-runtime");
3193
+ var import_react5 = require("react");
3194
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3439
3195
  var fmtTurnCost = (cost) => cost.toLocaleString(void 0, {
3440
3196
  minimumFractionDigits: cost > 0 && cost < 0.01 ? 4 : 2,
3441
3197
  maximumFractionDigits: cost > 0 && cost < 0.01 ? 6 : 4
@@ -3446,8 +3202,8 @@ var cachedData = (sessionId) => {
3446
3202
  };
3447
3203
  function MessageCost({ sessionId, messageId }) {
3448
3204
  const { t } = useI18n();
3449
- const [data, setData] = (0, import_react7.useState)(() => cachedData(sessionId));
3450
- (0, import_react7.useEffect)(() => {
3205
+ const [data, setData] = (0, import_react5.useState)(() => cachedData(sessionId));
3206
+ (0, import_react5.useEffect)(() => {
3451
3207
  setData(cachedData(sessionId));
3452
3208
  const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData(sessionId)));
3453
3209
  const hit = cachedData(sessionId)?.turns.some((turn2) => turn2.messageId === messageId) ?? false;
@@ -3458,7 +3214,7 @@ function MessageCost({ sessionId, messageId }) {
3458
3214
  if (turn === void 0) return null;
3459
3215
  const amount = `\xA5${fmtTurnCost(turn.cost)}`;
3460
3216
  const tooltip = t("messageCost.tooltip");
3461
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3217
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3462
3218
  "span",
3463
3219
  {
3464
3220
  className: "dq-message-cost",
@@ -3466,15 +3222,15 @@ function MessageCost({ sessionId, messageId }) {
3466
3222
  "data-dq-tooltip": tooltip,
3467
3223
  "aria-label": t("messageCost.aria", { amount }),
3468
3224
  children: [
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 })
3225
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-separator", "aria-hidden": true, children: "\xB7" }),
3226
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-amount", children: amount })
3471
3227
  ]
3472
3228
  }
3473
3229
  );
3474
3230
  }
3475
3231
 
3476
3232
  // src/client/session-cost-pill.tsx
3477
- var import_react8 = require("react");
3233
+ var import_react6 = require("react");
3478
3234
  var import_react_dom = require("react-dom");
3479
3235
 
3480
3236
  // src/pricing.ts
@@ -3482,7 +3238,7 @@ var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
3482
3238
  var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
3483
3239
  var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
3484
3240
  var PEAK_WINDOWS = [[9, 12], [14, 18]];
3485
- var BEIJING_OFFSET_MS4 = 8 * 36e5;
3241
+ var BEIJING_OFFSET_MS3 = 8 * 36e5;
3486
3242
  var FLAT_RATES = {
3487
3243
  pro: { cacheHit: 0.025, input: 3, output: 6 },
3488
3244
  flash: { cacheHit: 0.02, input: 1, output: 2 },
@@ -3527,12 +3283,12 @@ function billedTierOf(timeMs, model) {
3527
3283
  return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
3528
3284
  }
3529
3285
  function isBeijingWeekday(timeMs) {
3530
- const day = new Date(timeMs + BEIJING_OFFSET_MS4).getUTCDay();
3286
+ const day = new Date(timeMs + BEIJING_OFFSET_MS3).getUTCDay();
3531
3287
  return day >= 1 && day <= 5;
3532
3288
  }
3533
3289
  function isPeak(timeMs) {
3534
3290
  if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
3535
- const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS4) % 864e5 + 864e5) % 864e5 / 36e5);
3291
+ const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS3) % 864e5 + 864e5) % 864e5 / 36e5);
3536
3292
  return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
3537
3293
  }
3538
3294
  function ratesAt(timeMs, model) {
@@ -3542,7 +3298,7 @@ function ratesAt(timeMs, model) {
3542
3298
  }
3543
3299
 
3544
3300
  // src/client/session-cost-pill.tsx
3545
- var import_jsx_runtime7 = require("react/jsx-runtime");
3301
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3546
3302
  var PANEL_MARGIN = 12;
3547
3303
  var PANEL_GAP = 8;
3548
3304
  var cachedData2 = (sessionId) => {
@@ -3550,7 +3306,7 @@ var cachedData2 = (sessionId) => {
3550
3306
  return response?.ok === true ? response.data ?? null : null;
3551
3307
  };
3552
3308
  function CostIcon() {
3553
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3309
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3554
3310
  "svg",
3555
3311
  {
3556
3312
  width: "14",
@@ -3564,32 +3320,32 @@ function CostIcon() {
3564
3320
  "aria-hidden": "true",
3565
3321
  focusable: "false",
3566
3322
  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" })
3323
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "8", cy: "8", r: "6" }),
3324
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.5 5.3 8 8.4l2.5-3.1" }),
3325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 8.4v2.9" }),
3326
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 9.5h4.4" }),
3327
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 10.9h4.4" })
3572
3328
  ]
3573
3329
  }
3574
3330
  );
3575
3331
  }
3576
3332
  function SessionCostPill({ sessionId }) {
3577
3333
  const { t, locale } = useI18n();
3578
- const [data, setData] = (0, import_react8.useState)(
3334
+ const [data, setData] = (0, import_react6.useState)(
3579
3335
  () => sessionId === void 0 || sessionId === "" ? null : cachedData2(sessionId)
3580
3336
  );
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)(() => {
3337
+ const [open, setOpen] = (0, import_react6.useState)(false);
3338
+ const anchorRef = (0, import_react6.useRef)(null);
3339
+ const panelRef = (0, import_react6.useRef)(null);
3340
+ const [pos, setPos] = (0, import_react6.useState)(null);
3341
+ (0, import_react6.useEffect)(() => {
3586
3342
  if (sessionId === void 0 || sessionId === "") return;
3587
3343
  setData(cachedData2(sessionId));
3588
3344
  const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData2(sessionId)));
3589
3345
  if (getCachedSessionUsage(sessionId) === null) void fetchSessionUsage(sessionId);
3590
3346
  return unsubscribe;
3591
3347
  }, [sessionId]);
3592
- (0, import_react8.useLayoutEffect)(() => {
3348
+ (0, import_react6.useLayoutEffect)(() => {
3593
3349
  if (!open) {
3594
3350
  setPos(null);
3595
3351
  return;
@@ -3604,7 +3360,7 @@ function SessionCostPill({ sessionId }) {
3604
3360
  bottom: window.innerHeight - pill.top + PANEL_GAP
3605
3361
  });
3606
3362
  }, [open, data]);
3607
- (0, import_react8.useEffect)(() => {
3363
+ (0, import_react6.useEffect)(() => {
3608
3364
  if (!open) return;
3609
3365
  const close = () => setOpen(false);
3610
3366
  const onPointerDown = (event) => {
@@ -3656,8 +3412,8 @@ function SessionCostPill({ sessionId }) {
3656
3412
  hour12: false
3657
3413
  });
3658
3414
  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)(
3415
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3416
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { ref: anchorRef, className: "dq-cost-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3661
3417
  "button",
3662
3418
  {
3663
3419
  type: "button",
@@ -3667,13 +3423,13 @@ function SessionCostPill({ sessionId }) {
3667
3423
  "aria-label": t("sessionCost.aria", { amount }),
3668
3424
  onClick: () => setOpen((value) => !value),
3669
3425
  children: [
3670
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CostIcon, {}),
3671
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-pill-label", children: amount })
3426
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3427
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-pill-label", children: amount })
3672
3428
  ]
3673
3429
  }
3674
3430
  ) }),
3675
3431
  open && (0, import_react_dom.createPortal)(
3676
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3677
3433
  "div",
3678
3434
  {
3679
3435
  ref: panelRef,
@@ -3682,35 +3438,35 @@ function SessionCostPill({ sessionId }) {
3682
3438
  "aria-label": t("sessionCost.title"),
3683
3439
  style: pos === null ? { visibility: "hidden", left: 0, top: 0 } : { visibility: "visible", left: pos.left, bottom: pos.bottom, top: "auto" },
3684
3440
  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, {}),
3441
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dq-cost-panel-title", children: [
3442
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dq-cost-panel-title-label", children: [
3443
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3688
3444
  t("sessionCost.title")
3689
3445
  ] }),
3690
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-panel-value", children: amount })
3446
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-panel-value", children: amount })
3691
3447
  ] }),
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: [
3448
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dq-cost-panel-rule", "aria-hidden": true }),
3449
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "dq-cost-details", children: [
3450
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.input") }),
3451
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3696
3452
  "\xA5",
3697
3453
  fmtTurnCost(split.input)
3698
3454
  ] }),
3699
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.cacheHit") }),
3700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
3455
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.cacheHit") }),
3456
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3701
3457
  "\xA5",
3702
3458
  fmtTurnCost(split.cache)
3703
3459
  ] }),
3704
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.output") }),
3705
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
3460
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.output") }),
3461
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3706
3462
  "\xA5",
3707
3463
  fmtTurnCost(split.output)
3708
3464
  ] }),
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 })
3465
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionUsage.calls") }),
3466
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: fmtInt(data.calls) }),
3467
+ range !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3468
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionCost.active") }),
3469
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: range })
3714
3470
  ] })
3715
3471
  ] })
3716
3472
  ]
@@ -3773,18 +3529,6 @@ var css = `
3773
3529
  .dq-cost-pill:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
3774
3530
  .dq-cost-pill svg{flex:none}
3775
3531
  .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
3532
  /* Click-open breakdown: the host stat-dialog skin (menu surface, prominent
3789
3533
  elevation, 12/18 rows), portaled to the body and placed above the pill. */
3790
3534
  .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}
@@ -3955,7 +3699,11 @@ var css = `
3955
3699
  .dq-pricing-table{border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums;min-width:100%}
3956
3700
  .dq-pricing-table th{text-align:left;font-weight:500;color:var(--dsw-alias-label-secondary,#59636e);padding:4px 12px 4px 0;white-space:nowrap;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
3957
3701
  .dq-pricing-table td{padding:4px 12px 4px 0;color:var(--dsw-alias-label-primary,#1f2328);white-space:nowrap;vertical-align:top}
3958
- .dq-pricing-table th:nth-child(n+3),.dq-pricing-table td:nth-child(n+3){text-align:right}
3702
+ /* Align by counting from the end: a peak row starts with the rowSpan'd model
3703
+ cell (5 cells) while its off-peak pair starts at \u65F6\u6BB5 (4 cells), so
3704
+ nth-child(n+3) left the off-peak cache-hit price stranded under \u65F6\u6BB5. The
3705
+ last three cells are the three price columns in both row shapes. */
3706
+ .dq-pricing-table th:nth-last-child(-n+3),.dq-pricing-table td:nth-last-child(-n+3){text-align:right}
3959
3707
  .dq-pricing-table th:last-child,.dq-pricing-table td:last-child{padding-right:0}
3960
3708
  .dq-pricing-table tbody td:first-child{font-weight:500}
3961
3709
  .dq-pricing-table tbody tr+tr td{border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.05))}
@@ -3987,8 +3735,23 @@ var css = `
3987
3735
  .dq-legend-swatch--peak{background:var(--dsw-alias-state-warning-primary,#bf8700)}
3988
3736
  .dq-legend-swatch--offpeak{background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.85}
3989
3737
  .dq-peak-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-top:16px}
3990
- .dq-peak-delta{font-size:12px;font-weight:500;margin-left:8px;color:var(--dq-text-warn)}
3991
- .dq-peak-delta--save{color:var(--dq-text-success)}
3738
+ .dq-peak-foot{margin:12px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e);text-wrap:pretty}
3739
+ .dq-clock{margin-bottom:16px}
3740
+ .dq-clock-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}
3741
+ .dq-clock-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e)}
3742
+ .dq-clock-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px}
3743
+ .dq-clock-status::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor}
3744
+ .dq-clock-status--peak{color:var(--dq-text-warn);background:rgba(191,135,0,.12)}
3745
+ .dq-clock-status--off{color:var(--dq-text-success);background:rgba(45,164,78,.12)}
3746
+ .dq-clock-track{position:relative;display:flex;gap:1px;height:10px}
3747
+ .dq-clock-cell{flex:1 1 0;height:100%;background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.55}
3748
+ .dq-clock-cell--peak{background:var(--dsw-alias-state-warning-primary,#bf8700);opacity:.92}
3749
+ .dq-clock-cell:first-child{border-radius:999px 0 0 999px}
3750
+ .dq-clock-cell:last-child{border-radius:0 999px 999px 0}
3751
+ .dq-clock-now{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--dsw-alias-label-primary,#1f2328);border-radius:2px;transform:translateX(-1px)}
3752
+ .dq-clock-now::before{content:'';position:absolute;top:-4px;left:50%;transform:translateX(-50%);width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-primary,#1f2328)}
3753
+ .dq-clock-scale{display:flex;justify-content:space-between;margin-top:4px;font-size:10px;color:var(--dsw-alias-label-tertiary,#59636e);font-variant-numeric:tabular-nums}
3754
+ .dq-clock-hint{margin:6px 0 0;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary,#59636e)}
3992
3755
  .dq-cache-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-bottom:12px}
3993
3756
  @media (max-width:620px){.dq-cache-figures{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 16px}.dq-cache-figures .dq-stat{min-width:0}}
3994
3757
  .dq-muted-value{color:var(--dsw-alias-label-secondary,#59636e)!important;font-weight:500}
@@ -4054,28 +3817,29 @@ var css = `
4054
3817
  .dq-setting-label{width:128px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328);padding-top:6px;flex:none}
4055
3818
  .dq-setting-control{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
4056
3819
  .dq-setting-hint{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);line-height:1.5}
3820
+ .dq-setting-group{margin-top:28px}
3821
+ .dq-setting-group:first-of-type{margin-top:0}
3822
+ .dq-setting-group-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:0 0 10px;letter-spacing:.02em}
3823
+ .dq-setting-group .dq-toggle{margin-top:0}
3824
+ .dq-setting-group .dq-setting{margin-top:16px}
4057
3825
  /* Page footer: the package name + version, injected at build time from
4058
3826
  package.json (see build.mjs) so it can never drift from the release. */
4059
3827
  .dq-footer{margin:16px 0 0;font-size:12px;text-align:center;color:var(--dsw-alias-label-tertiary,#59636e)}
3828
+ .dq-sectionnav{position:fixed;top:50%;transform:translateY(-50%);z-index:20;width:max-content;pointer-events:none}
3829
+ .dq-sectionnav--hidden{display:none}
3830
+ .dq-sectionnav ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;pointer-events:auto}
3831
+ .dq-sectionnav li{display:flex;justify-content:flex-end}
3832
+ .dq-sectionnav-dot{display:inline-flex;align-items:center;justify-content:flex-end;gap:8px;padding:4px 2px;border:0;background:transparent;border-radius:6px;cursor:pointer;color:var(--dsw-alias-label-secondary,#59636e);font-size:12px;line-height:1.2;white-space:nowrap;transition:color .15s ease,background .15s ease}
3833
+ .dq-sectionnav-dot:hover,.dq-sectionnav-dot:focus-visible{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.10))}
3834
+ .dq-sectionnav-dot:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3835
+ .dq-sectionnav-label{font-size:12px;color:inherit}
3836
+ .dq-sectionnav-pip{flex:none;width:9px;height:9px;border-radius:50%;background:var(--dsw-alias-border-l2,rgba(0,0,0,.22));transition:background .15s ease,transform .15s ease}
3837
+ .dq-sectionnav-dot:hover .dq-sectionnav-pip{background:var(--dsw-alias-label-secondary,#59636e)}
3838
+ .dq-sectionnav-dot--active{color:var(--dsw-alias-label-primary,#1f2328);font-weight:600}
3839
+ .dq-sectionnav-dot--active .dq-sectionnav-pip{background:var(--dsw-alias-state-business-primary,#0969da);transform:scale(1.25)}
3840
+ @media (max-width:1199px){.dq-sectionnav{display:none}}
4060
3841
  .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}
4077
3842
  .dq-number:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;border-color:transparent}
4078
- .dq-links-title{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328);margin:0 0 12px}
4079
3843
  .dq-links{display:flex;flex-wrap:wrap;gap:8px}
4080
3844
  .dq-link{display:inline-flex;align-items:center;gap:4px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);text-decoration:none;font-size:13px;font-weight:500;cursor:pointer}
4081
3845
  .dq-link::after{content:"\u2197";font-size:12px;color:var(--dsw-alias-label-secondary,#59636e)}
@@ -4142,7 +3906,10 @@ var css = `
4142
3906
  .dq-chart-switch-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
4143
3907
  .dq-chart-switch-btn--on{background:var(--dsw-alias-bg-overlay,#fff);color:var(--dsw-alias-label-primary,#1f2328);box-shadow:0 1px 3px rgba(0,0,0,.14)}
4144
3908
  .dq-chart-wrap{position:relative}
4145
- .dq-bars-scroll{overflow-x:auto;overflow-y:hidden;padding-bottom:2px}
3909
+ .dq-plot{display:flex;align-items:flex-end;gap:8px}
3910
+ .dq-yaxis{flex:none;width:46px;display:flex;flex-direction:column;justify-content:space-between;margin-bottom:18px;font-size:11px;line-height:1;color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums;text-align:right;pointer-events:none}
3911
+ .dq-yaxis span{white-space:nowrap}
3912
+ .dq-bars-scroll{flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}
4146
3913
  .dq-tip{position:absolute;transform:translate(-50%,14px);z-index:40;pointer-events:none;background:var(--dsw-alias-tooltip-bg,#1f2328);color:var(--dsw-static-neutral-bluish-00,#fff);border-radius:8px;padding:7px 10px;font-size:12px;line-height:1.5;white-space:nowrap;box-shadow:0 6px 20px rgba(0,0,0,.28);font-variant-numeric:tabular-nums}
4147
3914
  .dq-tip--above{transform:translate(-50%,calc(-100% - 8px))}
4148
3915
  .dq-tip-head{font-weight:600;margin-bottom:2px}
@@ -4163,13 +3930,13 @@ var css = `
4163
3930
  .dq-heat-cell--pinned{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;position:relative;z-index:2}
4164
3931
  .dq-bar-label{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);text-align:center;margin-top:4px;overflow:visible;white-space:nowrap}
4165
3932
  .dq-heat{display:flex;flex-direction:column;gap:8px;max-width:100%}
4166
- .dq-heat-scroll{overflow-x:auto;padding-bottom:2px}
4167
- .dq-heatmap{display:flex;gap:3px}
4168
- .dq-heat-week{display:flex;flex:1 1 0;min-width:6px;flex-direction:column;gap:3px}
4169
- .dq-heat-cell{width:100%;aspect-ratio:1/1;border-radius:3px;flex:none}
3933
+ .dq-heat-scroll{overflow:hidden;padding-bottom:2px}
3934
+ .dq-heatmap{display:flex;gap:2px}
3935
+ .dq-heat-week{display:flex;flex:1 1 0;min-width:0;flex-direction:column;gap:2px}
3936
+ .dq-heat-cell{width:100%;aspect-ratio:1/1;border-radius:2px;flex:none;min-width:0}
4170
3937
  .dq-heat-cell--pad{background:transparent}
4171
- .dq-heat-months{display:flex;gap:3px;margin-top:4px}
4172
- .dq-heat-month{flex:1 1 0;min-width:6px;font-size:12px;line-height:15px;color:var(--dsw-alias-label-secondary,#59636e);white-space:nowrap;overflow:visible}
3938
+ .dq-heat-months{display:flex;gap:2px;margin-top:4px}
3939
+ .dq-heat-month{flex:1 1 0;min-width:0;font-size:12px;line-height:15px;color:var(--dsw-alias-label-secondary,#59636e);white-space:nowrap;overflow:visible}
4173
3940
  .dq-heat-key{width:12px;height:12px;border-radius:2px;flex:none}
4174
3941
  .dq-heat-scale{display:flex;align-items:center;gap:3px;font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);align-self:flex-end}
4175
3942
  .dq-heat-scale>span:first-child{margin-right:2px}
@@ -4203,6 +3970,20 @@ var css = `
4203
3970
  .dq-sync--error .dq-sync-dot{background:var(--dsw-alias-state-error-primary,#cf222e)}
4204
3971
  @keyframes dq-sync-pulse{50%{opacity:.35;transform:scale(.8)}}
4205
3972
  .dq-refresh-btn{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#59636e);border-radius:8px;padding:5px 10px;font-size:12px;cursor:pointer;transition:border-color .15s ease,color .15s ease}
3973
+ .dq-status-actions{display:flex;align-items:center;gap:8px;flex:none}
3974
+ .dq-settings-btn{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#59636e);border-radius:8px;padding:5px 10px;font-size:12px;cursor:pointer;transition:border-color .15s ease,color .15s ease}
3975
+ .dq-settings-btn:hover{border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
3976
+ .dq-settings-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
3977
+ .dq-settings-caret{font-size:10px;line-height:1;transition:transform .2s cubic-bezier(.16,1,.3,1)}
3978
+ .dq-settings-caret--open{transform:rotate(180deg)}
3979
+ .dq-settings-panel{box-sizing:border-box;min-width:0;max-width:100%;padding:20px;background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.10));border-radius:12px}
3980
+ /* Motion: eased (easeOutExpo) arrivals. Operate surface \u2014 fast, purposeful,
3981
+ and fully disabled under prefers-reduced-motion (see the global reduce block). */
3982
+ .dq-export-menu,.dq-model-menu{transform-origin:top right;animation:dq-pop .18s cubic-bezier(.16,1,.3,1)}
3983
+ @keyframes dq-pop{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:none}}
3984
+ .dq-alert{animation:dq-alert-in .28s cubic-bezier(.16,1,.3,1)}
3985
+ @keyframes dq-alert-in{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
3986
+ @media (max-width:620px){.dq-settings-panel{padding:16px}}
4206
3987
  .dq-refresh-btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
4207
3988
  .dq-refresh-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
4208
3989
  .dq-refresh-btn:disabled{cursor:default;opacity:.6}
@@ -4290,7 +4071,7 @@ html[data-dq-quota-view] .dq-balance{padding-bottom:48px}
4290
4071
  `;
4291
4072
 
4292
4073
  // src/client/widget.tsx
4293
- var import_react9 = require("react");
4074
+ var import_react7 = require("react");
4294
4075
 
4295
4076
  // src/client/sessions.ts
4296
4077
  function currentSessionIdOf(state) {
@@ -4299,7 +4080,7 @@ function currentSessionIdOf(state) {
4299
4080
  }
4300
4081
 
4301
4082
  // src/client/widget.tsx
4302
- var import_jsx_runtime8 = require("react/jsx-runtime");
4083
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4303
4084
  var MARGIN = 16;
4304
4085
  var CORNER_KEY = "widget.corner";
4305
4086
  var COLLAPSED_KEY = "widget.collapsed";
@@ -4350,47 +4131,47 @@ function cornerPos(corner, node, bounds) {
4350
4131
  function QuotaWidget(props) {
4351
4132
  const { t } = useI18n();
4352
4133
  const sessionId = props.useSessions(currentSessionIdOf);
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);
4134
+ const [visible, setVisible] = (0, import_react7.useState)(widgetVisibleStore.get());
4135
+ (0, import_react7.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
4136
+ const [quotaViewActive2, setQuotaViewActive] = (0, import_react7.useState)(quotaViewActiveStore.get());
4137
+ (0, import_react7.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
4138
+ const [lowBalance, setLowBalance] = (0, import_react7.useState)(lowBalanceStore.get());
4139
+ (0, import_react7.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
4140
+ const [selectedTabIds, setSelectedTabIds] = (0, import_react7.useState)(widgetTabIdsStore.get());
4141
+ (0, import_react7.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
4142
+ const [viewTabs, setViewTabs] = (0, import_react7.useState)(() => props.views.list());
4143
+ (0, import_react7.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
4144
+ const [activeViewId, setActiveViewId] = (0, import_react7.useState)(null);
4364
4145
  const cachedBalance = getCachedBalance();
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);
4146
+ const [data, setData] = (0, import_react7.useState)(cachedBalance?.data ?? null);
4147
+ const [error, setError] = (0, import_react7.useState)(null);
4148
+ const [loading, setLoading] = (0, import_react7.useState)(cachedBalance === null);
4149
+ const [collapsed, setCollapsed] = (0, import_react7.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
4150
+ const [pos, setPos] = (0, import_react7.useState)(null);
4151
+ const [corner, setCorner] = (0, import_react7.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
4152
+ const [dragging, setDragging] = (0, import_react7.useState)(false);
4153
+ const rootRef = (0, import_react7.useRef)(null);
4154
+ const dragRef = (0, import_react7.useRef)(null);
4374
4155
  const readUsage = () => {
4375
4156
  const cached = getCachedUsage();
4376
4157
  return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
4377
4158
  };
4378
- const [usage, setUsage] = (0, import_react9.useState)(readUsage);
4379
- (0, import_react9.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
4159
+ const [usage, setUsage] = (0, import_react7.useState)(readUsage);
4160
+ (0, import_react7.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
4380
4161
  const readSessionUsage = (id) => {
4381
4162
  if (id === void 0) return null;
4382
4163
  const response = getCachedSessionUsage(id);
4383
4164
  return response?.ok === true ? response.data ?? null : null;
4384
4165
  };
4385
- const [sessionUsage, setSessionUsage] = (0, import_react9.useState)(() => readSessionUsage(sessionId));
4386
- (0, import_react9.useEffect)(() => {
4166
+ const [sessionUsage, setSessionUsage] = (0, import_react7.useState)(() => readSessionUsage(sessionId));
4167
+ (0, import_react7.useEffect)(() => {
4387
4168
  setSessionUsage(readSessionUsage(sessionId));
4388
4169
  if (sessionId === void 0) return;
4389
4170
  const unsubscribe = subscribeSessionUsage(sessionId, () => setSessionUsage(readSessionUsage(sessionId)));
4390
4171
  void fetchSessionUsage(sessionId);
4391
4172
  return unsubscribe;
4392
4173
  }, [sessionId]);
4393
- (0, import_react9.useLayoutEffect)(() => {
4174
+ (0, import_react7.useLayoutEffect)(() => {
4394
4175
  const viewIds = viewTabs.map((tab) => tab.id);
4395
4176
  const read = () => {
4396
4177
  const next = getActiveConversationViewId(getShellFrame(rootRef.current), viewIds);
@@ -4427,14 +4208,14 @@ function QuotaWidget(props) {
4427
4208
  setLoading(false);
4428
4209
  }
4429
4210
  };
4430
- (0, import_react9.useEffect)(() => {
4211
+ (0, import_react7.useEffect)(() => {
4431
4212
  void load(true);
4432
4213
  const id = setInterval(() => {
4433
4214
  void load(false);
4434
4215
  }, 6e4);
4435
4216
  return () => clearInterval(id);
4436
4217
  }, []);
4437
- (0, import_react9.useLayoutEffect)(() => {
4218
+ (0, import_react7.useLayoutEffect)(() => {
4438
4219
  if (!visible || quotaViewActive2) return;
4439
4220
  const node = rootRef.current;
4440
4221
  if (node === null) return;
@@ -4562,9 +4343,9 @@ function QuotaWidget(props) {
4562
4343
  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";
4563
4344
  const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
4564
4345
  const usageAgeText = ageMinutes === null || ageMinutes < 1 ? t("widget.updatedNow") : t("widget.stale", { minutes: ageMinutes });
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: [
4346
+ const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
4347
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
4348
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
4568
4349
  "\xA5",
4569
4350
  fmtTurnCost(sessionUsage.cost)
4570
4351
  ] })
@@ -4572,50 +4353,50 @@ function QuotaWidget(props) {
4572
4353
  let body = null;
4573
4354
  if (!collapsed) {
4574
4355
  if (loading && data === null && error === null) {
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") }),
4356
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4357
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: t("widget.loading") }),
4577
4358
  sessionCostRow
4578
4359
  ] });
4579
4360
  } else if (error !== null && data === null) {
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") }),
4361
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4362
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
4582
4363
  sessionCostRow
4583
4364
  ] });
4584
4365
  } else if (primary !== null) {
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 })
4366
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4367
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
4368
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
4369
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
4370
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
4590
4371
  ] }),
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: [
4372
+ usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
4373
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
4374
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
4594
4375
  "\xA5",
4595
4376
  fmt(usage.data.summary.today.cost)
4596
4377
  ] })
4597
4378
  ] }),
4598
4379
  sessionCostRow,
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") })
4380
+ data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", children: [
4381
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
4382
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
4602
4383
  ] })
4603
4384
  ] });
4604
4385
  } else {
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") }),
4386
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4387
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
4607
4388
  sessionCostRow
4608
4389
  ] });
4609
4390
  }
4610
4391
  }
4611
4392
  const style = pos !== null ? { left: `${pos.x}px`, top: `${pos.y}px`, right: "auto", bottom: "auto" } : void 0;
4612
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4393
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4613
4394
  "div",
4614
4395
  {
4615
4396
  className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${hidden ? " dsh-quota-root--hidden" : ""}`,
4616
4397
  style,
4617
4398
  "aria-hidden": hidden,
4618
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4399
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4619
4400
  "div",
4620
4401
  {
4621
4402
  ref: rootRef,
@@ -4627,14 +4408,14 @@ function QuotaWidget(props) {
4627
4408
  onPointerCancel: () => endDrag(false),
4628
4409
  onLostPointerCapture: () => endDrag(false),
4629
4410
  children: [
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") })
4411
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-header", children: [
4412
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-title", children: [
4413
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
4414
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: dotClass }),
4415
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
4635
4416
  ] }),
4636
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-actions", children: [
4637
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4417
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-actions", children: [
4418
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4638
4419
  "button",
4639
4420
  {
4640
4421
  className: "dsh-quota-btn",
@@ -4650,7 +4431,7 @@ function QuotaWidget(props) {
4650
4431
  children: "\u21BB"
4651
4432
  }
4652
4433
  ),
4653
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4434
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4654
4435
  "button",
4655
4436
  {
4656
4437
  className: "dsh-quota-btn",
@@ -4668,7 +4449,7 @@ function QuotaWidget(props) {
4668
4449
  )
4669
4450
  ] })
4670
4451
  ] }),
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: [
4452
+ collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
4672
4453
  fmt(primary.total),
4673
4454
  " ",
4674
4455
  primary.currency
@@ -4699,7 +4480,7 @@ function conversationViewsSource(slots) {
4699
4480
  }
4700
4481
 
4701
4482
  // src/client/index.tsx
4702
- var import_jsx_runtime9 = require("react/jsx-runtime");
4483
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4703
4484
  var name = "dsh-usage-dashboard";
4704
4485
  var inject = ["slots", "locale", "layout"];
4705
4486
  var openQuotaTab = null;
@@ -4720,15 +4501,11 @@ function apply(ctx) {
4720
4501
  });
4721
4502
  ctx.slots.inject("shell.overlay", () => ctx.slots.register(
4722
4503
  { name: "shell.overlay", id: "deepseek-quota", order: 1e3, locale: NS, label: () => t("widget.title") },
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 }) }) })
4504
+ ({ t: slotT, useSessions }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuotaWidget, { useSessions, views }) }) })
4724
4505
  ));
4725
4506
  ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register(
4726
4507
  { 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 }) }) })
4508
+ ({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SessionCostPill, { sessionId }) }) })
4732
4509
  ));
4733
4510
  ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register(
4734
4511
  {
@@ -4738,13 +4515,13 @@ function apply(ctx) {
4738
4515
  locale: NS,
4739
4516
  label: () => t("messageCost.label")
4740
4517
  },
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 }) }) })
4518
+ ({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageCost, { sessionId, messageId }) }) })
4742
4519
  ));
4743
4520
  const QuotaView = ({ t: slotT, sessionId, openView }) => {
4744
- (0, import_react10.useEffect)(() => {
4521
+ (0, import_react8.useEffect)(() => {
4745
4522
  if (openView !== void 0) openQuotaTab = openView;
4746
4523
  }, [openView]);
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 }) }) });
4524
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BalanceDashboard, { sessionId, views }) }) });
4748
4525
  };
4749
4526
  ctx.effect(() => {
4750
4527
  let disposeView = null;
@@ -4780,14 +4557,14 @@ function apply(ctx) {
4780
4557
  disposeView?.();
4781
4558
  };
4782
4559
  }, "dsh-usage-dashboard: quota tab registration");
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") })
4560
+ const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel", children: [
4561
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel-head", "data-window-drag": true, children: [
4562
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { className: "dq-panel-title", children: slotT("nav.quota") }),
4563
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-panel-caption", children: slotT("panel.caption") })
4787
4564
  ] }),
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 }) }) })
4565
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BalanceDashboard, { views }) }) })
4789
4566
  ] });
4790
- const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4567
+ const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4791
4568
  "svg",
4792
4569
  {
4793
4570
  className: "dq-side-icon",
@@ -4803,23 +4580,23 @@ function apply(ctx) {
4803
4580
  "aria-hidden": "true",
4804
4581
  focusable: "false",
4805
4582
  children: [
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" })
4583
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 3.3 8 8.7l4.5-5.4" }),
4584
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 8.7V13.2" }),
4585
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 10.2h9" }),
4586
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 12.1h9" })
4810
4587
  ]
4811
4588
  }
4812
4589
  );
4813
4590
  const QuotaFooterButton = ({ wide, t: slotT }) => {
4814
- const [active, setActive] = (0, import_react10.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4815
- (0, import_react10.useEffect)(() => {
4591
+ const [active, setActive] = (0, import_react8.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4592
+ (0, import_react8.useEffect)(() => {
4816
4593
  const sync = () => {
4817
4594
  setActive(ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4818
4595
  };
4819
4596
  sync();
4820
4597
  return ctx.layout.panelInfo.subscribe(sync);
4821
4598
  }, []);
4822
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4599
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4823
4600
  "button",
4824
4601
  {
4825
4602
  type: "button",
@@ -4831,8 +4608,8 @@ function apply(ctx) {
4831
4608
  ctx.layout.selectPanel(active ? null : QUOTA_PANEL_ID);
4832
4609
  },
4833
4610
  children: [
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") })
4611
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuotaGlyph, { size: 16 }) }),
4612
+ wide && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-label", children: slotT("nav.quota") })
4836
4613
  ]
4837
4614
  }
4838
4615
  );