@cassius0924/dsh-usage-dashboard 0.8.3 → 0.9.1

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
  }
@@ -1359,10 +1248,8 @@ function getShellFrame(node) {
1359
1248
  return overlay?.parentElement ?? null;
1360
1249
  }
1361
1250
  function getComposerElement(frame) {
1362
- if (frame === null) {
1363
- return slotElement(document.documentElement, "conversation.composer.dock", "ancestor");
1364
- }
1365
- return slotElement(frame, "conversation.composer.dock", "ancestor");
1251
+ const root = frame ?? document.documentElement;
1252
+ return slotElement(root, "conversation.composer", "ancestor") ?? slotElement(root, "conversation.composer.dock", "ancestor");
1366
1253
  }
1367
1254
  function getComposerInputElement(frame) {
1368
1255
  const root = frame ?? document.documentElement;
@@ -1389,9 +1276,9 @@ function getActiveConversationViewId(frame, viewIds) {
1389
1276
  }
1390
1277
 
1391
1278
  // src/client/export.ts
1392
- var BEIJING_OFFSET_MS3 = 8 * 60 * 60 * 1e3;
1279
+ var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
1393
1280
  function exportDateStamp(nowMs = Date.now()) {
1394
- const date = new Date(nowMs + BEIJING_OFFSET_MS3);
1281
+ const date = new Date(nowMs + BEIJING_OFFSET_MS2);
1395
1282
  return [
1396
1283
  date.getUTCFullYear(),
1397
1284
  String(date.getUTCMonth() + 1).padStart(2, "0"),
@@ -1507,19 +1394,19 @@ var isBoolean = (value) => typeof value === "boolean";
1507
1394
 
1508
1395
  // src/client/store.ts
1509
1396
  function createStore(key, isValid, fallback) {
1510
- let current2 = loadPref(key, isValid, fallback);
1511
- const listeners2 = /* @__PURE__ */ new Set();
1397
+ let current = loadPref(key, isValid, fallback);
1398
+ const listeners = /* @__PURE__ */ new Set();
1512
1399
  return {
1513
- get: () => current2,
1400
+ get: () => current,
1514
1401
  set: (value) => {
1515
- current2 = value;
1402
+ current = value;
1516
1403
  savePref(key, value);
1517
- for (const listener of [...listeners2]) listener();
1404
+ for (const listener of [...listeners]) listener();
1518
1405
  },
1519
1406
  subscribe: (fn) => {
1520
- listeners2.add(fn);
1407
+ listeners.add(fn);
1521
1408
  return () => {
1522
- listeners2.delete(fn);
1409
+ listeners.delete(fn);
1523
1410
  };
1524
1411
  }
1525
1412
  };
@@ -1582,16 +1469,6 @@ function needsTopUp(balance, warningLine) {
1582
1469
  // src/client/dashboard.tsx
1583
1470
  var import_jsx_runtime4 = require("react/jsx-runtime");
1584
1471
  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
1472
  function Skel(props) {
1596
1473
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-skel", style: { width: `${props.w}px`, height: `${props.h}px` } });
1597
1474
  }
@@ -1641,18 +1518,19 @@ function HeatmapSkeleton() {
1641
1518
  }
1642
1519
  function Delta(props) {
1643
1520
  const { t } = useI18n();
1644
- const { current: current2, previous, label } = props;
1521
+ const { current, previous, label } = props;
1645
1522
  if (previous <= 0) {
1646
- if (current2 <= 0) return null;
1523
+ if (current <= 0) return null;
1647
1524
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--new", title: t("delta.noBaselineTitle", { label }), children: t("delta.noBaseline") });
1648
1525
  }
1649
- const percent = Math.round((current2 - previous) / previous * 100);
1526
+ const percent = Math.round((current - previous) / previous * 100);
1650
1527
  const title = `${label} \xA5${fmt(previous)}`;
1651
1528
  if (percent === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta dq-delta--flat", title, children: t("delta.flat") });
1652
1529
  const up = percent > 0;
1530
+ const magnitude = Math.abs(percent) >= 1e3 ? "999+" : String(Math.abs(percent));
1653
1531
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: `dq-delta ${up ? "dq-delta--up" : "dq-delta--down"}`, title, children: [
1654
1532
  up ? "\u2191" : "\u2193",
1655
- Math.abs(percent),
1533
+ magnitude,
1656
1534
  "% ",
1657
1535
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-delta-label", children: label })
1658
1536
  ] });
@@ -1734,7 +1612,7 @@ function PricingNote(props) {
1734
1612
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheMiss") }),
1735
1613
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.output") })
1736
1614
  ] }) }),
1737
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react5.Fragment, { children: [
1615
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react4.Fragment, { children: [
1738
1616
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1739
1617
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan: 2, children: tier.model }),
1740
1618
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.peak") }),
@@ -1810,6 +1688,137 @@ function CardTitle(props) {
1810
1688
  props.caption !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-card-caption", children: props.caption })
1811
1689
  ] });
1812
1690
  }
