@cassius0924/dsh-usage-dashboard 0.8.0 → 0.8.3

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