1691
+ function SectionNav(props) {
1692
+ const { rootRef, label, sections } = props;
1693
+ const [targets, setTargets] = (0, import_react4.useState)([]);
1694
+ const [active, setActive] = (0, import_react4.useState)(0);
1695
+ const [pos, setPos] = (0, import_react4.useState)({ right: 0, visible: false });
1696
+ const signature = sections.map((section) => `${section.key}:${section.label}`).join("|");
1697
+ (0, import_react4.useEffect)(() => {
1698
+ const root = rootRef.current;
1699
+ if (root === null) return;
1700
+ const nodes = Array.from(root.querySelectorAll(":scope > .dq-card, :scope > .dq-card-pair"));
1701
+ setTargets(nodes.map((el, i) => ({ el, label: sections[i]?.label ?? "" })).filter((item) => item.label !== ""));
1702
+ }, [rootRef, signature]);
1703
+ (0, import_react4.useEffect)(() => {
1704
+ const root = rootRef.current;
1705
+ if (root === null) return;
1706
+ const scrollport = root.closest("[data-conversation-scroll]");
1707
+ const measure = () => {
1708
+ const rect = root.getBoundingClientRect();
1709
+ setPos({ right: window.innerWidth - rect.left + 12, visible: window.innerWidth >= 1200 && rect.left >= 170 });
1710
+ };
1711
+ measure();
1712
+ const scroller = scrollport ?? window;
1713
+ scroller.addEventListener("scroll", measure, { passive: true });
1714
+ window.addEventListener("resize", measure);
1715
+ return () => {
1716
+ scroller.removeEventListener("scroll", measure);
1717
+ window.removeEventListener("resize", measure);
1718
+ };
1719
+ }, [rootRef]);
1720
+ (0, import_react4.useEffect)(() => {
1721
+ if (targets.length === 0) return;
1722
+ const root = rootRef.current;
1723
+ if (root === null) return;
1724
+ const scrollport = root.closest("[data-conversation-scroll]");
1725
+ const onScroll = () => {
1726
+ const base = scrollport === null ? 0 : scrollport.getBoundingClientRect().top;
1727
+ let current = 0;
1728
+ targets.forEach((target, i) => {
1729
+ if (target.el.getBoundingClientRect().top - base <= 120) current = i;
1730
+ });
1731
+ setActive(current);
1732
+ };
1733
+ onScroll();
1734
+ const scroller = scrollport ?? window;
1735
+ scroller.addEventListener("scroll", onScroll, { passive: true });
1736
+ return () => scroller.removeEventListener("scroll", onScroll);
1737
+ }, [targets, rootRef]);
1738
+ if (targets.length < 3) return null;
1739
+ const go = (el) => {
1740
+ const root = rootRef.current;
1741
+ const scrollport = root?.closest("[data-conversation-scroll]");
1742
+ const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1743
+ const behavior = reduce ? "auto" : "smooth";
1744
+ if (scrollport !== null) {
1745
+ const base = scrollport.getBoundingClientRect().top;
1746
+ scrollport.scrollTo({ top: scrollport.scrollTop + el.getBoundingClientRect().top - base - 12, behavior });
1747
+ } else {
1748
+ window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 12, behavior });
1749
+ }
1750
+ };
1751
+ 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)(
1752
+ "button",
1753
+ {
1754
+ type: "button",
1755
+ className: `dq-sectionnav-dot${i === active ? " dq-sectionnav-dot--active" : ""}`,
1756
+ "aria-current": i === active ? "true" : void 0,
1757
+ onClick: () => go(target.el),
1758
+ children: [
1759
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sectionnav-label", children: target.label }),
1760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-sectionnav-pip", "aria-hidden": "true" })
1761
+ ]
1762
+ }
1763
+ ) }, `${target.label}-${String(i)}`)) }) });
1764
+ }
1765
+ function Collapsible(props) {
1766
+ const ref = (0, import_react4.useRef)(null);
1767
+ const animationRef = (0, import_react4.useRef)(null);
1768
+ const open = props.open;
1769
+ (0, import_react4.useLayoutEffect)(() => {
1770
+ const el = ref.current;
1771
+ if (el === null) return;
1772
+ animationRef.current?.cancel();
1773
+ animationRef.current = null;
1774
+ const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1775
+ const canAnimate = typeof el.animate === "function" && !reduce;
1776
+ const reset = () => {
1777
+ el.style.height = "";
1778
+ el.style.overflow = "";
1779
+ el.style.opacity = "";
1780
+ };
1781
+ if (open) {
1782
+ if (!canAnimate) {
1783
+ el.style.display = "";
1784
+ reset();
1785
+ return;
1786
+ }
1787
+ el.style.display = "block";
1788
+ el.style.overflow = "hidden";
1789
+ el.style.height = "0px";
1790
+ el.style.opacity = "0";
1791
+ const height2 = el.scrollHeight;
1792
+ const animation2 = el.animate(
1793
+ [{ height: "0px", opacity: 0 }, { height: `${height2}px`, opacity: 1 }],
1794
+ { duration: 300, easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
1795
+ );
1796
+ animationRef.current = animation2;
1797
+ animation2.addEventListener("finish", reset);
1798
+ return () => animation2.cancel();
1799
+ }
1800
+ if (el.style.display === "none") return;
1801
+ if (!canAnimate) {
1802
+ reset();
1803
+ el.style.display = "none";
1804
+ return;
1805
+ }
1806
+ const height = el.getBoundingClientRect().height || el.scrollHeight;
1807
+ el.style.overflow = "hidden";
1808
+ el.style.height = `${height}px`;
1809
+ const animation = el.animate(
1810
+ [{ height: `${height}px`, opacity: 1 }, { height: "0px", opacity: 0 }],
1811
+ { duration: 220, easing: "cubic-bezier(0.4, 0, 1, 1)" }
1812
+ );
1813
+ animationRef.current = animation;
1814
+ animation.addEventListener("finish", () => {
1815
+ reset();
1816
+ el.style.display = "none";
1817
+ });
1818
+ return () => animation.cancel();
1819
+ }, [open]);
1820
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, children: props.children });
1821
+ }
1813
1822
  function Stat(props) {
1814
1823
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
1815
1824
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: props.label }),
@@ -1821,13 +1830,13 @@ function Link(props) {
1821
1830
  }
1822
1831
  function ExportMenu(props) {
1823
1832
  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)(() => {
1833
+ const [open, setOpen] = (0, import_react4.useState)(false);
1834
+ const [done, setDone] = (0, import_react4.useState)(false);
1835
+ const rootRef = (0, import_react4.useRef)(null);
1836
+ const triggerRef = (0, import_react4.useRef)(null);
1837
+ const menuRef = (0, import_react4.useRef)(null);
1838
+ const doneTimerRef = (0, import_react4.useRef)(null);
1839
+ (0, import_react4.useEffect)(() => {
1831
1840
  if (!open) return;
1832
1841
  const onDown = (event) => {
1833
1842
  if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
@@ -1845,7 +1854,7 @@ function ExportMenu(props) {
1845
1854
  document.removeEventListener("keydown", onKey);
1846
1855
  };
1847
1856
  }, [open]);
1848
- (0, import_react5.useEffect)(() => () => {
1857
+ (0, import_react4.useEffect)(() => () => {
1849
1858
  if (doneTimerRef.current !== null) window.clearTimeout(doneTimerRef.current);
1850
1859
  }, []);
1851
1860
  const finish = () => {
@@ -2015,7 +2024,8 @@ function CacheCard(props) {
2015
2024
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--miss" }),
2016
2025
  t("cache.missLegend", { tokens: fmtCompact(totals.input, locale) })
2017
2026
  ] })
2018
- ] })
2027
+ ] }),
2028
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: t("cache.basis", { cost: fmt(totals.cost), miss: fmt(wouldHaveCost) }) })
2019
2029
  ] });
2020
2030
  }
2021
2031
  function VisionCard(props) {
@@ -2058,11 +2068,44 @@ function VisionCard(props) {
2058
2068
  function PeakCard(props) {
2059
2069
  const { t, locale } = useI18n();
2060
2070
  const { split, pricing } = props;
2071
+ const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
2072
+ (0, import_react4.useEffect)(() => {
2073
+ const id = setInterval(() => setNowMs(Date.now()), 6e4);
2074
+ return () => clearInterval(id);
2075
+ }, []);
2061
2076
  const total = split.peak.total + split.offPeak.total;
2062
2077
  if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") });
2063
2078
  const peakShare = split.peak.total / total;
2064
2079
  const shiftSaving = split.peakEraCost - split.offPeakEraCost;
2080
+ const beijing = new Date(nowMs + 8 * 36e5);
2081
+ const hour = beijing.getUTCHours();
2082
+ const minute = beijing.getUTCMinutes();
2083
+ const weekday = beijing.getUTCDay();
2084
+ const weekend = weekday === 0 || weekday === 6;
2085
+ 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])]);
2086
+ const isPeakNow = !weekend && windows.some(([from, to]) => hour >= from && hour < to);
2087
+ const nowPercent = (hour + minute / 60) / 24 * 100;
2088
+ const clockCells = Array.from({ length: 24 }, (_, h) => !weekend && windows.some(([from, to]) => h >= from && h < to));
2089
+ const nowState = isPeakNow ? t("peak.nowPeak") : t("peak.nowOff");
2065
2090
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak", children: [
2091
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock", children: [
2092
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-head", children: [
2093
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-clock-title", children: t("peak.clockTitle") }),
2094
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-clock-status ${isPeakNow ? "dq-clock-status--peak" : "dq-clock-status--off"}`, children: nowState })
2095
+ ] }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-track", role: "img", "aria-label": t("peak.clockAria", { state: nowState }), children: [
2097
+ clockCells.map((peak, h) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-clock-cell${peak ? " dq-clock-cell--peak" : ""}` }, h)),
2098
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-clock-now", style: { left: `${nowPercent}%` }, "aria-hidden": "true" })
2099
+ ] }),
2100
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-clock-scale", "aria-hidden": "true", children: [
2101
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "0" }),
2102
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "6" }),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "12" }),
2104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "18" }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "24" })
2106
+ ] }),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-clock-hint", children: t("peak.clockHint") })
2108
+ ] }),
2066
2109
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2067
2110
  "div",
2068
2111
  {
@@ -2092,12 +2135,9 @@ function PeakCard(props) {
2092
2135
  ] }),
2093
2136
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-figures", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2094
2137
  /* @__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
- ] }) })
2138
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value dq-stat-value--ok", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
2139
+ ] }) }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-peak-foot", children: t("peak.basis", { total: fmt(split.offPeakEraCost) }) })
2101
2141
  ] });
2102
2142
  }
2103
2143
  function agoText(ms, t) {
@@ -2119,8 +2159,8 @@ function SessionDetail(props) {
2119
2159
  function SessionRanking(props) {
2120
2160
  const { t, locale } = useI18n();
2121
2161
  const { sessions, count, totalCost } = props;
2122
- const [details, setDetails] = (0, import_react5.useState)({});
2123
- const startedRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
2162
+ const [details, setDetails] = (0, import_react4.useState)({});
2163
+ const startedRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
2124
2164
  const loadDetail = (id) => {
2125
2165
  if (startedRef.current.has(id)) return;
2126
2166
  startedRef.current.add(id);
@@ -2221,11 +2261,11 @@ function ModelRanking(props) {
2221
2261
  function ModelPicker(props) {
2222
2262
  const { t, locale } = useI18n();
2223
2263
  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)(() => {
2264
+ const [open, setOpen] = (0, import_react4.useState)(false);
2265
+ const rootRef = (0, import_react4.useRef)(null);
2266
+ const triggerRef = (0, import_react4.useRef)(null);
2267
+ const menuRef = (0, import_react4.useRef)(null);
2268
+ (0, import_react4.useEffect)(() => {
2229
2269
  if (!open) return;
2230
2270
  const onDown = (e) => {
2231
2271
  if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
@@ -2327,55 +2367,46 @@ function BalanceDashboard(props) {
2327
2367
  const cachedBalance = getCachedBalance();
2328
2368
  const cachedUsage = getCachedUsage();
2329
2369
  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)(() => {
2370
+ const [balance, setBalance] = (0, import_react4.useState)(cachedBalance?.data ?? null);
2371
+ const [usage, setUsage] = (0, import_react4.useState)(cachedUsage?.data ?? null);
2372
+ const [error, setError] = (0, import_react4.useState)(null);
2373
+ const [notice, setNotice] = (0, import_react4.useState)(null);
2374
+ const [loadingBalance, setLoadingBalance] = (0, import_react4.useState)(cachedBalance === null);
2375
+ const [loadingUsage, setLoadingUsage] = (0, import_react4.useState)(cachedUsage === null);
2376
+ const [refreshing, setRefreshing] = (0, import_react4.useState)(false);
2377
+ const [usageUpdatedAt, setUsageUpdatedAt] = (0, import_react4.useState)(cachedUsageAt);
2378
+ const [syncState, setSyncState] = (0, import_react4.useState)(cachedUsageAt === null ? "syncing" : "cached");
2379
+ const [freshnessNow, setFreshnessNow] = (0, import_react4.useState)(Date.now());
2380
+ const [settingsOpen, setSettingsOpen] = (0, import_react4.useState)(false);
2381
+ const hadDataRef = (0, import_react4.useRef)(cachedBalance !== null || cachedUsage !== null);
2382
+ const [widgetOn, setWidgetOn] = (0, import_react4.useState)(widgetVisibleStore.get());
2383
+ const [widgetTabIds, setWidgetTabIds] = (0, import_react4.useState)(widgetTabIdsStore.get());
2384
+ const [viewTabs, setViewTabs] = (0, import_react4.useState)(() => props.views.list());
2385
+ (0, import_react4.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
2386
+ const [lowBalance, setLowBalance] = (0, import_react4.useState)(lowBalanceStore.get());
2387
+ const [monthlyBudget, setMonthlyBudget] = (0, import_react4.useState)(monthlyBudgetStore.get());
2388
+ const [barMode, setBarMode] = (0, import_react4.useState)("daily");
2389
+ const [selectedModels, setSelectedModels] = (0, import_react4.useState)([]);
2390
+ const [windowDays, setWindowDays] = (0, import_react4.useState)(usageWindowStore.get());
2391
+ const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
2392
+ const [sidebarEntry, setSidebarEntry] = (0, import_react4.useState)(sidebarEntryStore.get());
2393
+ (0, import_react4.useEffect)(() => sidebarEntryStore.subscribe(() => setSidebarEntry(sidebarEntryStore.get())), []);
2394
+ const [tabDragging, setTabDragging] = (0, import_react4.useState)(false);
2395
+ const [tabDropTarget, setTabDropTarget] = (0, import_react4.useState)(null);
2396
+ const [retainedVision, setRetainedVision] = (0, import_react4.useState)(null);
2397
+ const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
2398
+ const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
2399
+ const [remainingHovering, setRemainingHovering] = (0, import_react4.useState)(false);
2400
+ const [remainingManuallyOpened, setRemainingManuallyOpened] = (0, import_react4.useState)(false);
2401
+ const budgetInputRef = (0, import_react4.useRef)(null);
2402
+ const rootRef = (0, import_react4.useRef)(null);
2403
+ const [composerHeight, setComposerHeight] = (0, import_react4.useState)(null);
2404
+ const [composerWidth, setComposerWidth] = (0, import_react4.useState)(null);
2405
+ const [sessionUsage, setSessionUsage] = (0, import_react4.useState)(null);
2406
+ const [loadingSessionUsage, setLoadingSessionUsage] = (0, import_react4.useState)(props.sessionId !== void 0 && props.sessionId !== "");
2407
+ const [sessionUsageError, setSessionUsageError] = (0, import_react4.useState)(null);
2408
+ const sessionUsageIdRef = (0, import_react4.useRef)(null);
2409
+ (0, import_react4.useLayoutEffect)(() => {
2379
2410
  const node = rootRef.current;
2380
2411
  if (node === null) return;
2381
2412
  const measure = () => {
@@ -2406,13 +2437,13 @@ function BalanceDashboard(props) {
2406
2437
  window.removeEventListener("resize", measure);
2407
2438
  };
2408
2439
  }, []);
2409
- (0, import_react5.useLayoutEffect)(() => {
2440
+ (0, import_react4.useLayoutEffect)(() => {
2410
2441
  const node = rootRef.current;
2411
2442
  if (node === null) return;
2412
2443
  const scrollport = node.closest("[data-conversation-scroll]");
2413
2444
  if (scrollport instanceof HTMLElement) scrollport.scrollTop = 0;
2414
2445
  }, [props.sessionId]);
2415
- (0, import_react5.useLayoutEffect)(() => {
2446
+ (0, import_react4.useLayoutEffect)(() => {
2416
2447
  quotaViewActiveStore.set(true);
2417
2448
  return () => quotaViewActiveStore.set(false);
2418
2449
  }, []);
@@ -2486,10 +2517,10 @@ function BalanceDashboard(props) {
2486
2517
  if (sessionUsageIdRef.current === id) setLoadingSessionUsage(false);
2487
2518
  }
2488
2519
  };
2489
- (0, import_react5.useEffect)(() => {
2520
+ (0, import_react4.useEffect)(() => {
2490
2521
  void load(false);
2491
2522
  }, []);
2492
- (0, import_react5.useEffect)(() => {
2523
+ (0, import_react4.useEffect)(() => {
2493
2524
  const id = props.sessionId;
2494
2525
  sessionUsageIdRef.current = id ?? null;
2495
2526
  if (id === void 0 || id === "") {
@@ -2503,57 +2534,7 @@ function BalanceDashboard(props) {
2503
2534
  setLoadingSessionUsage(true);
2504
2535
  void loadSessionUsage(id, false);
2505
2536
  }, [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)(() => {
2537
+ (0, import_react4.useEffect)(() => {
2557
2538
  const timer = window.setInterval(() => setFreshnessNow(Date.now()), 3e4);
2558
2539
  return () => window.clearInterval(timer);
2559
2540
  }, []);
@@ -2577,66 +2558,6 @@ function BalanceDashboard(props) {
2577
2558
  setMonthlyBudget(value);
2578
2559
  monthlyBudgetStore.set(value);
2579
2560
  };
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
2561
  const changeWindowDays = (value) => {
2641
2562
  setWindowDays(value);
2642
2563
  usageWindowStore.set(value);
@@ -2732,7 +2653,7 @@ function BalanceDashboard(props) {
2732
2653
  costTotal: window2.totals.cost
2733
2654
  });
2734
2655
  const liveVision = visionOpen && activeWindow !== null && visionStats !== void 0 ? visionPayload(activeWindow, visionStats, activeWindowName) : null;
2735
- (0, import_react5.useEffect)(() => {
2656
+ (0, import_react4.useEffect)(() => {
2736
2657
  if (visionOpen && activeWindow !== null && visionStats !== void 0) {
2737
2658
  setRetainedVision(visionPayload(activeWindow, visionStats, activeWindowName));
2738
2659
  return;
@@ -2816,15 +2737,6 @@ function BalanceDashboard(props) {
2816
2737
  { error: localizeApiError(notice.error, t, "error.query") }
2817
2738
  );
2818
2739
  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
2740
  const composerSafeAreaStyle = {
2829
2741
  "--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px`,
2830
2742
  // Only set once measured; .dq-balance's max-width falls back to 860px
@@ -2832,7 +2744,18 @@ function BalanceDashboard(props) {
2832
2744
  // column, then narrows in place — no flash of the old width).
2833
2745
  ...composerWidth !== null ? { "--dq-composer-w": `${composerWidth}px` } : {}
2834
2746
  };
2747
+ const navSections = [
2748
+ { key: "balance", label: t("nav.balance") },
2749
+ ...props.sessionId !== void 0 && props.sessionId !== "" ? [{ key: "session", label: t("nav.session") }] : [],
2750
+ { key: "overview", label: t("nav.overview") },
2751
+ { key: "trend", label: t("nav.trend") },
2752
+ { key: "heatmap", label: t("nav.heatmap") },
2753
+ { key: "models", label: t("nav.models") },
2754
+ { key: "sessions", label: t("nav.sessions") },
2755
+ { key: "peakcache", label: t("nav.peakcache") }
2756
+ ];
2835
2757
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance", ref: rootRef, style: composerSafeAreaStyle, children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SectionNav, { rootRef, label: t("nav.label"), sections: navSections }),
2836
2759
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-row", children: [
2837
2760
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-messages", children: [
2838
2761
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2851,23 +2774,39 @@ function BalanceDashboard(props) {
2851
2774
  noticeText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-warn", children: noticeText }),
2852
2775
  errorText !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-error", children: errorText })
2853
2776
  ] }),
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
- )
2777
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-actions", children: [
2778
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2779
+ "button",
2780
+ {
2781
+ type: "button",
2782
+ className: "dq-settings-btn",
2783
+ "aria-expanded": settingsOpen,
2784
+ "aria-controls": "dq-settings-panel",
2785
+ onClick: () => setSettingsOpen((open) => !open),
2786
+ children: [
2787
+ t("settings.title"),
2788
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-settings-caret${settingsOpen ? " dq-settings-caret--open" : ""}`, "aria-hidden": "true", children: "\u25BE" })
2789
+ ]
2790
+ }
2791
+ ),
2792
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2793
+ "button",
2794
+ {
2795
+ type: "button",
2796
+ className: "dq-refresh-btn",
2797
+ title: t("status.forceRefresh"),
2798
+ disabled: refreshing,
2799
+ "aria-busy": refreshing,
2800
+ onClick: () => {
2801
+ void load(true);
2802
+ },
2803
+ children: [
2804
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-refresh-icon${refreshing ? " dq-refresh-icon--spin" : ""}`, children: "\u21BB" }),
2805
+ refreshing ? t("status.refreshing") : t("status.refresh")
2806
+ ]
2807
+ }
2808
+ )
2809
+ ] })
2871
2810
  ] }),
2872
2811
  balanceLow && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-alert", role: "status", children: [
2873
2812
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-alert-icon", "aria-hidden": "true", children: "!" }),
@@ -2949,6 +2888,144 @@ function BalanceDashboard(props) {
2949
2888
  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
2889
  ] }) : loadingBalance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BalanceSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("balance.empty") })
2951
2890
  ] }),
2891
+ /* @__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: [
2892
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
2893
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupDisplay") }),
2895
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-toggle", children: [
2896
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: widgetOn, onChange: toggle }),
2897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.widget") })
2898
+ ] }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-toggle-hint", children: t("settings.widgetHint") }),
2900
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting dq-setting--tabs", children: [
2901
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-widget-tabs-label", children: t("settings.widgetTabs") }),
2902
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control dq-widget-tabs", role: "group", "aria-labelledby": "dq-widget-tabs-label", children: [
2903
+ /* @__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: [
2904
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2905
+ "input",
2906
+ {
2907
+ type: "checkbox",
2908
+ checked: widgetTabIds === null || widgetTabIds.includes(tab.id),
2909
+ onChange: () => toggleWidgetTab(tab.id)
2910
+ }
2911
+ ),
2912
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: tab.label })
2913
+ ] }, tab.id)) }),
2914
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: widgetTabs.length === 0 ? t("settings.widgetTabsEmpty") : t("settings.widgetTabsHint") })
2915
+ ] })
2916
+ ] })
2917
+ ] }),
2918
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2919
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupAlerts") }),
2920
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2921
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
2922
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2923
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2924
+ "input",
2925
+ {
2926
+ id: "dq-low-balance",
2927
+ className: "dq-number",
2928
+ type: "number",
2929
+ min: 0,
2930
+ step: 1,
2931
+ value: lowBalance,
2932
+ onChange: (e) => changeLowBalance(Math.max(0, Number(e.target.value) || 0))
2933
+ }
2934
+ ),
2935
+ /* @__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") })
2936
+ ] })
2937
+ ] }),
2938
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2939
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-monthly-budget", children: t("settings.monthlyBudget") }),
2940
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2941
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2942
+ "input",
2943
+ {
2944
+ ref: budgetInputRef,
2945
+ id: "dq-monthly-budget",
2946
+ className: "dq-number",
2947
+ type: "number",
2948
+ min: 0,
2949
+ step: 10,
2950
+ value: monthlyBudget,
2951
+ "aria-describedby": "dq-monthly-budget-hint",
2952
+ onChange: (e) => changeMonthlyBudget(Math.max(0, Number(e.target.value) || 0))
2953
+ }
2954
+ ),
2955
+ /* @__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") })
2956
+ ] })
2957
+ ] })
2958
+ ] }),
2959
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
2960
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("settings.groupLayout") }),
2961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2962
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-sidebar-entry", children: t("settings.sidebarEntry") }),
2963
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2964
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2965
+ "select",
2966
+ {
2967
+ id: "dq-sidebar-entry",
2968
+ className: "dq-select",
2969
+ value: sidebarEntry,
2970
+ onChange: (e) => changeSidebarEntry(e.target.value),
2971
+ children: [
2972
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: t("settings.sidebarEntryOff") }),
2973
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "top", children: t("settings.sidebarEntryTop") }),
2974
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "bottom", children: t("settings.sidebarEntryBottom") })
2975
+ ]
2976
+ }
2977
+ ),
2978
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: t("settings.sidebarEntryHint") })
2979
+ ] })
2980
+ ] }),
2981
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2982
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-tab-order-label", children: t("settings.tabPosition") }),
2983
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2984
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2985
+ "div",
2986
+ {
2987
+ className: "dq-tab-order",
2988
+ role: "group",
2989
+ "aria-labelledby": "dq-tab-order-label",
2990
+ "aria-describedby": "dq-tab-order-hint",
2991
+ onDragOver: onOrderDragOver,
2992
+ onDrop: onOrderDrop,
2993
+ children: viewTabs.map((tab, index) => {
2994
+ const current = tab.id === "balance";
2995
+ const dropTarget = tabDragging && !current && tabDropTarget === index;
2996
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2997
+ "span",
2998
+ {
2999
+ className: `dq-tab-chip${current ? " dq-tab-chip--current" : ""}${current && tabDragging ? " dq-tab-chip--dragging" : ""}${dropTarget ? " dq-tab-chip--drop-target" : ""}`,
3000
+ draggable: current,
3001
+ tabIndex: current ? 0 : void 0,
3002
+ role: current ? "button" : void 0,
3003
+ "aria-label": current ? t("settings.tabDragAria") : void 0,
3004
+ onDragStart: current ? onQuotaTabDragStart : void 0,
3005
+ onDragEnd: current ? endTabDrag : void 0,
3006
+ onDragOver: (event) => onChipDragOver(event, index),
3007
+ onDrop: (event) => onChipDrop(event, index),
3008
+ onKeyDown: current ? onQuotaTabKeyDown : void 0,
3009
+ children: tab.label
3010
+ },
3011
+ tab.id
3012
+ );
3013
+ })
3014
+ }
3015
+ ),
3016
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-tab-order-hint", className: "dq-setting-hint", children: t("settings.tabPositionHint") })
3017
+ ] })
3018
+ ] })
3019
+ ] }),
3020
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-group", children: [
3021
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-group-title", children: t("links.title") }),
3022
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-links", children: [
3023
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/usage", children: t("links.usage") }),
3024
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://platform.deepseek.com/api_keys", children: t("links.apiKey") }),
3025
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Link, { href: "https://status.deepseek.com", children: t("links.status") })
3026
+ ] })
3027
+ ] })
3028
+ ] }) }),
2952
3029
  props.sessionId !== void 0 && props.sessionId !== "" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
2953
3030
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: t("common.estimated"), children: t("sessionUsage.title") }),
2954
3031
  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 +3052,10 @@ function BalanceDashboard(props) {
2975
3052
  {
2976
3053
  spent: usage.summary.month.cost,
2977
3054
  budget: monthlyBudget,
2978
- onConfigure: () => budgetInputRef.current?.focus()
3055
+ onConfigure: () => {
3056
+ setSettingsOpen(true);
3057
+ window.setTimeout(() => budgetInputRef.current?.focus(), 80);
3058
+ }
2979
3059
  }
2980
3060
  ),
2981
3061
  usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
@@ -3103,339 +3183,13 @@ function BalanceDashboard(props) {
3103
3183
  ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
3104
3184
  ] })
3105
3185
  ] }),
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" })
3186
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.9.1" })
3301
3187
  ] });
3302
3188
  }
3303
3189
 
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
3190
  // src/client/message-cost.tsx
3437
- var import_react7 = require("react");
3438
- var import_jsx_runtime6 = require("react/jsx-runtime");
3191
+ var import_react5 = require("react");
3192
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3439
3193
  var fmtTurnCost = (cost) => cost.toLocaleString(void 0, {
3440
3194
  minimumFractionDigits: cost > 0 && cost < 0.01 ? 4 : 2,
3441
3195
  maximumFractionDigits: cost > 0 && cost < 0.01 ? 6 : 4
@@ -3446,8 +3200,8 @@ var cachedData = (sessionId) => {
3446
3200
  };
3447
3201
  function MessageCost({ sessionId, messageId }) {
3448
3202
  const { t } = useI18n();
3449
- const [data, setData] = (0, import_react7.useState)(() => cachedData(sessionId));
3450
- (0, import_react7.useEffect)(() => {
3203
+ const [data, setData] = (0, import_react5.useState)(() => cachedData(sessionId));
3204
+ (0, import_react5.useEffect)(() => {
3451
3205
  setData(cachedData(sessionId));
3452
3206
  const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData(sessionId)));
3453
3207
  const hit = cachedData(sessionId)?.turns.some((turn2) => turn2.messageId === messageId) ?? false;
@@ -3458,7 +3212,7 @@ function MessageCost({ sessionId, messageId }) {
3458
3212
  if (turn === void 0) return null;
3459
3213
  const amount = `\xA5${fmtTurnCost(turn.cost)}`;
3460
3214
  const tooltip = t("messageCost.tooltip");
3461
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3215
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3462
3216
  "span",
3463
3217
  {
3464
3218
  className: "dq-message-cost",
@@ -3466,15 +3220,15 @@ function MessageCost({ sessionId, messageId }) {
3466
3220
  "data-dq-tooltip": tooltip,
3467
3221
  "aria-label": t("messageCost.aria", { amount }),
3468
3222
  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 })
3223
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-separator", "aria-hidden": true, children: "\xB7" }),
3224
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-amount", children: amount })
3471
3225
  ]
3472
3226
  }
3473
3227
  );
3474
3228
  }
3475
3229
 
3476
3230
  // src/client/session-cost-pill.tsx
3477
- var import_react8 = require("react");
3231
+ var import_react6 = require("react");
3478
3232
  var import_react_dom = require("react-dom");
3479
3233
 
3480
3234
  // src/pricing.ts
@@ -3482,7 +3236,7 @@ var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
3482
3236
  var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
3483
3237
  var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
3484
3238
  var PEAK_WINDOWS = [[9, 12], [14, 18]];
3485
- var BEIJING_OFFSET_MS4 = 8 * 36e5;
3239
+ var BEIJING_OFFSET_MS3 = 8 * 36e5;
3486
3240
  var FLAT_RATES = {
3487
3241
  pro: { cacheHit: 0.025, input: 3, output: 6 },
3488
3242
  flash: { cacheHit: 0.02, input: 1, output: 2 },
@@ -3527,12 +3281,12 @@ function billedTierOf(timeMs, model) {
3527
3281
  return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
3528
3282
  }
3529
3283
  function isBeijingWeekday(timeMs) {
3530
- const day = new Date(timeMs + BEIJING_OFFSET_MS4).getUTCDay();
3284
+ const day = new Date(timeMs + BEIJING_OFFSET_MS3).getUTCDay();
3531
3285
  return day >= 1 && day <= 5;
3532
3286
  }
3533
3287
  function isPeak(timeMs) {
3534
3288
  if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
3535
- const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS4) % 864e5 + 864e5) % 864e5 / 36e5);
3289
+ const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS3) % 864e5 + 864e5) % 864e5 / 36e5);
3536
3290
  return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
3537
3291
  }
3538
3292
  function ratesAt(timeMs, model) {
@@ -3542,7 +3296,7 @@ function ratesAt(timeMs, model) {
3542
3296
  }
3543
3297
 
3544
3298
  // src/client/session-cost-pill.tsx
3545
- var import_jsx_runtime7 = require("react/jsx-runtime");
3299
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3546
3300
  var PANEL_MARGIN = 12;
3547
3301
  var PANEL_GAP = 8;
3548
3302
  var cachedData2 = (sessionId) => {
@@ -3550,7 +3304,7 @@ var cachedData2 = (sessionId) => {
3550
3304
  return response?.ok === true ? response.data ?? null : null;
3551
3305
  };
3552
3306
  function CostIcon() {
3553
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3307
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3554
3308
  "svg",
3555
3309
  {
3556
3310
  width: "14",
@@ -3564,32 +3318,32 @@ function CostIcon() {
3564
3318
  "aria-hidden": "true",
3565
3319
  focusable: "false",
3566
3320
  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" })
3321
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "8", cy: "8", r: "6" }),
3322
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.5 5.3 8 8.4l2.5-3.1" }),
3323
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 8.4v2.9" }),
3324
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 9.5h4.4" }),
3325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 10.9h4.4" })
3572
3326
  ]
3573
3327
  }
3574
3328
  );
3575
3329
  }
3576
3330
  function SessionCostPill({ sessionId }) {
3577
3331
  const { t, locale } = useI18n();
3578
- const [data, setData] = (0, import_react8.useState)(
3332
+ const [data, setData] = (0, import_react6.useState)(
3579
3333
  () => sessionId === void 0 || sessionId === "" ? null : cachedData2(sessionId)
3580
3334
  );
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)(() => {
3335
+ const [open, setOpen] = (0, import_react6.useState)(false);
3336
+ const anchorRef = (0, import_react6.useRef)(null);
3337
+ const panelRef = (0, import_react6.useRef)(null);
3338
+ const [pos, setPos] = (0, import_react6.useState)(null);
3339
+ (0, import_react6.useEffect)(() => {
3586
3340
  if (sessionId === void 0 || sessionId === "") return;
3587
3341
  setData(cachedData2(sessionId));
3588
3342
  const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData2(sessionId)));
3589
3343
  if (getCachedSessionUsage(sessionId) === null) void fetchSessionUsage(sessionId);
3590
3344
  return unsubscribe;
3591
3345
  }, [sessionId]);
3592
- (0, import_react8.useLayoutEffect)(() => {
3346
+ (0, import_react6.useLayoutEffect)(() => {
3593
3347
  if (!open) {
3594
3348
  setPos(null);
3595
3349
  return;
@@ -3604,7 +3358,7 @@ function SessionCostPill({ sessionId }) {
3604
3358
  bottom: window.innerHeight - pill.top + PANEL_GAP
3605
3359
  });
3606
3360
  }, [open, data]);
3607
- (0, import_react8.useEffect)(() => {
3361
+ (0, import_react6.useEffect)(() => {
3608
3362
  if (!open) return;
3609
3363
  const close = () => setOpen(false);
3610
3364
  const onPointerDown = (event) => {
@@ -3656,8 +3410,8 @@ function SessionCostPill({ sessionId }) {
3656
3410
  hour12: false
3657
3411
  });
3658
3412
  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)(
3413
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3414
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { ref: anchorRef, className: "dq-cost-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3661
3415
  "button",
3662
3416
  {
3663
3417
  type: "button",
@@ -3667,13 +3421,13 @@ function SessionCostPill({ sessionId }) {
3667
3421
  "aria-label": t("sessionCost.aria", { amount }),
3668
3422
  onClick: () => setOpen((value) => !value),
3669
3423
  children: [
3670
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CostIcon, {}),
3671
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-pill-label", children: amount })
3424
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3425
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-pill-label", children: amount })
3672
3426
  ]
3673
3427
  }
3674
3428
  ) }),
3675
3429
  open && (0, import_react_dom.createPortal)(
3676
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3430
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3677
3431
  "div",
3678
3432
  {
3679
3433
  ref: panelRef,
@@ -3682,35 +3436,35 @@ function SessionCostPill({ sessionId }) {
3682
3436
  "aria-label": t("sessionCost.title"),
3683
3437
  style: pos === null ? { visibility: "hidden", left: 0, top: 0 } : { visibility: "visible", left: pos.left, bottom: pos.bottom, top: "auto" },
3684
3438
  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, {}),
3439
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dq-cost-panel-title", children: [
3440
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dq-cost-panel-title-label", children: [
3441
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3688
3442
  t("sessionCost.title")
3689
3443
  ] }),
3690
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-cost-panel-value", children: amount })
3444
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-panel-value", children: amount })
3691
3445
  ] }),
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: [
3446
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dq-cost-panel-rule", "aria-hidden": true }),
3447
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "dq-cost-details", children: [
3448
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.input") }),
3449
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3696
3450
  "\xA5",
3697
3451
  fmtTurnCost(split.input)
3698
3452
  ] }),
3699
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.cacheHit") }),
3700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
3453
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.cacheHit") }),
3454
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3701
3455
  "\xA5",
3702
3456
  fmtTurnCost(split.cache)
3703
3457
  ] }),
3704
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: t("common.output") }),
3705
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dd", { children: [
3458
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.output") }),
3459
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3706
3460
  "\xA5",
3707
3461
  fmtTurnCost(split.output)
3708
3462
  ] }),
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 })
3463
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionUsage.calls") }),
3464
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: fmtInt(data.calls) }),
3465
+ range !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3466
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionCost.active") }),
3467
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: range })
3714
3468
  ] })
3715
3469
  ] })
3716
3470
  ]
@@ -3773,18 +3527,6 @@ var css = `
3773
3527
  .dq-cost-pill:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
3774
3528
  .dq-cost-pill svg{flex:none}
3775
3529
  .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
3530
  /* Click-open breakdown: the host stat-dialog skin (menu surface, prominent
3789
3531
  elevation, 12/18 rows), portaled to the body and placed above the pill. */
3790
3532
  .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 +3697,11 @@ var css = `
3955
3697
  .dq-pricing-table{border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums;min-width:100%}
3956
3698
  .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
3699
  .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}
3700
+ /* Align by counting from the end: a peak row starts with the rowSpan'd model
3701
+ cell (5 cells) while its off-peak pair starts at \u65F6\u6BB5 (4 cells), so
3702
+ nth-child(n+3) left the off-peak cache-hit price stranded under \u65F6\u6BB5. The
3703
+ last three cells are the three price columns in both row shapes. */
3704
+ .dq-pricing-table th:nth-last-child(-n+3),.dq-pricing-table td:nth-last-child(-n+3){text-align:right}
3959
3705
  .dq-pricing-table th:last-child,.dq-pricing-table td:last-child{padding-right:0}
3960
3706
  .dq-pricing-table tbody td:first-child{font-weight:500}
3961
3707
  .dq-pricing-table tbody tr+tr td{border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.05))}
@@ -3987,8 +3733,23 @@ var css = `
3987
3733
  .dq-legend-swatch--peak{background:var(--dsw-alias-state-warning-primary,#bf8700)}
3988
3734
  .dq-legend-swatch--offpeak{background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.85}
3989
3735
  .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)}
3736
+ .dq-peak-foot{margin:12px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e);text-wrap:pretty}
3737
+ .dq-clock{margin-bottom:16px}
3738
+ .dq-clock-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}
3739
+ .dq-clock-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e)}
3740
+ .dq-clock-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px}
3741
+ .dq-clock-status::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor}
3742
+ .dq-clock-status--peak{color:var(--dq-text-warn);background:rgba(191,135,0,.12)}
3743
+ .dq-clock-status--off{color:var(--dq-text-success);background:rgba(45,164,78,.12)}
3744
+ .dq-clock-track{position:relative;display:flex;gap:1px;height:10px}
3745
+ .dq-clock-cell{flex:1 1 0;height:100%;background:var(--dsw-alias-state-success-primary,#2da44e);opacity:.55}
3746
+ .dq-clock-cell--peak{background:var(--dsw-alias-state-warning-primary,#bf8700);opacity:.92}
3747
+ .dq-clock-cell:first-child{border-radius:999px 0 0 999px}
3748
+ .dq-clock-cell:last-child{border-radius:0 999px 999px 0}
3749
+ .dq-clock-now{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--dsw-alias-label-primary,#1f2328);border-radius:2px;transform:translateX(-1px)}
3750
+ .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)}
3751
+ .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}
3752
+ .dq-clock-hint{margin:6px 0 0;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary,#59636e)}
3992
3753
  .dq-cache-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-bottom:12px}
3993
3754
  @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
3755
  .dq-muted-value{color:var(--dsw-alias-label-secondary,#59636e)!important;font-weight:500}
@@ -4054,28 +3815,29 @@ var css = `
4054
3815
  .dq-setting-label{width:128px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328);padding-top:6px;flex:none}
4055
3816
  .dq-setting-control{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
4056
3817
  .dq-setting-hint{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);line-height:1.5}
3818
+ .dq-setting-group{margin-top:28px}
3819
+ .dq-setting-group:first-of-type{margin-top:0}
3820
+ .dq-setting-group-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:0 0 10px;letter-spacing:.02em}
3821
+ .dq-setting-group .dq-toggle{margin-top:0}
3822
+ .dq-setting-group .dq-setting{margin-top:16px}
4057
3823
  /* Page footer: the package name + version, injected at build time from
4058
3824
  package.json (see build.mjs) so it can never drift from the release. */
4059
3825
  .dq-footer{margin:16px 0 0;font-size:12px;text-align:center;color:var(--dsw-alias-label-tertiary,#59636e)}
3826
+ .dq-sectionnav{position:fixed;top:50%;transform:translateY(-50%);z-index:20;width:max-content;pointer-events:none}
3827
+ .dq-sectionnav--hidden{display:none}
3828
+ .dq-sectionnav ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;pointer-events:auto}
3829
+ .dq-sectionnav li{display:flex;justify-content:flex-end}
3830
+ .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}
3831
+ .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))}
3832
+ .dq-sectionnav-dot:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3833
+ .dq-sectionnav-label{font-size:12px;color:inherit}
3834
+ .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}
3835
+ .dq-sectionnav-dot:hover .dq-sectionnav-pip{background:var(--dsw-alias-label-secondary,#59636e)}
3836
+ .dq-sectionnav-dot--active{color:var(--dsw-alias-label-primary,#1f2328);font-weight:600}
3837
+ .dq-sectionnav-dot--active .dq-sectionnav-pip{background:var(--dsw-alias-state-business-primary,#0969da);transform:scale(1.25)}
3838
+ @media (max-width:1199px){.dq-sectionnav{display:none}}
4060
3839
  .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
3840
  .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
3841
  .dq-links{display:flex;flex-wrap:wrap;gap:8px}
4080
3842
  .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
3843
  .dq-link::after{content:"\u2197";font-size:12px;color:var(--dsw-alias-label-secondary,#59636e)}
@@ -4142,7 +3904,10 @@ var css = `
4142
3904
  .dq-chart-switch-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
4143
3905
  .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
3906
  .dq-chart-wrap{position:relative}
4145
- .dq-bars-scroll{overflow-x:auto;overflow-y:hidden;padding-bottom:2px}
3907
+ .dq-plot{display:flex;align-items:flex-end;gap:8px}
3908
+ .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}
3909
+ .dq-yaxis span{white-space:nowrap}
3910
+ .dq-bars-scroll{flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}
4146
3911
  .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
3912
  .dq-tip--above{transform:translate(-50%,calc(-100% - 8px))}
4148
3913
  .dq-tip-head{font-weight:600;margin-bottom:2px}
@@ -4163,13 +3928,13 @@ var css = `
4163
3928
  .dq-heat-cell--pinned{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;position:relative;z-index:2}
4164
3929
  .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
3930
  .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}
3931
+ .dq-heat-scroll{overflow:hidden;padding-bottom:2px}
3932
+ .dq-heatmap{display:flex;gap:2px}
3933
+ .dq-heat-week{display:flex;flex:1 1 0;min-width:0;flex-direction:column;gap:2px}
3934
+ .dq-heat-cell{width:100%;aspect-ratio:1/1;border-radius:2px;flex:none;min-width:0}
4170
3935
  .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}
3936
+ .dq-heat-months{display:flex;gap:2px;margin-top:4px}
3937
+ .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
3938
  .dq-heat-key{width:12px;height:12px;border-radius:2px;flex:none}
4174
3939
  .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
3940
  .dq-heat-scale>span:first-child{margin-right:2px}
@@ -4203,6 +3968,20 @@ var css = `
4203
3968
  .dq-sync--error .dq-sync-dot{background:var(--dsw-alias-state-error-primary,#cf222e)}
4204
3969
  @keyframes dq-sync-pulse{50%{opacity:.35;transform:scale(.8)}}
4205
3970
  .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}
3971
+ .dq-status-actions{display:flex;align-items:center;gap:8px;flex:none}
3972
+ .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}
3973
+ .dq-settings-btn:hover{border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
3974
+ .dq-settings-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
3975
+ .dq-settings-caret{font-size:10px;line-height:1;transition:transform .2s cubic-bezier(.16,1,.3,1)}
3976
+ .dq-settings-caret--open{transform:rotate(180deg)}
3977
+ .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}
3978
+ /* Motion: eased (easeOutExpo) arrivals. Operate surface \u2014 fast, purposeful,
3979
+ and fully disabled under prefers-reduced-motion (see the global reduce block). */
3980
+ .dq-export-menu,.dq-model-menu{transform-origin:top right;animation:dq-pop .18s cubic-bezier(.16,1,.3,1)}
3981
+ @keyframes dq-pop{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:none}}
3982
+ .dq-alert{animation:dq-alert-in .28s cubic-bezier(.16,1,.3,1)}
3983
+ @keyframes dq-alert-in{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
3984
+ @media (max-width:620px){.dq-settings-panel{padding:16px}}
4206
3985
  .dq-refresh-btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary,#0969da);color:var(--dsw-alias-state-business-primary,#0969da)}
4207
3986
  .dq-refresh-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
4208
3987
  .dq-refresh-btn:disabled{cursor:default;opacity:.6}
@@ -4290,7 +4069,7 @@ html[data-dq-quota-view] .dq-balance{padding-bottom:48px}
4290
4069
  `;
4291
4070
 
4292
4071
  // src/client/widget.tsx
4293
- var import_react9 = require("react");
4072
+ var import_react7 = require("react");
4294
4073
 
4295
4074
  // src/client/sessions.ts
4296
4075
  function currentSessionIdOf(state) {
@@ -4299,7 +4078,7 @@ function currentSessionIdOf(state) {
4299
4078
  }
4300
4079
 
4301
4080
  // src/client/widget.tsx
4302
- var import_jsx_runtime8 = require("react/jsx-runtime");
4081
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4303
4082
  var MARGIN = 16;
4304
4083
  var CORNER_KEY = "widget.corner";
4305
4084
  var COLLAPSED_KEY = "widget.collapsed";
@@ -4328,8 +4107,8 @@ function getBounds(node) {
4328
4107
  if (headerRect !== null && headerRect.bottom > top && headerRect.bottom < bottom - MIN_REGION_HEIGHT) {
4329
4108
  top = headerRect.bottom;
4330
4109
  }
4331
- const composerRect = slotBox(frame, "conversation.composer.dock", "ancestor");
4332
- if (composerRect !== null && composerRect.top < bottom && composerRect.top > top + MIN_REGION_HEIGHT) {
4110
+ const composerRect = getComposerElement(frame)?.getBoundingClientRect() ?? null;
4111
+ if (composerRect !== null && composerRect.top < bottom && composerRect.top > top + (bottom - top) / 2) {
4333
4112
  bottom = composerRect.top;
4334
4113
  }
4335
4114
  return { left, top, right, bottom };
@@ -4350,47 +4129,47 @@ function cornerPos(corner, node, bounds) {
4350
4129
  function QuotaWidget(props) {
4351
4130
  const { t } = useI18n();
4352
4131
  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);
4132
+ const [visible, setVisible] = (0, import_react7.useState)(widgetVisibleStore.get());
4133
+ (0, import_react7.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
4134
+ const [quotaViewActive2, setQuotaViewActive] = (0, import_react7.useState)(quotaViewActiveStore.get());
4135
+ (0, import_react7.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
4136
+ const [lowBalance, setLowBalance] = (0, import_react7.useState)(lowBalanceStore.get());
4137
+ (0, import_react7.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
4138
+ const [selectedTabIds, setSelectedTabIds] = (0, import_react7.useState)(widgetTabIdsStore.get());
4139
+ (0, import_react7.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
4140
+ const [viewTabs, setViewTabs] = (0, import_react7.useState)(() => props.views.list());
4141
+ (0, import_react7.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
4142
+ const [activeViewId, setActiveViewId] = (0, import_react7.useState)(null);
4364
4143
  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);
4144
+ const [data, setData] = (0, import_react7.useState)(cachedBalance?.data ?? null);
4145
+ const [error, setError] = (0, import_react7.useState)(null);
4146
+ const [loading, setLoading] = (0, import_react7.useState)(cachedBalance === null);
4147
+ const [collapsed, setCollapsed] = (0, import_react7.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
4148
+ const [pos, setPos] = (0, import_react7.useState)(null);
4149
+ const [corner, setCorner] = (0, import_react7.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
4150
+ const [dragging, setDragging] = (0, import_react7.useState)(false);
4151
+ const rootRef = (0, import_react7.useRef)(null);
4152
+ const dragRef = (0, import_react7.useRef)(null);
4374
4153
  const readUsage = () => {
4375
4154
  const cached = getCachedUsage();
4376
4155
  return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
4377
4156
  };
4378
- const [usage, setUsage] = (0, import_react9.useState)(readUsage);
4379
- (0, import_react9.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
4157
+ const [usage, setUsage] = (0, import_react7.useState)(readUsage);
4158
+ (0, import_react7.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
4380
4159
  const readSessionUsage = (id) => {
4381
4160
  if (id === void 0) return null;
4382
4161
  const response = getCachedSessionUsage(id);
4383
4162
  return response?.ok === true ? response.data ?? null : null;
4384
4163
  };
4385
- const [sessionUsage, setSessionUsage] = (0, import_react9.useState)(() => readSessionUsage(sessionId));
4386
- (0, import_react9.useEffect)(() => {
4164
+ const [sessionUsage, setSessionUsage] = (0, import_react7.useState)(() => readSessionUsage(sessionId));
4165
+ (0, import_react7.useEffect)(() => {
4387
4166
  setSessionUsage(readSessionUsage(sessionId));
4388
4167
  if (sessionId === void 0) return;
4389
4168
  const unsubscribe = subscribeSessionUsage(sessionId, () => setSessionUsage(readSessionUsage(sessionId)));
4390
4169
  void fetchSessionUsage(sessionId);
4391
4170
  return unsubscribe;
4392
4171
  }, [sessionId]);
4393
- (0, import_react9.useLayoutEffect)(() => {
4172
+ (0, import_react7.useLayoutEffect)(() => {
4394
4173
  const viewIds = viewTabs.map((tab) => tab.id);
4395
4174
  const read = () => {
4396
4175
  const next = getActiveConversationViewId(getShellFrame(rootRef.current), viewIds);
@@ -4427,14 +4206,14 @@ function QuotaWidget(props) {
4427
4206
  setLoading(false);
4428
4207
  }
4429
4208
  };
4430
- (0, import_react9.useEffect)(() => {
4209
+ (0, import_react7.useEffect)(() => {
4431
4210
  void load(true);
4432
4211
  const id = setInterval(() => {
4433
4212
  void load(false);
4434
4213
  }, 6e4);
4435
4214
  return () => clearInterval(id);
4436
4215
  }, []);
4437
- (0, import_react9.useLayoutEffect)(() => {
4216
+ (0, import_react7.useLayoutEffect)(() => {
4438
4217
  if (!visible || quotaViewActive2) return;
4439
4218
  const node = rootRef.current;
4440
4219
  if (node === null) return;
@@ -4562,9 +4341,9 @@ function QuotaWidget(props) {
4562
4341
  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
4342
  const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
4564
4343
  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: [
4344
+ const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
4345
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
4346
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
4568
4347
  "\xA5",
4569
4348
  fmtTurnCost(sessionUsage.cost)
4570
4349
  ] })
@@ -4572,50 +4351,50 @@ function QuotaWidget(props) {
4572
4351
  let body = null;
4573
4352
  if (!collapsed) {
4574
4353
  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") }),
4354
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4355
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: t("widget.loading") }),
4577
4356
  sessionCostRow
4578
4357
  ] });
4579
4358
  } 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") }),
4359
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4360
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
4582
4361
  sessionCostRow
4583
4362
  ] });
4584
4363
  } 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 })
4364
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4365
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
4366
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
4367
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
4368
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
4590
4369
  ] }),
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: [
4370
+ usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
4371
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
4372
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
4594
4373
  "\xA5",
4595
4374
  fmt(usage.data.summary.today.cost)
4596
4375
  ] })
4597
4376
  ] }),
4598
4377
  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") })
4378
+ data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", children: [
4379
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
4380
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
4602
4381
  ] })
4603
4382
  ] });
4604
4383
  } 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") }),
4384
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4385
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
4607
4386
  sessionCostRow
4608
4387
  ] });
4609
4388
  }
4610
4389
  }
4611
4390
  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)(
4391
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4613
4392
  "div",
4614
4393
  {
4615
4394
  className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${hidden ? " dsh-quota-root--hidden" : ""}`,
4616
4395
  style,
4617
4396
  "aria-hidden": hidden,
4618
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4397
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4619
4398
  "div",
4620
4399
  {
4621
4400
  ref: rootRef,
@@ -4627,14 +4406,14 @@ function QuotaWidget(props) {
4627
4406
  onPointerCancel: () => endDrag(false),
4628
4407
  onLostPointerCapture: () => endDrag(false),
4629
4408
  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") })
4409
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-header", children: [
4410
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-title", children: [
4411
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
4412
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: dotClass }),
4413
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
4635
4414
  ] }),
4636
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-quota-actions", children: [
4637
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4415
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-actions", children: [
4416
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4638
4417
  "button",
4639
4418
  {
4640
4419
  className: "dsh-quota-btn",
@@ -4650,7 +4429,7 @@ function QuotaWidget(props) {
4650
4429
  children: "\u21BB"
4651
4430
  }
4652
4431
  ),
4653
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4432
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4654
4433
  "button",
4655
4434
  {
4656
4435
  className: "dsh-quota-btn",
@@ -4668,7 +4447,7 @@ function QuotaWidget(props) {
4668
4447
  )
4669
4448
  ] })
4670
4449
  ] }),
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: [
4450
+ 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
4451
  fmt(primary.total),
4673
4452
  " ",
4674
4453
  primary.currency
@@ -4699,7 +4478,7 @@ function conversationViewsSource(slots) {
4699
4478
  }
4700
4479
 
4701
4480
  // src/client/index.tsx
4702
- var import_jsx_runtime9 = require("react/jsx-runtime");
4481
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4703
4482
  var name = "dsh-usage-dashboard";
4704
4483
  var inject = ["slots", "locale", "layout"];
4705
4484
  var openQuotaTab = null;
@@ -4720,15 +4499,11 @@ function apply(ctx) {
4720
4499
  });
4721
4500
  ctx.slots.inject("shell.overlay", () => ctx.slots.register(
4722
4501
  { 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 }) }) })
4502
+ ({ 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
4503
  ));
4725
4504
  ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register(
4726
4505
  { 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 }) }) })
4506
+ ({ 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
4507
  ));
4733
4508
  ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register(
4734
4509
  {
@@ -4738,13 +4513,13 @@ function apply(ctx) {
4738
4513
  locale: NS,
4739
4514
  label: () => t("messageCost.label")
4740
4515
  },
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 }) }) })
4516
+ ({ 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
4517
  ));
4743
4518
  const QuotaView = ({ t: slotT, sessionId, openView }) => {
4744
- (0, import_react10.useEffect)(() => {
4519
+ (0, import_react8.useEffect)(() => {
4745
4520
  if (openView !== void 0) openQuotaTab = openView;
4746
4521
  }, [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 }) }) });
4522
+ 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
4523
  };
4749
4524
  ctx.effect(() => {
4750
4525
  let disposeView = null;
@@ -4780,14 +4555,14 @@ function apply(ctx) {
4780
4555
  disposeView?.();
4781
4556
  };
4782
4557
  }, "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") })
4558
+ const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel", children: [
4559
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel-head", "data-window-drag": true, children: [
4560
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { className: "dq-panel-title", children: slotT("nav.quota") }),
4561
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-panel-caption", children: slotT("panel.caption") })
4787
4562
  ] }),
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 }) }) })
4563
+ /* @__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
4564
  ] });
4790
- const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4565
+ const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4791
4566
  "svg",
4792
4567
  {
4793
4568
  className: "dq-side-icon",
@@ -4803,23 +4578,23 @@ function apply(ctx) {
4803
4578
  "aria-hidden": "true",
4804
4579
  focusable: "false",
4805
4580
  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" })
4581
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 3.3 8 8.7l4.5-5.4" }),
4582
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 8.7V13.2" }),
4583
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 10.2h9" }),
4584
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 12.1h9" })
4810
4585
  ]
4811
4586
  }
4812
4587
  );
4813
4588
  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)(() => {
4589
+ const [active, setActive] = (0, import_react8.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4590
+ (0, import_react8.useEffect)(() => {
4816
4591
  const sync = () => {
4817
4592
  setActive(ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4818
4593
  };
4819
4594
  sync();
4820
4595
  return ctx.layout.panelInfo.subscribe(sync);
4821
4596
  }, []);
4822
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4597
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4823
4598
  "button",
4824
4599
  {
4825
4600
  type: "button",
@@ -4831,8 +4606,8 @@ function apply(ctx) {
4831
4606
  ctx.layout.selectPanel(active ? null : QUOTA_PANEL_ID);
4832
4607
  },
4833
4608
  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") })
4609
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuotaGlyph, { size: 16 }) }),
4610
+ wide && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-label", children: slotT("nav.quota") })
4836
4611
  ]
4837
4612
  }
4838
4613
  );